forked from platypush/platypush
vue.js refactor part 1 - prepared webpanel logic and migrated light.hue plugin
This commit is contained in:
parent
95d2a48bff
commit
db710b3154
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -12,3 +12,4 @@ platypush/backend/http/static/resources/*
|
|||
docs/build
|
||||
.idea/
|
||||
config
|
||||
platypush/backend/http/static/css/*/.sass-cache/
|
||||
|
|
|
@ -24,29 +24,39 @@ def index():
|
|||
if not authentication_ok(request):
|
||||
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()
|
||||
enabled_plugins = {}
|
||||
hidden_plugins = {}
|
||||
enabled_templates = {}
|
||||
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():
|
||||
template_file = os.path.join('panel', plugin, 'index.html')
|
||||
if os.path.isfile(os.path.join(template_folder, template_file)):
|
||||
if plugin in _hidden_plugins:
|
||||
hidden_plugins[plugin] = conf
|
||||
else:
|
||||
enabled_plugins[plugin] = conf
|
||||
template_file = os.path.join(
|
||||
template_folder, 'plugins', plugin, 'index.html')
|
||||
|
||||
script_file = os.path.join(js_folder, 'plugins', plugin, 'index.js')
|
||||
style_file = os.path.join(style_folder, 'webpanel', 'plugins', plugin+'.css')
|
||||
|
||||
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')
|
||||
return render_template('index.html', plugins=enabled_plugins,
|
||||
hidden_plugins=hidden_plugins, utils=HttpUtils,
|
||||
token=Config.get('token'),
|
||||
return render_template('index.html', templates=enabled_templates,
|
||||
scripts=enabled_scripts, styles=enabled_styles,
|
||||
utils=HttpUtils, token=Config.get('token'),
|
||||
websocket_port=get_websocket_port(),
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
85
platypush/backend/http/static/css/source/common/layout.scss
Normal file
85
platypush/backend/http/static/css/source/common/layout.scss
Normal file
|
@ -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;
|
||||
}
|
||||
|
23
platypush/backend/http/static/css/source/common/mixins.scss
Normal file
23
platypush/backend/http/static/css/source/common/mixins.scss
Normal file
|
@ -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;
|
||||
}
|
||||
|
36
platypush/backend/http/static/css/source/common/modal.scss
Normal file
36
platypush/backend/http/static/css/source/common/modal.scss
Normal file
|
@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
49
platypush/backend/http/static/css/source/common/vars.scss
Normal file
49
platypush/backend/http/static/css/source/common/vars.scss
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
platypush/backend/http/static/css/source/webpanel/index.scss
Normal file
32
platypush/backend/http/static/css/source/webpanel/index.scss
Normal file
|
@ -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);
|
||||
}
|
||||
|
61
platypush/backend/http/static/css/source/webpanel/nav.scss
Normal file
61
platypush/backend/http/static/css/source/webpanel/nav.scss
Normal file
|
@ -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; }
|
||||
|
43
platypush/backend/http/static/js/api.js
Normal file
43
platypush/backend/http/static/js/api.js
Normal file
|
@ -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
|
||||
var app;
|
||||
Vue.component('app-header', {
|
||||
template: '#tmpl-app-header',
|
||||
data: function() {
|
||||
return {
|
||||
now: new Date(),
|
||||
};
|
||||
},
|
||||
|
||||
function ready(callback){
|
||||
if (document.readyState!='loading') callback();
|
||||
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
|
||||
else document.attachEvent('onreadystatechange', function(){
|
||||
if (document.readyState=='complete') callback();
|
||||
});
|
||||
}
|
||||
|
||||
ready(function() {
|
||||
app = new Vue({
|
||||
el: '#app',
|
||||
delimiters: ['[[',']]'],
|
||||
data: {
|
||||
config: {foo:"bar"}
|
||||
},
|
||||
|
||||
created: function() {
|
||||
},
|
||||
|
||||
mounted: function() {
|
||||
},
|
||||
|
||||
updated: function() {
|
||||
},
|
||||
|
||||
destroyed: function() {
|
||||
},
|
||||
});
|
||||
created: function() {
|
||||
const self = this;
|
||||
setInterval(() => {
|
||||
self.now = new Date();
|
||||
}, 1000)
|
||||
},
|
||||
});
|
||||
|
||||
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() {},
|
||||
});
|
||||
|
||||
|
|
14
platypush/backend/http/static/js/elements.js
Normal file
14
platypush/backend/http/static/js/elements.js
Normal file
|
@ -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
9
platypush/backend/http/static/js/lib/axios.min.js
vendored
Normal file
9
platypush/backend/http/static/js/lib/axios.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
106
platypush/backend/http/static/js/plugins/light.hue/converter.js
Normal file
106
platypush/backend/http/static/js/plugins/light.hue/converter.js
Normal file
|
@ -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});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
118
platypush/backend/http/static/js/plugins/light.hue/groups.js
Normal file
118
platypush/backend/http/static/js/plugins/light.hue/groups.js
Normal file
|
@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
162
platypush/backend/http/static/js/plugins/light.hue/index.js
Normal file
162
platypush/backend/http/static/js/plugins/light.hue/index.js
Normal file
|
@ -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();
|
||||
},
|
||||
});
|
||||
|
11
platypush/backend/http/static/js/plugins/light.hue/scenes.js
Normal file
11
platypush/backend/http/static/js/plugins/light.hue/scenes.js
Normal file
|
@ -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});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
69
platypush/backend/http/static/js/plugins/light.hue/units.js
Normal file
69
platypush/backend/http/static/js/plugins/light.hue/units.js
Normal file
|
@ -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;
|
||||
});
|
||||
});
|
2
platypush/backend/http/templates/elements.html
Normal file
2
platypush/backend/http/templates/elements.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
{% include 'elements/switch.html' %}
|
||||
|
7
platypush/backend/http/templates/elements/switch.html
Normal file
7
platypush/backend/http/templates/elements/switch.html
Normal file
|
@ -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>
|
||||
|
18
platypush/backend/http/templates/header.html
Normal file
18
platypush/backend/http/templates/header.html
Normal file
|
@ -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>
|
||||
<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-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/dist/webpanel.css') }}">
|
||||
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/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/vue.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/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/api.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">
|
||||
if (!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') %},
|
||||
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 %}
|
||||
window.config.token = '{% print(token) %}';
|
||||
window.config.token = '{{ token }}';
|
||||
{% else %}
|
||||
window.config.token = undefined;
|
||||
{% endif %}
|
||||
</script>
|
||||
|
||||
{% include 'elements.html' %}
|
||||
</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 id="app">
|
||||
{% include 'header.html' %}
|
||||
|
||||
{% with plugins=templates.keys() %}
|
||||
{% 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>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div id="date-time" class="three columns">
|
||||
<div class="date"></div>
|
||||
<div class="time"></div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{% include 'plugins/template.html' %}
|
||||
|
||||
<nav>
|
||||
{% for plugin in plugins.keys()|sort() %}
|
||||
<a href="#{% print plugin %}">
|
||||
{% print plugin %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
{% for script in scripts.values() %}
|
||||
<script type="text/javascript" src="{{ url_for('static', filename=script['_script_file']) }}"></script>
|
||||
{% endfor %}
|
||||
|
||||
<main>
|
||||
<div id="app">
|
||||
{% 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>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/elements.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
|
||||
</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>
|
||||
|
20
platypush/backend/http/templates/nav.html
Normal file
20
platypush/backend/http/templates/nav.html
Normal file
|
@ -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>
|
||||
|
6
platypush/backend/http/templates/plugins/template.html
Normal file
6
platypush/backend/http/templates/plugins/template.html
Normal file
|
@ -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):
|
||||
return Config.get(attr)
|
||||
|
||||
@classmethod
|
||||
def plugin_name_to_tag(cls, module_name):
|
||||
return module_name.replace('.','-')
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -135,3 +135,6 @@ websocket-client
|
|||
# mpv player plugin
|
||||
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 os
|
||||
import distutils.cmd
|
||||
from distutils.command.build import build
|
||||
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):
|
||||
paths = []
|
||||
|
@ -14,6 +71,7 @@ def pkg_files(dir):
|
|||
paths.append(os.path.join('..', path, file))
|
||||
return paths
|
||||
|
||||
|
||||
def create_etc_dir():
|
||||
path = '/etc/platypush'
|
||||
try:
|
||||
|
@ -26,6 +84,7 @@ def create_etc_dir():
|
|||
else:
|
||||
raise
|
||||
|
||||
|
||||
plugins = pkg_files('platypush/plugins')
|
||||
backend = pkg_files('platypush/backend')
|
||||
# create_etc_dir()
|
||||
|
@ -37,8 +96,8 @@ setup(
|
|||
author_email = "info@fabiomanganiello.com",
|
||||
description = ("Platypush service"),
|
||||
license = "MIT",
|
||||
python_requires = '>= 3',
|
||||
keywords = "pushbullet notifications automation",
|
||||
python_requires = '>= 3.5',
|
||||
keywords = "home-automation iot mqtt websockets redis dashboard notificaions",
|
||||
url = "https://github.com/BlackLight/platypush",
|
||||
packages = find_packages(),
|
||||
include_package_data = True,
|
||||
|
@ -50,10 +109,14 @@ setup(
|
|||
],
|
||||
},
|
||||
scripts = ['bin/platyvenv'],
|
||||
cmdclass = {
|
||||
'web_build': WebBuildCommand,
|
||||
'build': BuildCommand,
|
||||
},
|
||||
# data_files = [
|
||||
# ('/etc/platypush', ['platypush/config.example.yaml'])
|
||||
# ],
|
||||
long_description = read('README.md'),
|
||||
long_description = readfile('README.md'),
|
||||
classifiers = [
|
||||
"Topic :: Utilities",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
|
@ -99,6 +162,7 @@ setup(
|
|||
'Support for web media subtitles': ['webvtt-py'],
|
||||
'Support for mopidy backend': ['websocket-client'],
|
||||
'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 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']
|
||||
|
|
Loading…
Reference in a new issue