forked from platypush/platypush
vue.js refactor part 1 - prepared webpanel logic and migrated light.hue plugin
This commit is contained in:
parent
f053aa455d
commit
e72d4de581
|
@ -12,3 +12,4 @@ platypush/backend/http/static/resources/*
|
||||||
docs/build
|
docs/build
|
||||||
.idea/
|
.idea/
|
||||||
config
|
config
|
||||||
|
platypush/backend/http/static/css/*/.sass-cache/
|
||||||
|
|
|
@ -24,29 +24,39 @@ def index():
|
||||||
if not authentication_ok(request):
|
if not authentication_ok(request):
|
||||||
return authenticate()
|
return authenticate()
|
||||||
|
|
||||||
# These plugins have their own template file but won't be shown as a tab in
|
|
||||||
# the web panel. This is usually the case for plugins that only include JS
|
|
||||||
# code but no template content.
|
|
||||||
_hidden_plugins = {
|
|
||||||
'assistant.google'
|
|
||||||
}
|
|
||||||
|
|
||||||
configured_plugins = Config.get_plugins()
|
configured_plugins = Config.get_plugins()
|
||||||
enabled_plugins = {}
|
enabled_templates = {}
|
||||||
hidden_plugins = {}
|
enabled_scripts = {}
|
||||||
|
enabled_styles = {}
|
||||||
|
|
||||||
|
js_folder = os.path.abspath(
|
||||||
|
os.path.join(template_folder, '..', 'static', 'js'))
|
||||||
|
style_folder = os.path.abspath(
|
||||||
|
os.path.join(template_folder, '..', 'static', 'css', 'dist'))
|
||||||
|
|
||||||
for plugin, conf in configured_plugins.items():
|
for plugin, conf in configured_plugins.items():
|
||||||
template_file = os.path.join('panel', plugin, 'index.html')
|
template_file = os.path.join(
|
||||||
if os.path.isfile(os.path.join(template_folder, template_file)):
|
template_folder, 'plugins', plugin, 'index.html')
|
||||||
if plugin in _hidden_plugins:
|
|
||||||
hidden_plugins[plugin] = conf
|
script_file = os.path.join(js_folder, 'plugins', plugin, 'index.js')
|
||||||
else:
|
style_file = os.path.join(style_folder, 'webpanel', 'plugins', plugin+'.css')
|
||||||
enabled_plugins[plugin] = conf
|
|
||||||
|
if os.path.isfile(template_file):
|
||||||
|
conf['_template_file'] = '/' + '/'.join(template_file.split(os.sep)[-3:])
|
||||||
|
enabled_templates[plugin] = conf
|
||||||
|
|
||||||
|
if os.path.isfile(script_file):
|
||||||
|
conf['_script_file'] = '/'.join(script_file.split(os.sep)[-4:])
|
||||||
|
enabled_scripts[plugin] = conf
|
||||||
|
|
||||||
|
if os.path.isfile(style_file):
|
||||||
|
conf['_style_file'] = 'css/dist/' + style_file[len(style_folder)+1:]
|
||||||
|
enabled_styles[plugin] = conf
|
||||||
|
|
||||||
http_conf = Config.get('backend.http')
|
http_conf = Config.get('backend.http')
|
||||||
return render_template('index.html', plugins=enabled_plugins,
|
return render_template('index.html', templates=enabled_templates,
|
||||||
hidden_plugins=hidden_plugins, utils=HttpUtils,
|
scripts=enabled_scripts, styles=enabled_styles,
|
||||||
token=Config.get('token'),
|
utils=HttpUtils, token=Config.get('token'),
|
||||||
websocket_port=get_websocket_port(),
|
websocket_port=get_websocket_port(),
|
||||||
has_ssl=http_conf.get('ssl_cert') is not None)
|
has_ssl=http_conf.get('ssl_cert') is not None)
|
||||||
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
|
|
||||||
ul.tab-nav {
|
|
||||||
list-style: none;
|
|
||||||
border-bottom: 1px solid #bbb;
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.tab-nav li {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.tab-nav li a.button {
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
margin-bottom: -1px;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.tab-nav li a.active.button {
|
|
||||||
border-bottom: 1px solid #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content .tab-pane {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content .tab-pane.active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
//// General purpose classes /////
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
background: $selected-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-right {
|
||||||
|
text-align: right !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
//// UI elements definitions /////
|
||||||
|
|
||||||
|
@import 'common/elements/button';
|
||||||
|
@import 'common/elements/switch';
|
||||||
|
@import 'common/elements/slider';
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
button[disabled],
|
||||||
|
.button[disabled] {
|
||||||
|
color: #bbb;
|
||||||
|
background: rgba(240,240,240,1);
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #d8ffe0 !important;
|
||||||
|
border: 1px solid #98efb0 !important;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
.slider {
|
||||||
|
@include appearance(none);
|
||||||
|
@include transition(opacity .2s);
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: $slider-bg;
|
||||||
|
outline: none;
|
||||||
|
opacity: 0.7;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: $slider-thumb-bg;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled]::-webkit-slider-thumb {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-thumb {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
background: $slider-thumb-bg;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
@import url(https://fonts.googleapis.com/css?family=Francois+One);
|
||||||
|
@import url(https://fonts.googleapis.com/css?family=PT+Sans);
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Audiowide';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local("Audiowide"), local("Audiowide-Regular"), url(http://themes.googleusercontent.com/static/fonts/audiowide/v2/8XtYtNKEyyZh481XVWfVOj8E0i7KZn-EPnyo3HZu7kw.woff) format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
padding-top: 1rem;
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
label, label:before, label:after,
|
||||||
|
input[type=checkbox], input[type=checkbox]:before, input[type=checkbox]:after,
|
||||||
|
.feature, .feature:before, .feature:after {
|
||||||
|
transition: all 250ms ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
label:before, label:after,
|
||||||
|
input[type=checkbox]:before, input[type=checkbox]:after,
|
||||||
|
.feature:before, .feature:after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
position: relative;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background-color: $switch-bg-1;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: $switch-shadow-1;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: 1.4em;
|
||||||
|
transition: all 350ms ease-in;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 22.7272727273px;
|
||||||
|
height: 22.7272727273px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: $switch-bg-2;
|
||||||
|
box-shadow: $switch-shadow-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 35%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 4px;
|
||||||
|
height: 12px;
|
||||||
|
background-color: $switch-bg-3;
|
||||||
|
box-shadow: $switch-shadow-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before, &:after {
|
||||||
|
transition-duration: 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
&:before { box-shadow: $switch-shadow-hover; }
|
||||||
|
&:after { background-color: $switch-bg-hover; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox]:checked + label {
|
||||||
|
box-shadow: $switch-shadow-checked-1;
|
||||||
|
&:before { box-shadow: $switch-shadow-checked-2; }
|
||||||
|
&:after { background-color: $switch-bg-checked; }
|
||||||
|
}
|
||||||
|
|
||||||
|
&.glow {
|
||||||
|
label {
|
||||||
|
background-color: $switch-bg-glow-2;
|
||||||
|
box-shadow: $switch-shadow-glow-1;
|
||||||
|
|
||||||
|
&:before { box-shadow: $switch-shadow-glow-2; }
|
||||||
|
&:after {
|
||||||
|
background-color: $switch-bg-glow-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
label:hover {
|
||||||
|
&:before { box-shadow: $switch-shadow-glow-hover; }
|
||||||
|
&:after { background-color: $switch-bg-glow-hover; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox]:checked + label {
|
||||||
|
box-shadow: $switch-shadow-glow-checked-1;
|
||||||
|
&:before { box-shadow: $switch-shadow-glow-checked-2; }
|
||||||
|
&:after { background-color: $switch-bg-glow-checked; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
$widths: (
|
||||||
|
s: '(max-width: 720px)',
|
||||||
|
m: '(max-width: 1024px) and (min-width: 720px)',
|
||||||
|
l: '(min-width: 1024px)',
|
||||||
|
);
|
||||||
|
|
||||||
|
@for $i from 1 through 12 {
|
||||||
|
.col-#{$i} {
|
||||||
|
float: left;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
@if $i < 12 {
|
||||||
|
width: (4.66666666667%*$i) + (4% * if($i > 1, $i - 1, 0));
|
||||||
|
margin-left: 4%;
|
||||||
|
} @else {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-no-margin-#{$i} {
|
||||||
|
float: left;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: ((100%/12)*$i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@if $i < 12 {
|
||||||
|
.col-offset-#{$i} {
|
||||||
|
margin-left: (8.66666666667%*$i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $size, $width in $widths {
|
||||||
|
@media #{$width} {
|
||||||
|
@for $i from 1 through 12 {
|
||||||
|
.col-#{$size}-#{$i} {
|
||||||
|
float: left;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
@if $i < 12 {
|
||||||
|
width: (4.66666666667%*$i) + (4% * if($i > 1, $i - 1, 0));
|
||||||
|
margin-left: 4%;
|
||||||
|
} @else {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if $i < 12 {
|
||||||
|
.col-offset-#{$size}-#{$i} {
|
||||||
|
margin-left: (8.66666666667%*$i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-no-margin-#{$size}-#{$i} {
|
||||||
|
float: left;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: ((100%/12)*$i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$size}-hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$size}-visible {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
@mixin appearance($value) {
|
||||||
|
-webkit-appearance: $value;
|
||||||
|
-ms-appearance: $value;
|
||||||
|
-o-appearance: $value;
|
||||||
|
-ms-appearance: $value;
|
||||||
|
appearance: $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin transition($value) {
|
||||||
|
-webkit-transition: $value;
|
||||||
|
-ms-transition: $value;
|
||||||
|
-o-transition: $value;
|
||||||
|
-ms-transition: $value;
|
||||||
|
transition: $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin box-shadow($value) {
|
||||||
|
-webkit-box-shadow: $value;
|
||||||
|
-o-box-shadow: $value;
|
||||||
|
-ms-box-shadow: $value;
|
||||||
|
box-shadow: $value;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: rgba(10,10,10,0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
margin: 5% auto auto auto;
|
||||||
|
width: 70%;
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
margin: 0.5rem auto;
|
||||||
|
padding: 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
background-color: $modal-bg;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .1rem;
|
||||||
|
line-height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 2.5rem 2rem 1.5rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
#notification-container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 25em;
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
background: rgba(180,245,188,0.85);
|
||||||
|
border: 1px solid #bbb;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification:hover {
|
||||||
|
background: rgba(160,235,168,0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-title {
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: capitalize;
|
||||||
|
line-height: 30px;
|
||||||
|
letter-spacing: .1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-body {
|
||||||
|
height: 6em;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
letter-spacing: .05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-text {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-image {
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: .6em;
|
||||||
|
padding-bottom: .6em;
|
||||||
|
|
||||||
|
.notification-image-item {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa.notification-image-item {
|
||||||
|
margin-top: .8em;
|
||||||
|
margin-left: .2em;
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
//// Common defaults
|
||||||
|
$default-bg: #f4f5f6 !default;
|
||||||
|
$default-fg: black !default;
|
||||||
|
$default-fg-2: #333333 !default;
|
||||||
|
|
||||||
|
$default-font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif !default;
|
||||||
|
$default-border: 1px solid #e1e4e8 !default;
|
||||||
|
$default-border-2: 1px solid #dddddd !default;
|
||||||
|
$default-bottom: $default-border !default;
|
||||||
|
$default-link-fg: #5f7869 !default;
|
||||||
|
|
||||||
|
$selected-bg: #c8ffd0 !default;
|
||||||
|
$hover-bg: #def6ea !default;
|
||||||
|
$header-bg: $default_bg !default;
|
||||||
|
$nav-bg: #e8e8e8 !default;
|
||||||
|
$nav-fg: $default-link-fg;
|
||||||
|
$modal-bg: #f0f0f0 !default;
|
||||||
|
|
||||||
|
//// Switch element
|
||||||
|
$switch-bg-1: #f9f8f6 !default;
|
||||||
|
$switch-bg-2: #38ffa0 !default;
|
||||||
|
$switch-bg-3: #cccccc !default;
|
||||||
|
$switch-bg-hover: #b3b3b3 !default;
|
||||||
|
$switch-bg-checked: #38ffa0 !default;
|
||||||
|
$switch-bg-glow-1: #111111 !default;
|
||||||
|
$switch-bg-glow-2: #ffffff !default;
|
||||||
|
$switch-bg-glow-3: #aaaaaa !default;
|
||||||
|
$switch-bg-glow-hover: #ffffff !default;
|
||||||
|
$switch-bg-glow-checked: #00e094 !default;
|
||||||
|
|
||||||
|
$switch-shadow-1: 0 5px 10px 0px #333, 0 15px 20px 0px #cccccc !default;
|
||||||
|
$switch-shadow-2: inset 0 0 0 5px #ccc, inset 0 0 0 14px #f9f8f6 !default;
|
||||||
|
$switch-shadow-3: 0 0 0 2.5px #f9f8f6 !default;
|
||||||
|
$switch-shadow-hover: inset 0 0 0 5px #b3b3b3, inset 0 0 0 14px #f9f8f6 !default;
|
||||||
|
$switch-shadow-checked-1: 0 2px 5px 0px gray, 0 15px 20px 0px transparent !default;
|
||||||
|
$switch-shadow-checked-2: inset 0 0 0 5px #38ffa0, inset 0 0 0 14px #f9f8f6 !default;
|
||||||
|
$switch-shadow-glow-1: 0 5px 10px 0 #aaa, 0 0 0 3px #bbb, 0 0 8px 2px transparent, 0 0 0 6px #eee !default;
|
||||||
|
$switch-shadow-glow-2: inset 0 0 0 5px #aaa, inset 0 0 0 14px #fff !default;
|
||||||
|
$switch-shadow-glow-hover: inset 0 0 0 5px #fff, inset 0 0 0 14px #fff !default;
|
||||||
|
$switch-shadow-glow-checked-1: 0 0px 8px 0 #00ad72, 0 0 0 3px #00e094, 0 0 30px 0 #00e094, 0 0 0 6px #fff !default;
|
||||||
|
$switch-shadow-glow-checked-2: inset 0 0 0 5px #00e094, inset 0 0 0 14px #fff !default;
|
||||||
|
|
||||||
|
//// Slier element
|
||||||
|
$slider-bg: #e4e4e4 !default;
|
||||||
|
$slider-thumb-bg: #4caf50 !default;
|
||||||
|
|
||||||
|
//// Header style
|
||||||
|
$header-bottom: $default-bottom;
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
header {
|
||||||
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
background: $header-bg;
|
||||||
|
padding: 1rem 2.5rem;
|
||||||
|
// margin: 0 1rem 3.5rem -1rem;
|
||||||
|
border-bottom: $header-bottom;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 25px;
|
||||||
|
|
||||||
|
.logo-1 {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-time {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 3rem;
|
||||||
|
|
||||||
|
.date {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
@import 'common/vars';
|
||||||
|
|
||||||
|
@import 'common/mixins';
|
||||||
|
@import 'common/layout';
|
||||||
|
@import 'common/elements';
|
||||||
|
@import 'common/modal';
|
||||||
|
@import 'common/notification';
|
||||||
|
|
||||||
|
@import 'header';
|
||||||
|
@import 'nav';
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
font-family: $default-font-family;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $default-link-fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-container {
|
||||||
|
border: $default-border-2;
|
||||||
|
border-radius: 1rem;
|
||||||
|
margin: 1.5rem;
|
||||||
|
box-shadow: 8px 8px 6px -1px rgba(187,187,187,0.75);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
nav {
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
background: $nav-bg;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: $default-bottom;
|
||||||
|
box-shadow: 0 2.5px 4px 0 #bbb;
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .1rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-radius: 2rem;
|
||||||
|
background: $hover-bg;
|
||||||
|
letter-spacing: .4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
color: $nav-fg;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $nav-fg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.decorator {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 20px solid transparent;
|
||||||
|
border-bottom: 20px solid transparent;
|
||||||
|
border-left: 17px solid $selected-bg;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.decorator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.selected {
|
||||||
|
border-radius: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
@import 'common/vars';
|
||||||
|
@import 'common/layout';
|
||||||
|
@import 'webpanel/plugins/light.hue/vars';
|
||||||
|
|
||||||
|
.light-hue-container {
|
||||||
|
display: flex;
|
||||||
|
color: $default-fg-2;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 3.8rem;
|
||||||
|
letter-spacing: .1rem;
|
||||||
|
|
||||||
|
.groups,
|
||||||
|
.scenes,
|
||||||
|
.units {
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-right: $default-border-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
padding: .75rem;
|
||||||
|
background: $default-bg;
|
||||||
|
border-bottom: $default-border-2;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-radius: 0 1rem 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.group,
|
||||||
|
.scene,
|
||||||
|
.unit,
|
||||||
|
.group-controller {
|
||||||
|
padding: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: $default-border-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.hidden) {
|
||||||
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* > .properties {
|
||||||
|
margin: 1.5rem auto;
|
||||||
|
padding: 1.5rem;
|
||||||
|
font-weight: 100;
|
||||||
|
border: $default-border-2;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
background: $light-hue-properties-bg;
|
||||||
|
box-shadow: $light-hue-properties-shadow;
|
||||||
|
|
||||||
|
.slider-container {
|
||||||
|
@extend .vertical-center;
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
* > .fa {
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* > .color-logo {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* > .color-logo-red { background-color: red; }
|
||||||
|
* > .color-logo-green { background-color: green; }
|
||||||
|
* > .color-logo-blue { background-color: blue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
* > .properties {
|
||||||
|
background: $light-hue-properties-hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.group {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-controller {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.groups {
|
||||||
|
.title {
|
||||||
|
border-radius: 1rem 0 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.units {
|
||||||
|
.title {
|
||||||
|
border-radius: 0 1rem 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
$light-hue-properties-bg: rgba(239,239,240,0.5);
|
||||||
|
$light-hue-properties-hover-bg: white;
|
||||||
|
$light-hue-properties-shadow: 0 0 4px 2px rgba(187,187,187,0.75);
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
@import url(https://fonts.googleapis.com/css?family=Francois+One);
|
|
||||||
@import url(https://fonts.googleapis.com/css?family=PT+Sans);
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Audiowide';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: local("Audiowide"), local("Audiowide-Regular"), url(http://themes.googleusercontent.com/static/fonts/audiowide/v2/8XtYtNKEyyZh481XVWfVOj8E0i7KZn-EPnyo3HZu7kw.woff) format("woff"); }
|
|
||||||
|
|
||||||
.toggle {
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
user-select: none; }
|
|
||||||
|
|
||||||
.toggle--checkbox {
|
|
||||||
display: none !important; }
|
|
||||||
|
|
||||||
.toggle--btn {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
font-size: 1.4em;
|
|
||||||
transition: all 350ms ease-in; }
|
|
||||||
.toggle--btn:hover {
|
|
||||||
cursor: pointer; }
|
|
||||||
|
|
||||||
.toggle--btn, .toggle--btn:before, .toggle--btn:after,
|
|
||||||
.toggle--checkbox,
|
|
||||||
.toggle--checkbox:before,
|
|
||||||
.toggle--checkbox:after,
|
|
||||||
.toggle--feature,
|
|
||||||
.toggle--feature:before,
|
|
||||||
.toggle--feature:after {
|
|
||||||
transition: all 250ms ease-in; }
|
|
||||||
.toggle--btn:before, .toggle--btn:after,
|
|
||||||
.toggle--checkbox:before,
|
|
||||||
.toggle--checkbox:after,
|
|
||||||
.toggle--feature:before,
|
|
||||||
.toggle--feature:after {
|
|
||||||
content: '';
|
|
||||||
display: block; }
|
|
||||||
|
|
||||||
/* =====================================================
|
|
||||||
Toggle - switch stylee
|
|
||||||
===================================================== */
|
|
||||||
.toggle--switch .toggle--btn {
|
|
||||||
position: relative;
|
|
||||||
width: 120px;
|
|
||||||
height: 44px;
|
|
||||||
font-family: 'PT Sans', Sans Serif;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #fff;
|
|
||||||
background: linear-gradient(90deg, #a4bf4d 0%, #a4bf4d 50%, #ca5046 50%, #ca5046 200%);
|
|
||||||
background-position: -80px 0;
|
|
||||||
background-size: 200% 100%;
|
|
||||||
box-shadow: inset 0 0px 22px -8px #111; }
|
|
||||||
.toggle--switch .toggle--btn, .toggle--switch .toggle--btn:before {
|
|
||||||
border-radius: 4px; }
|
|
||||||
.toggle--switch .toggle--btn:before {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 0;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
width: 52px;
|
|
||||||
height: 44px;
|
|
||||||
border: 2px solid #202027;
|
|
||||||
background-image: linear-gradient(90deg, transparent 50%, rgba(255, 255, 255, 0.15) 100%);
|
|
||||||
background-color: #2b2e3a;
|
|
||||||
background-size: 5px 5px;
|
|
||||||
text-indent: -100%; }
|
|
||||||
.toggle--switch .toggle--feature {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 44px;
|
|
||||||
text-shadow: 0 1px 2px #666; }
|
|
||||||
.toggle--switch .toggle--feature:before, .toggle--switch .toggle--feature:after {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%); }
|
|
||||||
.toggle--switch .toggle--feature:before {
|
|
||||||
content: attr(data-label-on);
|
|
||||||
left: -60%; }
|
|
||||||
.toggle--switch .toggle--feature:after {
|
|
||||||
content: attr(data-label-off);
|
|
||||||
right: 16%; }
|
|
||||||
.toggle--switch .toggle--checkbox:checked + .toggle--btn {
|
|
||||||
background-position: 0 0; }
|
|
||||||
.toggle--switch .toggle--checkbox:checked + .toggle--btn:before {
|
|
||||||
left: calc(100% - 52px); }
|
|
||||||
.toggle--switch .toggle--checkbox:checked + .toggle--btn .toggle--feature:before {
|
|
||||||
left: 20%; }
|
|
||||||
.toggle--switch .toggle--checkbox:checked + .toggle--btn .toggle--feature:after {
|
|
||||||
right: -60%; }
|
|
||||||
|
|
||||||
/* ======================================================
|
|
||||||
Push button toggle
|
|
||||||
====================================================== */
|
|
||||||
.toggle--push .toggle--btn {
|
|
||||||
position: relative;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
background-color: #f9f8f6;
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: 0 5px 10px 0px #333, 0 15px 20px 0px #cccccc; }
|
|
||||||
.toggle--push .toggle--btn, .toggle--push .toggle--btn:before, .toggle--push .toggle--btn:after {
|
|
||||||
transition-duration: 150ms; }
|
|
||||||
.toggle--push .toggle--btn:before {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 22.7272727273px;
|
|
||||||
height: 22.7272727273px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #38ffa0;
|
|
||||||
box-shadow: inset 0 0 0 5px #ccc, inset 0 0 0 14px #f9f8f6; }
|
|
||||||
.toggle--push .toggle--btn:after {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 35%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 4px;
|
|
||||||
height: 12px;
|
|
||||||
background-color: #ccc;
|
|
||||||
box-shadow: 0 0 0 2.5px #f9f8f6; }
|
|
||||||
.toggle--push .toggle--btn:hover:before {
|
|
||||||
box-shadow: inset 0 0 0 5px #b3b3b3, inset 0 0 0 14px #f9f8f6; }
|
|
||||||
.toggle--push .toggle--btn:hover:after {
|
|
||||||
background-color: #b3b3b3; }
|
|
||||||
.toggle--push .toggle--checkbox:checked + .toggle--btn {
|
|
||||||
box-shadow: 0 2px 5px 0px gray, 0 15px 20px 0px transparent; }
|
|
||||||
.toggle--push .toggle--checkbox:checked + .toggle--btn:before {
|
|
||||||
box-shadow: inset 0 0 0 5px #38ffa0, inset 0 0 0 14px #f9f8f6; }
|
|
||||||
.toggle--push .toggle--checkbox:checked + .toggle--btn:after {
|
|
||||||
background-color: #38ffa0; }
|
|
||||||
|
|
||||||
.toggle--push--glow {
|
|
||||||
background: #111;
|
|
||||||
padding: 50px 0;
|
|
||||||
margin-bottom: -50px; }
|
|
||||||
.toggle--push--glow .toggle--btn {
|
|
||||||
background-color: #dfdfdf;
|
|
||||||
box-shadow: 0 5px 10px 0px #333, 0 0 0 3px #444444, 0 0 8px 2px transparent, 0 0 0 6px #919191; }
|
|
||||||
.toggle--push--glow .toggle--btn:before {
|
|
||||||
box-shadow: inset 0 0 0 5px #aaa, inset 0 0 0 14px #dfdfdf; }
|
|
||||||
.toggle--push--glow .toggle--btn:after {
|
|
||||||
background-color: #aaa;
|
|
||||||
box-shadow: 0 0 0 2.5px #dfdfdf; }
|
|
||||||
.toggle--push--glow .toggle--btn:hover:before {
|
|
||||||
box-shadow: inset 0 0 0 5px #777777, inset 0 0 0 14px #dfdfdf; }
|
|
||||||
.toggle--push--glow .toggle--btn:hover:after {
|
|
||||||
background-color: #777777; }
|
|
||||||
.toggle--push--glow .toggle--checkbox:checked + .toggle--btn {
|
|
||||||
box-shadow: 0 0px 8px 0 #0072ad, 0 0 0 3px #0094e0, 0 0 30px 0 #0094e0, 0 0 0 6px #777777; }
|
|
||||||
.toggle--push--glow .toggle--checkbox:checked + .toggle--btn:before {
|
|
||||||
box-shadow: inset 0 0 0 5px #0094e0, inset 0 0 0 14px #dfdfdf; }
|
|
||||||
.toggle--push--glow .toggle--checkbox:checked + .toggle--btn:after {
|
|
||||||
background-color: #0094e0; }
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
function execute(request) {
|
||||||
|
var additionalPayload = {};
|
||||||
|
|
||||||
|
if (!('target' in request) || !request['target']) {
|
||||||
|
request['target'] = 'localhost';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('type' in request) || !request['type']) {
|
||||||
|
request['type'] = 'request';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.config.token) {
|
||||||
|
additionalPayload.headers = {
|
||||||
|
'X-Token': window.config.token
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axios.post('/execute', request, additionalPayload)
|
||||||
|
.then((response) => {
|
||||||
|
response = response.data.response;
|
||||||
|
if (!response.errors.length) {
|
||||||
|
resolve(response.output);
|
||||||
|
} else {
|
||||||
|
// TODO Handle error
|
||||||
|
reject(response.errors[0]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
// TODO Handle error
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function request(action, args={}) {
|
||||||
|
return execute({
|
||||||
|
type: 'request',
|
||||||
|
action: action,
|
||||||
|
args: args,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,32 +1,46 @@
|
||||||
// Declaration of the main vue app
|
Vue.component('app-header', {
|
||||||
var app;
|
template: '#tmpl-app-header',
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
now: new Date(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
function ready(callback){
|
created: function() {
|
||||||
if (document.readyState!='loading') callback();
|
const self = this;
|
||||||
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
|
setInterval(() => {
|
||||||
else document.attachEvent('onreadystatechange', function(){
|
self.now = new Date();
|
||||||
if (document.readyState=='complete') callback();
|
}, 1000)
|
||||||
});
|
},
|
||||||
}
|
|
||||||
|
|
||||||
ready(function() {
|
|
||||||
app = new Vue({
|
|
||||||
el: '#app',
|
|
||||||
delimiters: ['[[',']]'],
|
|
||||||
data: {
|
|
||||||
config: {foo:"bar"}
|
|
||||||
},
|
|
||||||
|
|
||||||
created: function() {
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted: function() {
|
|
||||||
},
|
|
||||||
|
|
||||||
updated: function() {
|
|
||||||
},
|
|
||||||
|
|
||||||
destroyed: function() {
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Vue.component('plugin', {
|
||||||
|
template: '#tmpl-plugin',
|
||||||
|
props: ['config','tag'],
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
selected: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Declaration of the main vue app
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
// Override {{ }} delimiters to prevent clash with Flask templates
|
||||||
|
delimiters: ['[[',']]'],
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
config: window.config,
|
||||||
|
selectedPlugin: undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted: function() {},
|
||||||
|
created: function() {},
|
||||||
|
updated: function() {},
|
||||||
|
destroyed: function() {},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
Vue.component('toggle-switch', {
|
||||||
|
template: '#tmpl-switch',
|
||||||
|
props: ['id','value','glow'],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggled: function(event) {
|
||||||
|
this.$emit('toggled', {
|
||||||
|
id: this.id,
|
||||||
|
value: !this.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,106 @@
|
||||||
|
// Source: https://gist.github.com/uredkar/bd305f2dda9abf5b393d417424777c87#file-cie_rgb_converter-js
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts CIE color space to RGB color space
|
||||||
|
* @param {Number} x
|
||||||
|
* @param {Number} y
|
||||||
|
* @param {Number} brightness - Ranges from 1 to 254
|
||||||
|
* @return {Array} Array that contains the color values for red, green and blue
|
||||||
|
*/
|
||||||
|
function toRGB(x, y, brightness) {
|
||||||
|
//Set to maximum brightness if no custom value was given (Not the slick ECMAScript 6 way for compatibility reasons)
|
||||||
|
if (brightness === undefined) {
|
||||||
|
brightness = 254;
|
||||||
|
}
|
||||||
|
|
||||||
|
var z = 1.0 - x - y;
|
||||||
|
var Y = (brightness / 254).toFixed(2);
|
||||||
|
var X = (Y / y) * x;
|
||||||
|
var Z = (Y / y) * z;
|
||||||
|
|
||||||
|
//Convert to RGB using Wide RGB D65 conversion
|
||||||
|
var red = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
|
||||||
|
var green = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
|
||||||
|
var blue = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
|
||||||
|
|
||||||
|
//If red, green or blue is larger than 1.0 set it back to the maximum of 1.0
|
||||||
|
if (red > blue && red > green && red > 1.0) {
|
||||||
|
|
||||||
|
green = green / red;
|
||||||
|
blue = blue / red;
|
||||||
|
red = 1.0;
|
||||||
|
}
|
||||||
|
else if (green > blue && green > red && green > 1.0) {
|
||||||
|
|
||||||
|
red = red / green;
|
||||||
|
blue = blue / green;
|
||||||
|
green = 1.0;
|
||||||
|
}
|
||||||
|
else if (blue > red && blue > green && blue > 1.0) {
|
||||||
|
|
||||||
|
red = red / blue;
|
||||||
|
green = green / blue;
|
||||||
|
blue = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reverse gamma correction
|
||||||
|
red = red <= 0.0031308 ? 12.92 * red : (1.0 + 0.055) * Math.pow(red, (1.0 / 2.4)) - 0.055;
|
||||||
|
green = green <= 0.0031308 ? 12.92 * green : (1.0 + 0.055) * Math.pow(green, (1.0 / 2.4)) - 0.055;
|
||||||
|
blue = blue <= 0.0031308 ? 12.92 * blue : (1.0 + 0.055) * Math.pow(blue, (1.0 / 2.4)) - 0.055;
|
||||||
|
|
||||||
|
|
||||||
|
//Convert normalized decimal to decimal
|
||||||
|
red = Math.round(red * 255);
|
||||||
|
green = Math.round(green * 255);
|
||||||
|
blue = Math.round(blue * 255);
|
||||||
|
|
||||||
|
if (isNaN(red))
|
||||||
|
red = 0;
|
||||||
|
|
||||||
|
if (isNaN(green))
|
||||||
|
green = 0;
|
||||||
|
|
||||||
|
if (isNaN(blue))
|
||||||
|
blue = 0;
|
||||||
|
|
||||||
|
|
||||||
|
return [red, green, blue];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts RGB color space to CIE color space
|
||||||
|
* @param {Number} red
|
||||||
|
* @param {Number} green
|
||||||
|
* @param {Number} blue
|
||||||
|
* @return {Array} Array that contains the CIE color values for x and y
|
||||||
|
*/
|
||||||
|
function toXY(red, green, blue) {
|
||||||
|
if (red > 1) { red /= 255; }
|
||||||
|
if (green > 1) { green /= 255; }
|
||||||
|
if (blue > 1) { blue /= 255; }
|
||||||
|
|
||||||
|
//Apply a gamma correction to the RGB values, which makes the color more vivid and more the like the color displayed on the screen of your device
|
||||||
|
var red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
|
||||||
|
var green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
|
||||||
|
var blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);
|
||||||
|
|
||||||
|
//RGB values to XYZ using the Wide RGB D65 conversion formula
|
||||||
|
var X = red * 0.664511 + green * 0.154324 + blue * 0.162028;
|
||||||
|
var Y = red * 0.283881 + green * 0.668433 + blue * 0.047685;
|
||||||
|
var Z = red * 0.000088 + green * 0.072310 + blue * 0.986039;
|
||||||
|
|
||||||
|
//Calculate the xy values from the XYZ values
|
||||||
|
var x = (X / (X + Y + Z)).toFixed(4);
|
||||||
|
var y = (Y / (X + Y + Z)).toFixed(4);
|
||||||
|
|
||||||
|
if (isNaN(x))
|
||||||
|
x = 0;
|
||||||
|
|
||||||
|
if (isNaN(y))
|
||||||
|
y = 0;
|
||||||
|
|
||||||
|
|
||||||
|
return [x, y];
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
Vue.component('light-hue-property-selector', {
|
||||||
|
template: '#tmpl-light-hue-property-selector',
|
||||||
|
props: ['id','value'],
|
||||||
|
computed: {
|
||||||
|
rgb: function() {
|
||||||
|
if (!(this.value && 'xy' in this.value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toRGB(this.value.xy[0], this.value.xy[1], this.value.bri);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
changed: function(event) {
|
||||||
|
var value = parseInt(event.target.value);
|
||||||
|
var xy;
|
||||||
|
|
||||||
|
if (event.target.getAttribute('class').split(' ').indexOf('bri') > -1) {
|
||||||
|
this.$emit('bri-changed', {bri: value});
|
||||||
|
return;
|
||||||
|
} else if (event.target.getAttribute('class').split(' ').indexOf('ct') > -1) {
|
||||||
|
this.$emit('ct-changed', {ct: value});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.getAttribute('class').split(' ').indexOf('red') > -1) {
|
||||||
|
xy = toXY(value, this.rgb[1], this.rgb[2]);
|
||||||
|
} else if (event.target.getAttribute('class').split(' ').indexOf('green') > -1) {
|
||||||
|
xy = toXY(this.rgb[0], value, this.rgb[2]);
|
||||||
|
} else if (event.target.getAttribute('class').split(' ').indexOf('blue') > -1) {
|
||||||
|
xy = toXY(this.rgb[0], this.rgb[1], value);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('color-changed', {xy: xy});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
Vue.component('light-hue-group', {
|
||||||
|
template: '#tmpl-light-hue-group',
|
||||||
|
props: ['id','name'],
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('light-hue-group-controller', {
|
||||||
|
template: '#tmpl-light-hue-group-controller',
|
||||||
|
props: ['id','groups','value','collapsed'],
|
||||||
|
computed: {
|
||||||
|
lights: function() {
|
||||||
|
return this.groups[this.id].lights;
|
||||||
|
},
|
||||||
|
|
||||||
|
name: function() {
|
||||||
|
return this.groups[this.id].name;
|
||||||
|
},
|
||||||
|
|
||||||
|
properties: function() {
|
||||||
|
var self = this;
|
||||||
|
var avg = function(values) {
|
||||||
|
if (values.length) {
|
||||||
|
return values.reduce((sum,value) => sum+value) / values.length;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var getLightValues = function(attribute) {
|
||||||
|
return Object.values(self.lights).map(
|
||||||
|
light => attribute in light.state && light.state.on ? light.state[attribute] : undefined
|
||||||
|
).filter(value => value !== undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
xy: [
|
||||||
|
avg(getLightValues('xy').map(_ => parseFloat(_[0]))),
|
||||||
|
avg(getLightValues('xy').map(_ => parseFloat(_[1])))
|
||||||
|
],
|
||||||
|
ct: avg(getLightValues('ct')),
|
||||||
|
bri: avg(getLightValues('bri')),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggled: async function(event) {
|
||||||
|
await request(
|
||||||
|
'light.hue.' + (event.value ? 'on' : 'off'),
|
||||||
|
{ groups: [this.id] },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$emit('input', {
|
||||||
|
...this.value,
|
||||||
|
lights: this._updateLights('on', event.value),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$emit('input', {...this.value, state: {...this.value.state, any_on: event.value, all_on: event.value}});
|
||||||
|
},
|
||||||
|
|
||||||
|
propertiesCollapsedToggled: function() {
|
||||||
|
this.$emit('properties-collapsed-toggled', {
|
||||||
|
type: 'group',
|
||||||
|
id: this.id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
colorChanged: async function(event) {
|
||||||
|
await request(
|
||||||
|
'light.hue.xy',
|
||||||
|
{ value: event.xy, groups: [this.id] },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$emit('input', {
|
||||||
|
...this.value,
|
||||||
|
lights: this._updateLights('xy', event.xy),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
briChanged: async function(event) {
|
||||||
|
await request(
|
||||||
|
'light.hue.bri',
|
||||||
|
{ value: event.bri, groups: [this.id] },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$emit('input', {
|
||||||
|
...this.value,
|
||||||
|
lights: this._updateLights('bri', event.bri),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
ctChanged: async function(event) {
|
||||||
|
await request(
|
||||||
|
'light.hue.ct',
|
||||||
|
{ value: event.ct, groups: [this.id] },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$emit('input', {
|
||||||
|
...this.value,
|
||||||
|
lights: this._updateLights('ct', event.ct),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateLights: function(attr, value) {
|
||||||
|
var lights = [];
|
||||||
|
for (const light of Object.values(this.value.lights)) {
|
||||||
|
var state = light.state;
|
||||||
|
state[attr] = value;
|
||||||
|
lights.push({
|
||||||
|
...light,
|
||||||
|
state: state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return lights;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
Vue.component('light-hue', {
|
||||||
|
template: '#tmpl-light-hue',
|
||||||
|
props: ['config'],
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
groups: {},
|
||||||
|
lights: {},
|
||||||
|
scenes: {},
|
||||||
|
selectedGroup: undefined,
|
||||||
|
selectedScene: undefined,
|
||||||
|
selectedProperties: {
|
||||||
|
type: undefined,
|
||||||
|
id: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
_prepareGroups: function() {
|
||||||
|
for (const [groupId, group] of Object.entries(this.groups)) {
|
||||||
|
if (group.type !== 'Room' || group.recycle) {
|
||||||
|
delete this.groups[groupId];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.groups[groupId].scenes = {};
|
||||||
|
var lights = {};
|
||||||
|
|
||||||
|
for (const lightId of this.groups[groupId].lights) {
|
||||||
|
lights[lightId] = this.lights[lightId];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.groups[groupId].lights = lights;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_prepareScenes: function() {
|
||||||
|
for (const [sceneId, scene] of Object.entries(this.scenes)) {
|
||||||
|
if (scene.recycle) {
|
||||||
|
delete this.scenes[sceneId];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scenes[sceneId].groups = {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_linkLights: function() {
|
||||||
|
// Special group for lights with no group
|
||||||
|
this.groups[-1] = {
|
||||||
|
type: undefined,
|
||||||
|
lights: {},
|
||||||
|
scenes: {},
|
||||||
|
name: "[No Group]",
|
||||||
|
recycle: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [lightId, light] of Object.entries(this.lights)) {
|
||||||
|
this.lights[lightId].groups = {};
|
||||||
|
|
||||||
|
for (const [groupId, group] of Object.entries(this.groups)) {
|
||||||
|
if (lightId in group.lights) {
|
||||||
|
this.lights[lightId].groups[groupId] = group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!light.groups.length) {
|
||||||
|
this.groups[-1].lights[lightId] = light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.groups[-1].lights.length) {
|
||||||
|
delete this.groups[-1];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_linkScenes: function() {
|
||||||
|
for (const [sceneId, scene] of Object.entries(this.scenes)) {
|
||||||
|
for (const lightId of scene.lights) {
|
||||||
|
for (const [groupId, group] of Object.entries(this.lights[lightId].groups)) {
|
||||||
|
this.scenes[sceneId].groups[groupId] = group;
|
||||||
|
this.groups[groupId].scenes[sceneId] = scene;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: async function() {
|
||||||
|
const getLights = request('light.hue.get_lights');
|
||||||
|
const getGroups = request('light.hue.get_groups');
|
||||||
|
const getScenes = request('light.hue.get_scenes');
|
||||||
|
|
||||||
|
[this.lights, this.groups, this.scenes] = await Promise.all([getLights, getGroups, getScenes]);
|
||||||
|
|
||||||
|
this._prepareGroups();
|
||||||
|
this._prepareScenes();
|
||||||
|
this._linkLights();
|
||||||
|
this._linkScenes();
|
||||||
|
},
|
||||||
|
|
||||||
|
updatedGroup: function(event) {
|
||||||
|
for (const light of Object.values(this.groups[this.selectedGroup].lights)) {
|
||||||
|
if (event.state.any_on === event.state.all_on) {
|
||||||
|
light.state.on = event.state.all_on;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attr in ['bri', 'xy', 'ct']) {
|
||||||
|
if (attr in event.state) {
|
||||||
|
light.state[attr] = event.state[attr];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
selectScene: async function(event) {
|
||||||
|
await request(
|
||||||
|
'light.hue.scene', {
|
||||||
|
name: event.name,
|
||||||
|
groups: [this.groups[this.selectedGroup].name],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.selectedScene = event.id;
|
||||||
|
for (const light of Object.values(this.scenes[this.selectedScene].lights)) {
|
||||||
|
this.lights[light].state.on = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
collapsedToggled: function(event) {
|
||||||
|
if (event.type == this.selectedProperties.type
|
||||||
|
&& event.id == this.selectedProperties.id) {
|
||||||
|
this.selectedProperties = {
|
||||||
|
type: undefined,
|
||||||
|
id: undefined,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.selectedProperties = {
|
||||||
|
type: event.type,
|
||||||
|
id: event.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnitInput: function(event) {
|
||||||
|
var groups = this.lights[event.id].groups;
|
||||||
|
for (const [groupId, group] of Object.entries(groups)) {
|
||||||
|
if (event.on) {
|
||||||
|
this.groups[groupId].state.any_on = true;
|
||||||
|
this.groups[groupId].state.all_on = Object.values(group.lights).filter((l) => l.state.on).length === Object.values(group.lights).length;
|
||||||
|
} else {
|
||||||
|
this.groups[groupId].state.all_on = false;
|
||||||
|
this.groups[groupId].state.any_on = Object.values(group.lights).filter((l) => l.state.on).length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
created: function() {
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
Vue.component('light-hue-scene', {
|
||||||
|
template: '#tmpl-light-hue-scene',
|
||||||
|
props: ['id','name'],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
clicked: function(event) {
|
||||||
|
this.$emit('input', {id: this.id, name: this.name});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
Vue.component('light-hue-unit', {
|
||||||
|
template: '#tmpl-light-hue-unit',
|
||||||
|
props: ['id','capabilities','config','name',
|
||||||
|
'uniqueid','type','productname','modelid',
|
||||||
|
'manufacturername', 'swupdate','swversion','value',
|
||||||
|
'collapsed'],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggled: async function(event) {
|
||||||
|
await request(
|
||||||
|
'light.hue.' + (event.value ? 'on' : 'off'),
|
||||||
|
{ lights: [this.id] },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$emit('input', {
|
||||||
|
...this.value,
|
||||||
|
id: this.id,
|
||||||
|
on: event.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
colorChanged: async function(event) {
|
||||||
|
await request(
|
||||||
|
'light.hue.xy',
|
||||||
|
{ value: event.xy, lights: [this.id] },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$emit('input', {
|
||||||
|
...this.value,
|
||||||
|
id: this.id,
|
||||||
|
xy: event.xy
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
briChanged: async function(event) {
|
||||||
|
await request(
|
||||||
|
'light.hue.bri',
|
||||||
|
{ value: event.bri, lights: [this.id] },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$emit('input', {
|
||||||
|
...this.value,
|
||||||
|
id: this.id,
|
||||||
|
bri: event.bri
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
ctChanged: async function(event) {
|
||||||
|
await request(
|
||||||
|
'light.hue.ct',
|
||||||
|
{ value: event.ct, lights: [this.id] },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$emit('input', {
|
||||||
|
...this.value,
|
||||||
|
id: this.id,
|
||||||
|
ct: event.ct
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
propertiesCollapsedToggled: function() {
|
||||||
|
this.$emit('properties-collapsed-toggled', {
|
||||||
|
type: 'unit',
|
||||||
|
id: this.id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
|
|
||||||
$(function() {
|
|
||||||
$('ul.tab-nav li a.button').click(function() {
|
|
||||||
var href = $(this).attr('href');
|
|
||||||
|
|
||||||
$('li a.active.button', $(this).parent().parent()).removeClass('active');
|
|
||||||
$(this).addClass('active');
|
|
||||||
|
|
||||||
$('.tab-pane.active', $(href).parent()).removeClass('active');
|
|
||||||
$(href).addClass('active');
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
{% include 'elements/switch.html' %}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script type="text/x-template" id="tmpl-switch">
|
||||||
|
<div class="switch" :class="{glow: glow}" @click="toggled">
|
||||||
|
<input type="checkbox" v-model="value">
|
||||||
|
<label></label>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script type="text/x-template" id="tmpl-app-header">
|
||||||
|
<header class="s-hidden m-hidden">
|
||||||
|
<div class="row">
|
||||||
|
<div class="logo col-9">
|
||||||
|
<span class="logo-1">Platypush</span>
|
||||||
|
<span class="logo-2">Web Panel</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="date-time col-3">
|
||||||
|
<div class="date" v-text="now.toDateString().substring(0,10)"></div>
|
||||||
|
<div class="time" v-text="now.toTimeString().substring(0,8)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<app-header></app-header>
|
||||||
|
|
|
@ -1,75 +1,73 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<head>
|
<head>
|
||||||
<title>Platypush Web Console</title>
|
<title>Platypush Web Panel</title>
|
||||||
|
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton.css') }}">
|
||||||
<!-- <link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton-tabs.css') }}"> -->
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/normalize.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/normalize.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='font-awesome/css/font-awesome.min.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='font-awesome/css/font-awesome.min.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/application.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/dist/webpanel.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/toggles.css') }}">
|
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/vue.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/vue.js') }}"></script>
|
||||||
<!--<script type="text/javascript" src="{{ url_for('static', filename='js/vue.min.js') }}"></script>-->
|
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/axios.min.js') }}"></script>
|
||||||
|
|
||||||
<!-- <script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.3.1.min.js') }}"></script> -->
|
<script type="text/javascript" src="{{ url_for('static', filename='js/api.js') }}"></script>
|
||||||
<!-- <script type="text/javascript" src="{{ url_for('static', filename='js/jquery-ui-1.12.1.min.js') }}"></script> -->
|
|
||||||
<!-- <script type="text/javascript" src="{{ url_for('static', filename='js/skeleton-tabs.js') }}"></script> -->
|
{% for style in styles.values() %}
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename=style['_style_file']) }}">
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
|
|
||||||
<!-- <script type="text/javascript" src="{{ url_for('static', filename='js/pushbullet.js') }}"></script> -->
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
if (!window.config) {
|
if (!window.config) {
|
||||||
window.config = {};
|
window.config = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
window.config = { ...window.config,
|
window.config = { ...window.config,
|
||||||
websocket_port: {% print(websocket_port) %},
|
websocket_port: {{ websocket_port }},
|
||||||
has_ssl: {% print('true' if has_ssl else 'false') %},
|
has_ssl: {% print('true' if has_ssl else 'false') %},
|
||||||
plugins: JSON.parse('{% print(utils.to_json(plugins))|safe %}'),
|
templates: JSON.parse('{% print(utils.to_json(templates))|safe %}'),
|
||||||
|
scripts: JSON.parse('{% print(utils.to_json(scripts))|safe %}'),
|
||||||
};
|
};
|
||||||
|
|
||||||
{% if token %}
|
{% if token %}
|
||||||
window.config.token = '{% print(token) %}';
|
window.config.token = '{{ token }}';
|
||||||
{% else %}
|
{% else %}
|
||||||
window.config.token = undefined;
|
window.config.token = undefined;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{% include 'elements.html' %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<div id="app">
|
||||||
<div class="row">
|
{% include 'header.html' %}
|
||||||
<div class="logo nine columns">
|
|
||||||
<span class="logo-1">Platypush</span>
|
{% with plugins=templates.keys() %}
|
||||||
<span class="logo-2">Web Panel</span>
|
{% include 'nav.html' %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="plugins-container">
|
||||||
|
{% for plugin, conf in templates.items() %}
|
||||||
|
{% with configuration=templates[plugin], utils=utils %}
|
||||||
|
{% include conf['_template_file'] %}
|
||||||
|
<plugin tag="{{ utils.plugin_name_to_tag(plugin) }}"
|
||||||
|
:config="{{ conf }}" :class="{hidden: '{{ plugin }}' != selectedPlugin}"/>
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="date-time" class="three columns">
|
{% include 'plugins/template.html' %}
|
||||||
<div class="date"></div>
|
|
||||||
<div class="time"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<nav>
|
{% for script in scripts.values() %}
|
||||||
{% for plugin in plugins.keys()|sort() %}
|
<script type="text/javascript" src="{{ url_for('static', filename=script['_script_file']) }}"></script>
|
||||||
<a href="#{% print plugin %}">
|
{% endfor %}
|
||||||
{% print plugin %}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/elements.js') }}"></script>
|
||||||
<div id="app">
|
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
|
||||||
{% for plugin in plugins.keys()|sort() %}
|
|
||||||
{% with configuration=plugins[plugin], utils=utils %}
|
|
||||||
<div class="tab-pane plugin-tab-content" id="{% print plugin %}-container">
|
|
||||||
{% include 'panel/' + plugin + '/index.html' %}
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<head>
|
|
||||||
<title>Platypush Web Console</title>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton.css') }}">
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton-tabs.css') }}">
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/normalize.css') }}">
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='font-awesome/css/font-awesome.min.css') }}">
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/application.css') }}">
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/toggles.css') }}">
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/jquery-ui.css') }}">
|
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.3.1.min.js') }}"></script>
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-ui-1.12.1.min.js') }}"></script>
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/skeleton-tabs.js') }}"></script>
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/pushbullet.js') }}"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
window.websocket_port = {% print(websocket_port) %};
|
|
||||||
window.has_ssl = {% print('true' if has_ssl else 'false') %};
|
|
||||||
|
|
||||||
{% if token %}
|
|
||||||
window.token = '{% print(token) %}'
|
|
||||||
{% else %}
|
|
||||||
window.token = undefined
|
|
||||||
{% endif %}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<div class="row">
|
|
||||||
<div class="logo nine columns">
|
|
||||||
<span class="logo-1">Platypush</span>
|
|
||||||
<span class="logo-2">Web Panel</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="date-time" class="three columns">
|
|
||||||
<div class="date"></div>
|
|
||||||
<div class="time"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<ul class="tab-nav">
|
|
||||||
{% for plugin in plugins.keys()|sort() %}
|
|
||||||
<li>
|
|
||||||
<a class="button plugin-tab-item" href="#{% print plugin %}">
|
|
||||||
{% print plugin %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="tab-content">
|
|
||||||
{% for plugin in plugins.keys()|sort() %}
|
|
||||||
{% with configuration=plugins[plugin], utils=utils %}
|
|
||||||
<div class="tab-pane plugin-tab-content" id="{% print plugin %}">
|
|
||||||
{% include 'plugins/' + plugin + '.html' %}
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="notification-container"></div>
|
|
||||||
|
|
||||||
<div id="hidden-plugins-container">
|
|
||||||
{% for plugin in hidden_plugins.keys()|sort() %}
|
|
||||||
{% set configuration = plugins[plugin] %}
|
|
||||||
<div class="plugin" id="{% print plugin %}">
|
|
||||||
{% include 'plugins/' + plugin + '.html' %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<nav>
|
||||||
|
<ul class="row">
|
||||||
|
{% for plugin in plugins|sort %}
|
||||||
|
<li :class="{selected: '{{ plugin }}' == selectedPlugin}">
|
||||||
|
<a href="#{{ plugin }}" @click="selectedPlugin = '{{ plugin }}'">
|
||||||
|
{{ plugin }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<span class="decorator" v-if="'{{ plugin }}' == selectedPlugin"></span>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="#{{ plugin }}">Test tab 2</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#{{ plugin }}">Test tab 3</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
IT WORKED!
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script type="text/x-template" id="tmpl-light-hue-property-selector">
|
||||||
|
<div class="properties color-properties">
|
||||||
|
<div class="row slider-container red-properties" v-if="value.xy">
|
||||||
|
<div class="col-2">
|
||||||
|
<div class="color-logo color-logo-red"></div>
|
||||||
|
</div>
|
||||||
|
<div class="slider-container col-10">
|
||||||
|
<input class="slider red" type="range" min="0" max="255" v-model="rgb[0]" @change="changed">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row slider-container green-properties" v-if="value.xy">
|
||||||
|
<div class="col-2">
|
||||||
|
<div class="color-logo color-logo-green"></div>
|
||||||
|
</div>
|
||||||
|
<div class="slider-container col-10">
|
||||||
|
<input class="slider green" type="range" min="0" max="255" v-model="rgb[1]" @change="changed">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row slider-container blue-properties" v-if="value.xy">
|
||||||
|
<div class="col-2">
|
||||||
|
<div class="color-logo color-logo-blue"></div>
|
||||||
|
</div>
|
||||||
|
<div class="slider-container col-10">
|
||||||
|
<input class="slider blue" type="range" min="0" max="255" v-model="rgb[2]" @change="changed">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row slider-container bri-properties" v-if="value.bri !== undefined">
|
||||||
|
<div class="col-2">
|
||||||
|
<i class="fa fa-lightbulb-o"></i>
|
||||||
|
</div>
|
||||||
|
<div class="slider-container col-10">
|
||||||
|
<input class="slider bri" type="range" min="0" max="255" v-model="value.bri" @change="changed">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row slider-container ct-properties" v-if="value.ct !== undefined">
|
||||||
|
<div class="col-2">
|
||||||
|
<i class="fa fa-thermometer"></i>
|
||||||
|
</div>
|
||||||
|
<div class="slider-container col-10">
|
||||||
|
<input class="slider ct" type="range" min="0" max="255" v-model="value.ct" @change="changed">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/light.hue/elements.js') }}"></script>
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script type="text/x-template" id="tmpl-light-hue-group">
|
||||||
|
<div class="group">
|
||||||
|
<div v-text="name" @click="$emit('set-selected')"></div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-template" id="tmpl-light-hue-group-controller">
|
||||||
|
<div class="group-controller">
|
||||||
|
<div class="row vertical-center">
|
||||||
|
<div class="col-10" @click="propertiesCollapsedToggled">All Lights</div>
|
||||||
|
<div class="col-2 pull-right">
|
||||||
|
<toggle-switch v-model="value.state.any_on" :glow="true" @toggled="toggled"></toggle-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<light-hue-property-selector
|
||||||
|
v-model="properties"
|
||||||
|
:class="{hidden: collapsed}"
|
||||||
|
@bri-changed="briChanged"
|
||||||
|
@color-changed="colorChanged"
|
||||||
|
@ct-changed="ctChanged">
|
||||||
|
</light-hue-property-selector>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/light.hue/groups.js') }}"></script>
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/light.hue/converter.js') }}"></script>
|
||||||
|
|
||||||
|
{% include 'plugins/light.hue/elements.html' %}
|
||||||
|
{% include 'plugins/light.hue/groups.html' %}
|
||||||
|
{% include 'plugins/light.hue/scenes.html' %}
|
||||||
|
{% include 'plugins/light.hue/units.html' %}
|
||||||
|
|
||||||
|
<script type="text/x-template" id="tmpl-light-hue">
|
||||||
|
<div class="row light-hue-container">
|
||||||
|
<div class="groups col-no-margin-3 col-s-12">
|
||||||
|
<div class="title">Rooms</div>
|
||||||
|
<light-hue-group
|
||||||
|
v-for="(group, id) in groups"
|
||||||
|
:key="id"
|
||||||
|
:id="id"
|
||||||
|
:name="group.name"
|
||||||
|
:class="{selected: selectedGroup == id}"
|
||||||
|
@set-selected="selectedGroup = id">
|
||||||
|
</light-hue-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scenes col-no-margin-3 col-s-12">
|
||||||
|
<div class="title">Scenes</div>
|
||||||
|
<light-hue-scene
|
||||||
|
v-for="(scene, id) in scenes"
|
||||||
|
:key="id"
|
||||||
|
:id="id"
|
||||||
|
:name="scene.name"
|
||||||
|
:class="{hidden: !(selectedGroup in scene.groups), selected: selectedScene == id}"
|
||||||
|
@input="selectScene">
|
||||||
|
</light-hue-scene>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="units col-no-margin-6 col-s-12">
|
||||||
|
<div class="title">Lights</div>
|
||||||
|
<light-hue-group-controller
|
||||||
|
v-if="selectedGroup"
|
||||||
|
v-model="groups[selectedGroup]"
|
||||||
|
:id="selectedGroup"
|
||||||
|
:groups="groups"
|
||||||
|
:collapsed="!(selectedProperties.type == 'group' && selectedProperties.id == selectedGroup)"
|
||||||
|
@properties-collapsed-toggled="collapsedToggled"
|
||||||
|
@input="updatedGroup">
|
||||||
|
</light-hue-group-controller>
|
||||||
|
|
||||||
|
<light-hue-unit
|
||||||
|
v-for="(light, id) in lights"
|
||||||
|
v-model="light.state"
|
||||||
|
:key="id"
|
||||||
|
:id="id"
|
||||||
|
:name="light.name"
|
||||||
|
:config="light.config"
|
||||||
|
:capabilities="light.capabilities"
|
||||||
|
:modelid="light.modelid"
|
||||||
|
:manufacturername="light.manufacturername"
|
||||||
|
:swupdate="light.swupdate"
|
||||||
|
:swversion="light.swversion"
|
||||||
|
:uniqueid="light.uniqueid"
|
||||||
|
:type="light.type"
|
||||||
|
:productname="light.productname"
|
||||||
|
:collapsed="!(selectedProperties.type == 'unit' && selectedProperties.id == id)"
|
||||||
|
:class="{hidden: !(selectedGroup in light.groups)}"
|
||||||
|
@input="onUnitInput"
|
||||||
|
@properties-collapsed-toggled="collapsedToggled">
|
||||||
|
</light-hue-unit>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script type="text/x-template" id="tmpl-light-hue-scene">
|
||||||
|
<div class="scene" @click="clicked">
|
||||||
|
<div v-text="name"></div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/light.hue/scenes.js') }}"></script>
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script type="text/x-template" id="tmpl-light-hue-unit">
|
||||||
|
<div class="unit">
|
||||||
|
<div class="row vertical-center">
|
||||||
|
<div class="name col-10" v-text="name" @click="propertiesCollapsedToggled"></div>
|
||||||
|
<div class="col-2 pull-right">
|
||||||
|
<toggle-switch v-model="value.on" @toggled="toggled"></toggle-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<light-hue-property-selector
|
||||||
|
v-model="value"
|
||||||
|
:class="{hidden: collapsed}"
|
||||||
|
@bri-changed="briChanged"
|
||||||
|
@color-changed="colorChanged"
|
||||||
|
@ct-changed="ctChanged">
|
||||||
|
</light-hue-property-selector>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/light.hue/units.js') }}"></script>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<script type="text/x-template" id="tmpl-plugin">
|
||||||
|
<div class="plugin-container" :class="{selected: selected}">
|
||||||
|
<component :is="tag" :config="config"/>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
|
@ -96,5 +96,9 @@ class HttpUtils(object):
|
||||||
def get_config(cls, attr):
|
def get_config(cls, attr):
|
||||||
return Config.get(attr)
|
return Config.get(attr)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def plugin_name_to_tag(cls, module_name):
|
||||||
|
return module_name.replace('.','-')
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -135,3 +135,6 @@ websocket-client
|
||||||
# mpv player plugin
|
# mpv player plugin
|
||||||
python-mpv
|
python-mpv
|
||||||
|
|
||||||
|
# SCSS/SASS to CSS compiler for web pages style
|
||||||
|
pyScss
|
||||||
|
|
||||||
|
|
74
setup.py
74
setup.py
|
@ -2,10 +2,67 @@
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
|
import distutils.cmd
|
||||||
|
from distutils.command.build import build
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
def read(fname):
|
|
||||||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
class WebBuildCommand(distutils.cmd.Command):
|
||||||
|
"""
|
||||||
|
Custom command to build the web files
|
||||||
|
"""
|
||||||
|
|
||||||
|
description = 'Build components and styles for the web pages'
|
||||||
|
user_options = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_css_files():
|
||||||
|
from scss import Compiler
|
||||||
|
|
||||||
|
print('Building CSS files')
|
||||||
|
base_path = path(os.path.join('platypush','backend','http','static','css'))
|
||||||
|
input_path = path(os.path.join(base_path,'source'))
|
||||||
|
output_path = path(os.path.join(base_path,'dist'))
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(input_path):
|
||||||
|
scss_file = os.path.join(root, 'index.scss')
|
||||||
|
if os.path.isfile(scss_file):
|
||||||
|
css_path = os.path.split(scss_file[len(input_path):])[0][1:] + '.css'
|
||||||
|
css_dir = os.path.join(output_path, os.path.dirname(css_path))
|
||||||
|
css_file = os.path.join(css_dir, os.path.basename(css_path))
|
||||||
|
|
||||||
|
os.makedirs(css_dir, exist_ok=True)
|
||||||
|
print('\tGenerating CSS {scss} -> {css}'.format(scss=scss_file, css=css_file))
|
||||||
|
|
||||||
|
with open(css_file, 'w') as f:
|
||||||
|
css_content = Compiler(output_style='compressed', search_path=[root, input_path]).compile(scss_file)
|
||||||
|
f.write(css_content)
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.generate_css_files()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BuildCommand(build):
|
||||||
|
def run(self):
|
||||||
|
build.run(self)
|
||||||
|
self.run_command('web_build')
|
||||||
|
|
||||||
|
|
||||||
|
def path(fname=''):
|
||||||
|
return os.path.abspath(os.path.join(os.path.dirname(__file__), fname))
|
||||||
|
|
||||||
|
|
||||||
|
def readfile(fname):
|
||||||
|
with open(path(fname)) as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
def pkg_files(dir):
|
def pkg_files(dir):
|
||||||
paths = []
|
paths = []
|
||||||
|
@ -14,6 +71,7 @@ def pkg_files(dir):
|
||||||
paths.append(os.path.join('..', path, file))
|
paths.append(os.path.join('..', path, file))
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
|
||||||
def create_etc_dir():
|
def create_etc_dir():
|
||||||
path = '/etc/platypush'
|
path = '/etc/platypush'
|
||||||
try:
|
try:
|
||||||
|
@ -26,6 +84,7 @@ def create_etc_dir():
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
plugins = pkg_files('platypush/plugins')
|
plugins = pkg_files('platypush/plugins')
|
||||||
backend = pkg_files('platypush/backend')
|
backend = pkg_files('platypush/backend')
|
||||||
# create_etc_dir()
|
# create_etc_dir()
|
||||||
|
@ -37,8 +96,8 @@ setup(
|
||||||
author_email = "info@fabiomanganiello.com",
|
author_email = "info@fabiomanganiello.com",
|
||||||
description = ("Platypush service"),
|
description = ("Platypush service"),
|
||||||
license = "MIT",
|
license = "MIT",
|
||||||
python_requires = '>= 3',
|
python_requires = '>= 3.5',
|
||||||
keywords = "pushbullet notifications automation",
|
keywords = "home-automation iot mqtt websockets redis dashboard notificaions",
|
||||||
url = "https://github.com/BlackLight/platypush",
|
url = "https://github.com/BlackLight/platypush",
|
||||||
packages = find_packages(),
|
packages = find_packages(),
|
||||||
include_package_data = True,
|
include_package_data = True,
|
||||||
|
@ -50,10 +109,14 @@ setup(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
scripts = ['bin/platyvenv'],
|
scripts = ['bin/platyvenv'],
|
||||||
|
cmdclass = {
|
||||||
|
'web_build': WebBuildCommand,
|
||||||
|
'build': BuildCommand,
|
||||||
|
},
|
||||||
# data_files = [
|
# data_files = [
|
||||||
# ('/etc/platypush', ['platypush/config.example.yaml'])
|
# ('/etc/platypush', ['platypush/config.example.yaml'])
|
||||||
# ],
|
# ],
|
||||||
long_description = read('README.md'),
|
long_description = readfile('README.md'),
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Topic :: Utilities",
|
"Topic :: Utilities",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
|
@ -99,6 +162,7 @@ setup(
|
||||||
'Support for web media subtitles': ['webvtt-py'],
|
'Support for web media subtitles': ['webvtt-py'],
|
||||||
'Support for mopidy backend': ['websocket-client'],
|
'Support for mopidy backend': ['websocket-client'],
|
||||||
'Support for mpv player plugin': ['python-mpv'],
|
'Support for mpv player plugin': ['python-mpv'],
|
||||||
|
'Support for compiling SASS/SCSS styles to CSS': ['pyScss'],
|
||||||
# 'Support for Leap Motion backend': ['git+ssh://git@github.com:BlackLight/leap-sdk-python3.git'],
|
# 'Support for Leap Motion backend': ['git+ssh://git@github.com:BlackLight/leap-sdk-python3.git'],
|
||||||
# 'Support for Flic buttons': ['git+https://@github.com/50ButtonsEach/fliclib-linux-hci.git']
|
# 'Support for Flic buttons': ['git+https://@github.com/50ButtonsEach/fliclib-linux-hci.git']
|
||||||
# 'Support for media subtitles': ['git+https://github.com/agonzalezro/python-opensubtitles#egg=python-opensubtitles']
|
# 'Support for media subtitles': ['git+https://github.com/agonzalezro/python-opensubtitles#egg=python-opensubtitles']
|
||||||
|
|
Loading…
Reference in New Issue