Webpanel refactoring in progress

This commit is contained in:
Fabio Manganiello 2019-05-26 03:53:48 +02:00
parent 897338399f
commit 01b111f436
28 changed files with 660 additions and 44 deletions

View File

@ -2,7 +2,6 @@ import os
from flask import Flask
from platypush.backend.http.utils import HttpUtils
from platypush.backend.http.app.utils import get_routes

View File

@ -15,5 +15,6 @@
@import 'common/elements/button';
@import 'common/elements/switch';
@import 'common/elements/range-slider';
@import 'common/elements/slider';

View File

@ -0,0 +1,54 @@
@supports (--css: variables) {
.input-range-container {
position: relative;
}
input[type="range"].multirange {
padding: 0;
margin: 0;
display: inline-block;
vertical-align: top;
opacity: 1 !important;
&.original {
position: absolute;
&::-webkit-slider-thumb {
position: relative;
z-index: 2;
}
&::-moz-range-thumb {
transform: scale(1); /* FF doesn't apply position it seems */
z-index: 1;
}
}
&::-moz-range-track {
border-color: transparent; /* needed to switch FF to "styleable" control */
}
&.ghost {
position: relative;
background: var(--track-background);
--track-background: linear-gradient(to right,
transparent var(--low), var(--range-color) 0,
var(--range-color) var(--high), transparent 0
) no-repeat 0 45% / 100% 40%;
--range-color: $slider-thumb-bg;
&::-webkit-slider-runnable-track {
background: var(--track-background);
}
&::-moz-range-track {
background: var(--track-background);
}
}
&[disabled]::-webkit-slider-thumb {
display: none;
}
}
}

View File

@ -6,11 +6,6 @@
border-radius: 5px;
background: $slider-bg;
outline: none;
opacity: 0.7;
&:hover {
opacity: 1;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
@ -26,6 +21,10 @@
display: none;
}
&.disabled {
opacity: 0.3;
}
&::-moz-range-thumb {
width: 25px;
height: 25px;

View File

@ -29,8 +29,11 @@ $widths: (
}
@if $i < 12 {
.col-offset-#{$i} {
margin-left: (8.66666666667%*$i);
.col-offset-#{$i}:first-child {
margin-left: (8.66666666667%*$i) !important;
}
.col-offset-#{$i}:not(first-child) {
margin-left: 4% + (8.66666666667%*$i) !important;
}
}
}

View File

@ -12,8 +12,11 @@ $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;
$nav-date-time-shadow: 2px 2px 2px #ccc !default;
$modal-bg: #f0f0f0 !default;
//// Switch element
@ -42,7 +45,9 @@ $switch-shadow-glow-checked-2: inset 0 0 0 5px #00e094, inset 0 0 0 14px #fff !d
//// Slier element
$slider-bg: #e4e4e4 !default;
$slider-thumb-bg: #4caf50 !default;
$slider-thumb-bg: rgba(0,215,80,1.0) !default;
$slider-thumb-disabled-bg: rgba(0,215,80,0.3) !default;
$slider-hover-on-hover-bg: #d2d2d2 !default;
//// Header style
$header-bottom: $default-bottom;

View File

@ -2,6 +2,7 @@ nav {
margin-bottom: 1.2rem;
ul {
position: relative;
margin: 0;
padding: 0;
list-style-type: none;
@ -35,6 +36,14 @@ nav {
}
}
.date-time {
position: absolute;
right: 0;
margin-right: .7rem;
font-size: 14pt;
text-shadow: $nav-date-time-shadow;
}
.decorator {
width: 0;
height: 0;
@ -55,6 +64,10 @@ nav {
li.selected {
border-radius: 2rem;
}
.date-time {
@extend .hidden;
}
}
}
}

View File

