diff --git a/platypush/backend/http/static/css/light.hue.css b/platypush/backend/http/static/css/light.hue.css deleted file mode 100644 index 97ab1838..00000000 --- a/platypush/backend/http/static/css/light.hue.css +++ /dev/null @@ -1,127 +0,0 @@ -#hue-container { - background-color: #f8f8f8; - padding: 12px; - border: 1px solid #ddd; - border-radius: 10px; - font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-weight: 400; - line-height: 38px; - letter-spacing: .1rem; -} - - #hue-container > .columns { - margin-left: 0; - } - - #hue-container > .three.columns { - width: 24%; - } - - #hue-container > .six.columns { - width: 52%; - } - -#rooms-list { - border-right: 1px solid #e4e4e4; -} - -#lights-list { - margin-left: 0; - border: 1px solid #e4e4e4; - border-radius: 8px; -} - -.hue-section-title { - padding: 7.5px; - background: #ececec; - border: 1px solid #e4e4e4; -} - -.room-item { - color: #333; - padding: 10px 5px; - border-bottom: 1px solid #ddd; - text-transform: uppercase; - cursor: pointer; -} - -.scene-item { - color: #333; - padding: 5px 10px; - border-bottom: 1px solid #e4e4e4; - cursor: pointer; -} - - .room-item:hover, .light-item:hover, .scene-item:hover { - background-color: #daf8e2 !important; - } - - .room-item.selected, .light-item.selected, .scene-item.selected { - background-color: #c8ffd0 !important; - } - -.room-lights-item, .room-scenes-item { - display: none; -} - -.light-item { - color: #333; - padding: 20px; - cursor: pointer; -} - - .light-item:not(:last-child) { - border-bottom: 1px solid #ddd; - } - -.light-item-name { - display: inline-block; -} - -.all-lights-item, .animation-item { - background-color: #ececec; -} - - .all-lights-item * > .light-item-name { - font-weight: bold; - } - - .animation-item * > .light-item-name { - font-style: italic; - } - -.slider-container { - padding: 1rem .5rem; -} - - .slider-container > .columns { - line-height: initial; - } - - .animation-type-container * > label { - line-height: initial; - font-weight: 100; - } - -.light-ctrl-switch-container { - float: right; - margin-top: -5px; -} - -.light-color-selector, -.animation-selector { - display: none; - background-color: white; - padding: 10px; - margin: 10px -10px -10px -10px; - border-radius: 10px; -} - -.animation-container { - display: none; -} - -.light-slider { - margin-top: 10px; -} - diff --git a/platypush/backend/http/static/js/application.js b/platypush/backend/http/static/js/application.js index da478e43..012da3d7 100644 --- a/platypush/backend/http/static/js/application.js +++ b/platypush/backend/http/static/js/application.js @@ -45,6 +45,8 @@ var app = new Vue({ setInterval(() => { self.now = new Date(); }, 1000) + + initEvents(); }, updated: function() {}, destroyed: function() {}, diff --git a/platypush/backend/http/static/js/events.js b/platypush/backend/http/static/js/events.js new file mode 100644 index 00000000..b918a664 --- /dev/null +++ b/platypush/backend/http/static/js/events.js @@ -0,0 +1,119 @@ +var websocket = { + ws: undefined, + instance: undefined, + pending: false, + opened: false, + timeout: undefined, + reconnectMsecs: 30000, + handlers: {}, +}; + +function initEvents() { + try { + url_prefix = window.config.has_ssl ? 'wss://' : 'ws://'; + websocket.ws = new WebSocket(url_prefix + window.location.hostname + ':' + window.config.websocket_port); + } catch (err) { + console.error("Websocket initialization error"); + console.log(err); + return; + } + + websocket.pending = true; + + var onWebsocketTimeout = function(self) { + return function() { + console.log('Websocket reconnection timed out, retrying'); + websocket.pending = false; + self.close(); + self.onclose(); + }; + }; + + websocket.timeout = setTimeout( + onWebsocketTimeout(websocket.ws), websocket.reconnectMsecs); + + websocket.ws.onmessage = function(event) { + console.debug(event); + handlers = []; + event = event.data; + + if (typeof event === 'string') { + event = JSON.parse(event); + } + + if (event.type !== 'event') { + // Discard non-event messages + return; + } + + if (null in websocket.handlers) { + handlers.push(websocket.handlers[null]); + } + + if (event.args.type in websocket.handlers) { + handlers.push(...websocket.handlers[event.args.type]); + } + + for (var handler of handlers) { + handler(event.args); + } + }; + + websocket.ws.onopen = function(event) { + if (websocket.instance) { + console.log("There's already an opened websocket connection, closing the newly opened one"); + this.onclose = function() {}; + this.close(); + } + + console.log('Websocket connection successful'); + websocket.instance = this; + + if (websocket.pending) { + websocket.pending = false; + } + + if (websocket.timeout) { + clearTimeout(websocket.timeout); + websocket.timeout = undefined; + } + }; + + websocket.ws.onerror = function(event) { + console.error(event); + }; + + websocket.ws.onclose = function(event) { + if (event) { + console.log('Websocket closed - code: ' + event.code + ' - reason: ' + event.reason); + } + + websocket.instance = undefined; + + if (!websocket.pending) { + websocket.pending = true; + initEvents(); + } + }; +}; + +function registerEventHandler(handler, ...events) { + if (events.length) { + // Event type filter specified + for (var event of events) { + if (!(event in websocket.handlers)) { + websocket.handlers[event] = []; + } + + websocket.handlers[event].push(handler); + } + } else { + // No event type specified, listen to all events + if (!(null in websocket.handlers)) { + websocket.handlers[null] = []; + } + + websocket.handlers[null].push(handler); + } +} + diff --git a/platypush/backend/http/static/js/light.hue.js b/platypush/backend/http/static/js/light.hue.js deleted file mode 100644 index 6a4fb711..00000000 --- a/platypush/backend/http/static/js/light.hue.js +++ /dev/null @@ -1,712 +0,0 @@ -$(document).ready(function() { - var lights, - groups, - scenes, - $roomsList = $('#rooms-list'), - $lightsList = $('#lights-list'), - $scenesList = $('#scenes-list'); - - var onEvent = function(event) { - switch (event.args.type) { - case 'platypush.message.event.light.LightStatusChangeEvent': - var $lightsList = $('#lights-list'); - var $light = $lightsList.find('.light-item').filter( - (i, light) => $(light).data('id') == event.args.light_id - ); - - var $roomLights = $light.parent('.room-lights-item').find('.light-item').filter( - (i, light) => $(light).data('type') == 'light' - ); - - var $allLightsItem = $light.parent('.room-lights-item').find('.light-item').filter( - (i, light) => $(light).data('type') == 'room' - ); - - if ('on' in event.args) { - $light.find('.light-ctrl-switch').prop('checked', event.args.on); - - if ($roomLights.find('.light-ctrl-switch:checked').length > 0) { - $allLightsItem.find('.light-ctrl-switch').prop('checked', true); - } else { - $allLightsItem.find('.light-ctrl-switch').prop('checked', false); - } - } - - if ('bri' in event.args) { - $light.find('.slider.bri').val(event.args.bri); - } - - if ('sat' in event.args) { - $light.find('.slider.sat').val(event.args.sat); - } - - if ('hue' in event.args) { - $light.find('.slider.hue').val(event.args.hue); - } - - break; - } - }; - - var createPowerToggleElement = function(data) { - var id = data['type'] + '_' + data['id']; - var $powerToggle = $('
').addClass('toggle toggle--push light-ctrl-switch-container'); - var $input = $('').attr('type', 'checkbox') - .attr('id', id).addClass('toggle--checkbox'); - - if (type === 'animation') { - $input.addClass('animation-switch'); - } else { - $input.addClass('light-ctrl-switch'); - } - - data = data || {}; - for (var attr of Object.keys(data)) { - $input.data(attr, data[attr]); - } - - var $label = $('').attr('for', id).addClass('toggle--btn'); - - $input.appendTo($powerToggle); - $label.appendTo($powerToggle); - - if ('on' in data && data['on']) { - $input.prop('checked', true); - } - - return $powerToggle; - }; - - var createColorSelector = function(data) { - var type = data.type; - var element; - - if (type === 'light') { - element = lights[data.id]; - } else if (type === 'room' || type === 'animation') { - element = groups[data.id]; - } else { - throw "Unknown type: " + type; - } - - var $colorSelector = $('') - .addClass('light-color-selector'); - - if (type === 'animation') { - // Animation type selector - var $typeContainer = $('') - .addClass('animation-type-container').addClass('row'); - - var $typeText = $('').addClass('two columns').text('Type'); - - // Color transition type - var $transitionTypeContainer = $('').addClass('five columns'); - - var $transitionType = $('').addClass('animation-type') - .addClass('one column').data('type', 'color_transition') - .attr('id', 'hue-animation-color-transition').data('name', element.name) - .attr('type', 'radio').attr('name', 'animation-type'); - - var $transitionTypeLabel = $('').attr('for', 'hue-animation-color-transition') - .addClass('four columns').text('Color transition'); - - $typeText.appendTo($typeContainer); - - $transitionType.appendTo($transitionTypeContainer); - $transitionTypeLabel.appendTo($transitionTypeContainer); - $transitionTypeContainer.appendTo($typeContainer); - - // Blink type - var $blinkTypeContainer = $('').addClass('five columns'); - - var $blinkType = $('').addClass('animation-type') - .addClass('one column').data('type', 'blink') - .attr('id', 'hue-animation-blink').data('name', element.name) - .attr('type', 'radio').attr('name', 'animation-type'); - - var $blinkTypeLabel = $('').attr('for', 'hue-animation-blink') - .addClass('four columns').text('Blink'); - - $blinkType.appendTo($blinkTypeContainer); - $blinkTypeLabel.appendTo($blinkTypeContainer); - $blinkTypeContainer.appendTo($typeContainer); - - $typeContainer.appendTo($colorSelector); - - // Color transition container - var $animationContainer = $('') - .addClass('animation-container row').data('animation-type', 'color_transition'); - - // Hue slider - var $hueContainer = $('') - .addClass('slider-container').addClass('row'); - - var defaultHueRange = [0, 65535]; - var $hueText = $('').addClass('two columns').text('Hue range'); - var $hueSlider = $('') - .addClass('ten columns').data('animation-property', 'hue_range') - .data('id', data.id).val(defaultHueRange).slider({ - range: true, - min: 0, - max: 65535, - values: defaultHueRange, - slide: function(event, ui) { - var values = $(event.target).slider("option", "values"); - $(this).val(values); - } - }); - - $hueText.appendTo($hueContainer); - $hueSlider.appendTo($hueContainer); - $hueContainer.appendTo($animationContainer); - - // Sat slider - var $satContainer = $('') - .addClass('slider-container').addClass('row'); - - var defaultSatRange = [155, 255]; - var $satText = $('').addClass('two columns').text('Sat range'); - var $satSlider = $('') - .addClass('ten columns').data('animation-property', 'sat_range') - .data('id', data.id).val(defaultSatRange).slider({ - range: true, - min: 0, - max: 255, - values: defaultSatRange, - slide: function(event, ui) { - var values = $(event.target).slider("option", "values"); - $(this).val(values); - } - }); - - $satText.appendTo($satContainer); - $satSlider.appendTo($satContainer); - $satContainer.appendTo($animationContainer); - - // Bri slider - var $briContainer = $('') - .addClass('slider-container').addClass('row'); - - var defaultBriRange = [240, 255]; - var $briText = $('').addClass('two columns').text('Bri range'); - var $briSlider = $('') - .addClass('ten columns').data('animation-property', 'bri_range') - .data('id', data.id).val(defaultBriRange).slider({ - range: true, - min: 0, - max: 255, - values: defaultBriRange, - slide: function(event, ui) { - var values = $(event.target).slider("option", "values"); - $(this).val(values); - } - }); - - $briText.appendTo($briContainer); - $briSlider.appendTo($briContainer); - $briContainer.appendTo($animationContainer); - - // Hue step - var $hueStepContainer = $('') - .addClass('slider-container').addClass('row'); - - var $hueStepText = $('').addClass('two columns').text('Hue step'); - var $hueStepSlider = $('').addClass('slider light-slider') - .addClass('ten columns').addClass('hue').data('animation-property', 'hue_step') - .attr('type', 'range').attr('min', 0).attr('max', 65535) - .data('id', data.id).data('name', element.name).val(1000); - - $hueStepText.appendTo($hueStepContainer); - $hueStepSlider.appendTo($hueStepContainer); - $hueStepContainer.appendTo($animationContainer); - - // Sat step - var $satStepContainer = $('') - .addClass('slider-container').addClass('row'); - - var $satStepText = $('').addClass('two columns').text('Sat step'); - var $satStepSlider = $('').addClass('slider light-slider') - .addClass('ten columns').addClass('sat').data('animation-property', 'sat_step') - .attr('type', 'range').attr('min', 0).attr('max', 255) - .data('id', data.id).data('name', element.name).val(2); - - $satStepText.appendTo($satStepContainer); - $satStepSlider.appendTo($satStepContainer); - $satStepContainer.appendTo($animationContainer); - - // Bri step - var $briStepContainer = $('') - .addClass('slider-container').addClass('row'); - - var $briStepText = $('').addClass('two columns').text('Bri step'); - var $briStepSlider = $('').addClass('slider light-slider') - .addClass('ten columns').addClass('bri').data('animation-property', 'bri_step') - .attr('type', 'range').attr('min', 0).attr('max', 255) - .data('id', data.id).data('name', element.name).val(1); - - $briStepText.appendTo($briStepContainer); - $briStepSlider.appendTo($briStepContainer); - $briStepContainer.appendTo($animationContainer); - - // Transition seconds - var $transitionContainer = $('') - .addClass('slider-container').addClass('row'); - - var $transitionText = $('').addClass('two columns').text('Transition seconds'); - var $transitionInput = $('').data('animation-property', 'transition_seconds') - .addClass('two columns pull-right').attr('type', 'text') - .data('id', data.id).data('name', element.name).val(1); - - $transitionText.appendTo($transitionContainer); - $transitionInput.appendTo($transitionContainer); - $transitionContainer.appendTo($animationContainer); - - // Duration seconds - var $durationContainer = $('') - .addClass('slider-container').addClass('row'); - - var $durationText = $('').addClass('two columns').text('Duration seconds'); - var $durationInput = $('').data('animation-property', 'duration') - .addClass('two columns pull-right').attr('type', 'text') - .data('id', data.id).data('name', element.name); - - $durationText.appendTo($durationContainer); - $durationInput.appendTo($durationContainer); - $durationContainer.appendTo($animationContainer); - - $animationContainer.appendTo($colorSelector); - - // Blink animation container - $animationContainer = $('') - .addClass('animation-container row').data('animation-type', 'blink'); - - // Transition seconds - $transitionContainer = $('') - .addClass('slider-container').addClass('row'); - - $transitionText = $('').addClass('two columns').text('Transition seconds'); - $transitionInput = $('').data('animation-property', 'transition_seconds') - .addClass('two columns pull-right').attr('type', 'text') - .data('id', data.id).data('name', element.name).val(1); - - $transitionText.appendTo($transitionContainer); - $transitionInput.appendTo($transitionContainer); - $transitionContainer.appendTo($animationContainer); - - // Duration seconds - $durationContainer = $('').data('animation-property', 'duration') - .addClass('slider-container').addClass('row'); - - $durationText = $('').addClass('two columns').text('Duration seconds'); - $durationInput = $('').data('animation-property', 'duration') - .addClass('two columns pull-right').attr('type', 'text') - .data('id', data.id).data('name', element.name); - - $durationText.appendTo($durationContainer); - $durationInput.appendTo($durationContainer); - $durationContainer.appendTo($animationContainer); - - $animationContainer.appendTo($colorSelector); - } else { - // Hue slider - var $hueContainer = $('') - .addClass('slider-container').addClass('row'); - - var $hueText = $('').addClass('two columns').text('Hue'); - var $hueSlider = $('').addClass('slider light-slider') - .addClass('ten columns').addClass('hue').data('type', type) - .attr('type', 'range').attr('min', 0).attr('max', 65535).data('property', 'hue') - .data('id', data.id).data('name', element.name).val(type === 'light' ? element.state.hue : 0); - - $hueText.appendTo($hueContainer); - $hueSlider.appendTo($hueContainer); - $hueContainer.appendTo($colorSelector); - - // Saturation slider - var $satContainer = $('') - .addClass('slider-container').addClass('row'); - - var $satText = $('').addClass('two columns').text('Saturation'); - var $satSlider = $('').addClass('slider light-slider') - .addClass('ten columns').addClass('sat').data('type', type) - .attr('type', 'range').attr('min', 0).attr('max', 255).data('property', 'sat') - .data('id', data.id).data('name', element.name).val(type === 'light' ? element.state.sat : 0); - - $satText.appendTo($satContainer); - $satSlider.appendTo($satContainer); - $satContainer.appendTo($colorSelector); - - // Brightness slider - var $briContainer = $('') - .addClass('slider-container').addClass('row'); - - var $briText = $('').addClass('two columns').text('Brightness'); - var $briSlider = $('').addClass('slider light-slider') - .addClass('ten columns').addClass('bri').data('type', type) - .attr('type', 'range').attr('min', 0).attr('max', 255).data('property', 'bri') - .data('id', data.id).data('name', element.name).val(type === 'light' ? element.state.bri : 0); - - $briText.appendTo($briContainer); - $briSlider.appendTo($briContainer); - $briContainer.appendTo($colorSelector); - } - - return $colorSelector; - }; - - var createLightCtrlElement = function(type, id) { - var element; - - if (type === 'light') { - element = lights[id]; - } else if (type === 'room' || type === 'animation') { - element = groups[id]; - } else { - throw "Unknown type: " + type; - } - - var on; - if (type === 'light') { - on = element.state.on; - } else if (type === 'room') { - on = element.state.any_on; - } else { - on = false; - } - - var $light = $('') - .addClass('light-item') - .data('type', type) - .data('id', id) - .data('name', element.name) - .data('on', on); - - if (type === 'room') { - $light.addClass('all-lights-item'); - } else if (type === 'animation') { - $light.addClass('animation-item'); - } - - var $row1 = $('').addClass('row'); - var $row2 = $('').addClass('row'); - - var lightName; - switch(type) { - case 'light': lightName = element.name; break; - case 'room': lightName = 'All Lights'; break; - case 'animation': lightName = 'Animate'; break; - } - - var $lightName = $('') - .addClass('light-item-name') - .text(lightName); - - var $powerToggle = createPowerToggleElement({ - type: type, - id: id, - on: on, - }); - - var $colorSelector = createColorSelector({ - type: type, - id: id, - }); - - $lightName.appendTo($row1); - $powerToggle.appendTo($row1); - $colorSelector.appendTo($row2); - - $row1.appendTo($light); - $row2.appendTo($light); - - return $light; - }; - - var updateRooms = function(rooms) { - var roomByLight = {}; - var roomsByScene = {}; - - $roomsList.html(''); - $lightsList.html(''); - $scenesList.html(''); - - for (var room of Object.keys(rooms)) { - var $room = $('') - .addClass('room-item') - .data('id', room) - .data('name', rooms[room].name) - .text(rooms[room].name); - - var $roomLights = $('') - .addClass('room-lights-item') - .data('id', room); - - var $roomScenes = $('') - .addClass('room-scenes-item') - .data('room-id', room) - .data('room-name', rooms[room].name); - - $room.appendTo($roomsList); - $roomLights.appendTo($lightsList); - $roomScenes.appendTo($scenesList); - - for (var light of rooms[room].lights) { - var $light = createLightCtrlElement(type='light', id=light); - $light.appendTo($roomLights); - roomByLight[light] = room; - } - - roomByLight[light] = room; - - var $animation = createLightCtrlElement(type='animation', id=room); - $animation.prependTo($roomLights); - - var $allLights = createLightCtrlElement(type='room', id=room); - $allLights.prependTo($roomLights); - } - - for (var scene of Object.keys(scenes)) { - if (scenes[scene].name.match(/(on|off) \d+$/)) { - // Old 1.0 scenes are saved as "scene_name on