@ -9,6 +9,16 @@
line-height: 3.8rem;
letter-spacing: .1rem;
%panel {
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;
}
.groups,
.scenes,
.units {
@ -29,6 +39,7 @@
.group,
.scene,
.unit,
.animations,
.group-controller {
padding: 1rem;
cursor: pointer;
@ -48,13 +59,7 @@
}
* > .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;
@extend %panel;
.slider-container {
@extend .vertical-center;
@ -90,6 +95,54 @@
.group-controller {
font-weight: 600;
}
.animations {
.row {
.caption {
font-style: italic;
}
.animation-container {
@extend %panel;
cursor: auto;
.animation {
.row {
padding: 1rem .3333rem;
&:hover {
background: $hover-bg;
border-radius: 1.5rem;
* > input[type=range] {
background: $slider-hover-on-hover-bg;
}
}
}
}
}
select[name=animation-type] {
width: 100%;
}
}
* > .input-range-container {
margin-top: 1rem;
margin-bottom: -1rem;
}
* > input[type="text"] {
width: 100%;
}
&:hover {
.row {
.animation-container {
background: $light-hue-properties-hover-bg;
}
}
}
}
}
.groups {

View File

@ -35,11 +35,17 @@ var app = new Vue({
return {
config: window.config,
selectedPlugin: undefined,
now: new Date(),
};
},
mounted: function() {},
created: function() {},
created: function() {
const self = this;
setInterval(() => {
self.now = new Date();
}, 1000)
},
updated: function() {},
destroyed: function() {},
});

View File

@ -0,0 +1,89 @@
Vue.component('range-slider', {
template: '#tmpl-range-slider',
props: ['min','max','value'],
mounted: function() {
var input = this.$el.querySelector('input[type=range]');
var supportsMultiple = self.HTMLInputElement && "valueLow" in HTMLInputElement.prototype;
var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
if (supportsMultiple || input.classList.contains("multirange")) {
return;
}
var values = [];
if (Array.isArray(this.value)) {
values = this.value;
} else if (this.value !== null) {
values = this.value.split(",");
}
var min = +(this.min || 0);
var max = +(this.max || 100);
var ghost = input.cloneNode();
input.classList.add("multirange", "original");
ghost.classList.add("multirange", "ghost");
input.value = values[0] !== undefined ? values[0] : min + (max - min) / 2;
ghost.value = values[1] !== undefined ? values[1] : min + (max - min) / 2;
input.parentNode.insertBefore(ghost, input.nextSibling);
Object.defineProperty(input, "originalValue", descriptor.get ? descriptor : {
// Fuck you Safari >:(
get: function() { return this.value; },
set: function(v) { this.value = v; }
});
Object.defineProperties(input, {
valueLow: {
get: function() { return Math.min(this.originalValue, ghost.value); },
set: function(v) { this.originalValue = v; },
enumerable: true
},
valueHigh: {
get: function() { return Math.max(this.originalValue, ghost.value); },
set: function(v) { ghost.value = v; },
enumerable: true
}
});
if (descriptor.get) {
// Again, fuck you Safari
Object.defineProperty(input, "value", {
get: function() { return this.valueLow + "," + this.valueHigh; },
set: function(v) {
var values = v.split(",");
this.valueLow = values[0];
this.valueHigh = values[1];
update();
},
enumerable: true
});
}
input.oninput = this.changed;
ghost.oninput = this.changed;
function update() {
ghost.style.setProperty("--low", 100 * ((input.valueLow - min) / (max - min)) + 1 + "%");
ghost.style.setProperty("--high", 100 * ((input.valueHigh - min) / (max - min)) - 1 + "%");
}
input.addEventListener("input", update);
ghost.addEventListener("input", update);
update();
},
methods: {
changed: function(event) {
const value = this.$el.querySelectorAll('input[type=range]')[0].value
.split(',').map(_ => parseFloat(_));
this.$emit('changed', value);
},
},
});

View File

@ -0,0 +1,39 @@
Vue.component('light-hue-animations-container', {
template: '#tmpl-light-hue-animations-container',
props: ['groupId','animation','collapsed'],
data: function() {
return {
selectedAnimation: 'color_transition',
};
},
methods: {
animationsCollapsedToggled: function() {
this.$emit('animations-collapsed-toggled', {
type: 'animation',
id: this.groupId,
});
},
toggled: async function(event) {
if (event.value) {
var args = {
...this.$refs[this.selectedAnimation].value,
animation: this.selectedAnimation,
groups: [this.groupId],
}
await request('light.hue.on', {groups: [this.groupId]});
await request('light.hue.animate', args);
this.$emit('animation-started', {
...this.$refs[this.selectedAnimation].value,
type: this.selectedAnimation,
});
} else {
await request('light.hue.stop_animation');
this.$emit('animation-stopped', {});
}
},
},
});

View File

@ -0,0 +1,28 @@
Vue.component('light-hue-animation-blink', {
template: '#tmpl-light-hue-animation-blink',
data: function() {
return {
value: {
transition_seconds: 1,
duration: undefined,
},
transitionSecondsRange: [0.1, 60],
durationRange: [0, 600],
};
},
methods: {
onTransitionSecondsChange: function(event) {
this.value.transition_seconds = event.target.value;
},
onDurationChanged: function(event) {
var value = event.target.value;
if (value == null || value.length === 0 || parseFloat(value) == 0) {
value = undefined;
}
this.value.duration = value;
},
},
});

View File

@ -0,0 +1,59 @@
Vue.component('light-hue-animation-color_transition', {
template: '#tmpl-light-hue-animation-color_transition',
data: function() {
return {
value: {
hue_range: [0,65535],
sat_range: [150,255],
bri_range: [190,255],
hue_step: 150,
sat_step: 5,
bri_step: 2,
transition_seconds: 1,
duration: undefined,
},
};
},
computed: {
hueStepRange: function() {
return [1, parseInt((this.value.hue_range[1]-this.value.hue_range[0])/2)-1];
},
satStepRange: function() {
return [1, parseInt((this.value.sat_range[1]-this.value.sat_range[0])/2)-1];
},
briStepRange: function() {
return [1, parseInt((this.value.bri_range[1]-this.value.bri_range[0])/2)-1];
},
transitionSecondsRange: function() {
return [0.1, 60];
},
durationRange: function() {
return [0, 600];
},
},
methods: {
hueRangeChanged: function(value) {
this.value.hue_range = value;
},
satRangeChanged: function(value) {
this.value.sat_range = value;
},
briRangeChanged: function(value) {
this.value.bri_range = value;
},
onTransitionSecondsChange: function(event) {
this.value.transition_seconds = event.target.value;
},
onDurationChanged: function(event) {
var value = event.target.value;
if (value == null || value.length === 0 || parseFloat(value) == 0) {
value = undefined;
}
this.value.duration = value;
},
},
});

View File

@ -6,6 +6,7 @@ Vue.component('light-hue', {
groups: {},
lights: {},
scenes: {},
animations: {},
selectedGroup: undefined,
selectedScene: undefined,
selectedProperties: {
@ -89,8 +90,10 @@ Vue.component('light-hue', {
const getLights = request('light.hue.get_lights');
const getGroups = request('light.hue.get_groups');
const getScenes = request('light.hue.get_scenes');
const getAnimations = request('light.hue.get_animations');
[this.lights, this.groups, this.scenes] = await Promise.all([getLights, getGroups, getScenes]);
[this.lights, this.groups, this.scenes, this.animations] = await Promise.all(
[getLights, getGroups, getScenes, getAnimations]);
this._prepareGroups();
this._prepareScenes();
@ -112,6 +115,14 @@ Vue.component('light-hue', {
}
},
startedAnimation: function(value) {
this.animations.groups[this.selectedGroup] = value;
},
stoppedAnimation: function() {
this.animations.groups[this.selectedGroup] = undefined;
},
selectScene: async function(event) {
await request(
'light.hue.scene', {

View File

@ -1,2 +1,3 @@
{% include 'elements/switch.html' %}
{% include 'elements/range-slider.html' %}

View File

@ -0,0 +1,8 @@
<script type="text/x-template" id="tmpl-range-slider">
<div class="input-range-container">
<input type="range" class="slider" multiple="multiple" :value="value" :min="min" :max="max" @input="changed">
</div>
</script>
<script type="application/javascript" src="{{ url_for('static', filename='js/elements/range-slider.js') }}"></script>

View File

@ -5,3 +5,5 @@
</div>
</script>
<script type="application/javascript" src="{{ url_for('static', filename='js/elements/switch.js') }}"></script>

View File

@ -1,15 +1,15 @@
<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="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 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>

View File

@ -42,7 +42,7 @@
<body>
<div id="app">
{% include 'header.html' %}
{# include 'header.html' #}
{% with plugins=templates.keys() %}
{% include 'nav.html' %}
@ -67,7 +67,6 @@
<script type="text/javascript" src="{{ url_for('static', filename=script['_script_file']) }}"></script>
{% endfor %}
<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>

View File

@ -1,5 +1,5 @@
<nav>
<ul class="row">
<ul>
{% for plugin in plugins|sort %}
<li :class="{selected: '{{ plugin }}' == selectedPlugin}">
<a href="#{{ plugin }}" @click="selectedPlugin = '{{ plugin }}'">
@ -15,6 +15,11 @@
<li>
<a href="#{{ plugin }}">Test tab 3</a>
</li>
<div class="date-time pull-right">
<!--<div class="date" v-text="now.toDateString().substring(0,10)"></div>-->
<div class="time" v-text="now.toTimeString().substring(0,8)"></div>
</div>
</ul>
</nav>

View File

@ -0,0 +1,41 @@
{% with templates = utils.find_templates_in_dir('plugins/light.hue/animations') %}
{% for template in templates %}
{% include template %}
{% endfor %}
{% endwith %}
<script type="text/x-template" id="tmpl-light-hue-animations-container">
<div class="animations">
<div class="row vertical-center">
<div class="col-10 caption" @click="animationsCollapsedToggled">Animate</div>
<div class="col-2 pull-right">
<toggle-switch :glow="true" :value="animation != null" @toggled="toggled"></toggle-switch>
</div>
</div>
<div class="row" :class="{hidden: collapsed}">
<div class="row">
{% with templates = utils.find_templates_in_dir('plugins/light.hue/animations') %}
<select name="animation-type" v-model="selectedAnimation"
v-if="{{ templates|length }} > 0">
{% for template_file in templates %}
{% with name = template_file.split('/')[-1].split('.')[0] %}
<option value="{{ name }}">{{ name }}</option>
{% endwith %}
{% endfor %}
</select>
</div>
{% for template_file in templates %}
{% with name = template_file.split('/')[-1].split('.')[0] %}
<div class="animation-container" :class="{hidden: selectedAnimation != '{{name}}'}">
<component :is="'light-hue-animation-{{ name }}'" ref="{{ name }}"></component>
</div>
{% endwith %}
{% endfor %}
{% endwith %}
</div>
</div>
</script>
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/light.hue/animations.js') }}"></script>

View File

@ -0,0 +1,28 @@
<script type="text/x-template" id="tmpl-light-hue-animation-blink">
<div class="animation">
<div class="row">
<div class="col-4">Transition</div>
<div class="col-2">
<input type="text" v-model="value.transition_seconds">
</div>
<div class="col-6 slider-container">
<input class="slider" type="range" :min="transitionSecondsRange[0]" :max="transitionSecondsRange[1]"
v-model="value.transition_seconds" @input="onTransitionSecondsChange" placeholder="secs">
</div>
</div>
<div class="row">
<div class="col-4">Duration</div>
<div class="col-2">
<input type="text" v-model="value.duration" @input="onDurationChanged">
</div>
<div class="col-6 slider-container">
<input class="slider" type="range" step="1" :min="durationRange[0]" :max="durationRange[1]" placeholder="secs"
:class="{disabled: value.duration == undefined}" v-model="value.duration" @input="onDurationChanged">
</div>
</div>
</div>
</script>
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/light.hue/animations/blink.js') }}"></script>

View File

@ -0,0 +1,73 @@
<script type="text/x-template" id="tmpl-light-hue-animation-color_transition">
<div class="animation">
<div class="row">
<div class="col-4">Hue range</div>
<div class="col-8">
<range-slider min="0" max="65535" v-model="value.hue_range" @changed="hueRangeChanged"></range-slider>
</div>
</div>
<div class="row">
<div class="col-4">Sat range</div>
<div class="col-8">
<range-slider min="0" max="255" v-model="value.sat_range" @changed="satRangeChanged"></range-slider>
</div>
</div>
<div class="row">
<div class="col-4">Bri range</div>
<div class="col-8">
<range-slider min="0" max="255" v-model="value.bri_range" @changed="briRangeChanged"></range-slider>
</div>
</div>
<div class="row">
<div class="col-4">Hue step</div>
<div class="col-8 slider-container">
<input class="slider" type="range" :min="hueStepRange[0]" :max="hueStepRange[1]" step="1"
v-model="value.hue_step">
</div>
</div>
<div class="row">
<div class="col-4">Sat step</div>
<div class="col-8 slider-container">
<input class="slider" type="range" :min="satStepRange[0]" :max="satStepRange[1]" step="1"
v-model="value.sat_step">
</div>
</div>
<div class="row">
<div class="col-4">Bri step</div>
<div class="col-8 slider-container">
<input class="slider" type="range" step="1" :min="briStepRange[0]" :max="briStepRange[1]"
v-model="value.bri_step">
</div>
</div>
<div class="row">
<div class="col-4">Transition</div>
<div class="col-2">
<input type="text" v-model="value.transition_seconds">
</div>
<div class="col-6 slider-container">
<input class="slider" type="range" :min="transitionSecondsRange[0]" :max="transitionSecondsRange[1]"
v-model="value.transition_seconds" @input="onTransitionSecondsChange" placeholder="secs">
</div>
</div>
<div class="row">
<div class="col-4">Duration</div>
<div class="col-2">
<input type="text" v-model="value.duration" @input="onDurationChanged">
</div>
<div class="col-6 slider-container">
<input class="slider" type="range" step="1" :min="durationRange[0]" :max="durationRange[1]" placeholder="secs"
:class="{disabled: value.duration == undefined}" v-model="value.duration" @input="onDurationChanged">
</div>
</div>
</div>
</script>
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/light.hue/animations/color_transition.js') }}"></script>

View File

@ -4,6 +4,7 @@
{% include 'plugins/light.hue/groups.html' %}
{% include 'plugins/light.hue/scenes.html' %}
{% include 'plugins/light.hue/units.html' %}
{% include 'plugins/light.hue/animations.html' %}
<script type="text/x-template" id="tmpl-light-hue">
<div class="row light-hue-container">
@ -33,6 +34,7 @@
<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]"
@ -43,6 +45,16 @@
@input="updatedGroup">
</light-hue-group-controller>
<light-hue-animations-container
v-if="selectedGroup"
:group-id="selectedGroup"
:animation="animations.groups[selectedGroup]"
:collapsed="!(selectedProperties.type == 'animation' && selectedProperties.id == selectedGroup)"
@animations-collapsed-toggled="collapsedToggled"
@animation-started="startedAnimation"
@animation-stopped="stoppedAnimation">
</light-hue-animations-container>
<light-hue-unit
v-for="(light, id) in lights"
v-model="light.state"

View File

@ -3,6 +3,8 @@ import os
import re
from platypush.config import Config
from platypush.backend.http.app import template_folder
class HttpUtils(object):
@staticmethod
@ -100,5 +102,19 @@ class HttpUtils(object):
def plugin_name_to_tag(cls, module_name):
return module_name.replace('.','-')
@classmethod
def find_templates_in_dir(cls, directory):
return [
os.path.join(directory, file)
for root, path, files in os.walk(os.path.abspath(os.path.join(template_folder, directory)))
for file in files
if file.endswith('.html') or file.endswith('.htm')
]
@classmethod
def readfile(cls, file):
with open(file) as f:
return f.read()
# vim:sw=4:ts=4:et:

View File

@ -70,12 +70,26 @@ class LightHuePlugin(LightPlugin):
self.redis = None
self.animation_thread = None
self.animations = {}
self._init_animations()
self.logger.info('Configured lights: "{}"'. format(self.lights))
def _expand_groups(self):
groups = [g for g in self.bridge.groups if g.name in self.groups]
for g in groups:
self.lights.extend([l.name for l in g.lights])
for l in g.lights:
self.lights += [l.name]
def _init_animations(self):
self.animations = {
'groups': {},
'lights': {},
}
for g in self.bridge.groups:
self.animations['groups'][g.group_id] = None
for l in self.bridge.lights:
self.animations['lights'][l.light_id] = None
@action
def connect(self):
@ -239,6 +253,36 @@ class LightHuePlugin(LightPlugin):
return self.bridge.get_group()
@action
def get_animations(self):
"""
Get the list of running light animations.
:returns: A dictionary with the following structure:
{
"groups": {
"id_1": {
"type": "color_transition",
"hue_range": [0,65535],
"sat_range": [0,255],
"bri_range": [0,255],
"hue_step": 10,
"sat_step": 10,
"bri_step": 2,
"transition_seconds": 2
},
...
},
"lights": {
"id_1": { ... },
...
}
}
"""
return self.animations
def _exec(self, attr, *args, **kwargs):
try:
self.connect()
@ -654,6 +698,7 @@ class LightHuePlugin(LightPlugin):
if self.animation_thread and self.animation_thread.is_alive():
redis = self._get_redis()
redis.rpush(self.ANIMATION_CTRL_QUEUE_NAME, 'STOP')
self._init_animations()
@action
def animate(self, animation, duration=None,
@ -669,27 +714,27 @@ class LightHuePlugin(LightPlugin):
:param duration: Animation duration in seconds (default: None, i.e. continue until stop)
:type duration: float
:param hue_range: If you selected a color transition, this will specify the hue range of your color transition.
:param hue_range: If you selected a color color_transition.html, this will specify the hue range of your color color_transition.html.
Default: [0, 65535]
:type hue_range: list[int]
:param sat_range: If you selected a color transition, this will specify the saturation range of your color
transition. Default: [0, 255]
:param sat_range: If you selected a color color_transition.html, this will specify the saturation range of your color
color_transition.html. Default: [0, 255]
:type sat_range: list[int]
:param bri_range: If you selected a color transition, this will specify the brightness range of your color
transition. Default: [254, 255] :type bri_range: list[int]
:param bri_range: If you selected a color color_transition.html, this will specify the brightness range of your color
color_transition.html. Default: [254, 255] :type bri_range: list[int]
:param lights: Lights to control (names or light objects). Default: plugin default lights
:param groups: Groups to control (names or group objects). Default: plugin default groups
:param lights: Lights to control (names, IDs or light objects). Default: plugin default lights
:param groups: Groups to control (names, IDs or group objects). Default: plugin default groups
:param hue_step: If you selected a color transition, this will specify by how much the color hue will change
:param hue_step: If you selected a color color_transition.html, this will specify by how much the color hue will change
between iterations. Default: 1000 :type hue_step: int
:param sat_step: If you selected a color transition, this will specify by how much the saturation will change
:param sat_step: If you selected a color color_transition.html, this will specify by how much the saturation will change
between iterations. Default: 2 :type sat_step: int
:param bri_step: If you selected a color transition, this will specify by how much the brightness will change
:param bri_step: If you selected a color color_transition.html, this will specify by how much the brightness will change
between iterations. Default: 1 :type bri_step: int
:param transition_seconds: Time between two transitions or blinks in seconds. Default: 1.0
@ -703,13 +748,34 @@ class LightHuePlugin(LightPlugin):
if hue_range is None:
hue_range = [0, self.MAX_HUE]
if groups:
groups = [g for g in self.bridge.groups if g.name in groups]
groups = [g for g in self.bridge.groups if g.name in groups or g.group_id in groups]
lights = lights or []
for g in groups:
lights.extend([l.name for l in g.lights])
elif not lights:
elif lights:
lights = [l.name for l in self.bridge.lights if l.name in lights or l.light_id in lights]
else:
lights = self.lights
info = {
'type': animation,
'duration': duration,
'hue_range': hue_range,
'sat_range': sat_range,
'bri_range': bri_range,
'hue_step': hue_step,
'sat_step': sat_step,
'bri_step': bri_step,
'transition_seconds': transition_seconds,
}
for g in groups:
self.animations['groups'][g.group_id] = info
for l in self.bridge.lights:
if l.name in lights:
self.animations['lights'][l.light_id] = info
def _initialize_light_attrs(lights):
if animation == self.Animation.COLOR_TRANSITION:
return { l: {

View File

@ -2,6 +2,7 @@
import errno
import os
import re
import distutils.cmd
from distutils.command.build import build
from setuptools import setup, find_packages
@ -15,8 +16,8 @@ class WebBuildCommand(distutils.cmd.Command):
description = 'Build components and styles for the web pages'
user_options = []
@staticmethod
def generate_css_files():
@classmethod
def generate_css_files(cls):
from scss import Compiler
print('Building CSS files')
@ -36,8 +37,13 @@ class WebBuildCommand(distutils.cmd.Command):
with open(css_file, 'w') as f:
css_content = Compiler(output_style='compressed', search_path=[root, input_path]).compile(scss_file)
css_content = cls._fix_css4_vars(css_content)
f.write(css_content)
@staticmethod
def _fix_css4_vars(css):
return re.sub(r'var\("--([^"]+)"\)', r'var(--\1)', css)
def initialize_options(self):
pass