<commit_before>/*
 * Say Cheese!
 * Lee Machin, 2012
 * http://leemach.in, http://new-bamboo.co.uk
 *
 * Minimal javascript library for integrating a webcam and snapshots into your app.
 *
 * Handles starting up the webcam and rendering the element, and also capturing shots
 * in a separate canvas element.
 *
 * Depends on video and canvas, and of course, getUserMedia. It's unlikely to work
 * on anything but the newest browsers.
 */

var SayCheese = (function($) {

  var SayCheese;

  /* Check for the existence of the userMedia feature. */
  navigator.getUserMedia = (navigator.getUserMedia ||
                            navigator.webkitGetUserMedia ||
                            navigator.mozGetUserMedia ||
                            navigator.msGetUserMedia ||
                            false);

  function eventCoords(evt) {
    return { x: evt.offsetX || evt.layerX, y: evt.offsetY || evt.layerY };
  };

  SayCheese = function SayCheese(element) {
    if (navigator.getUserMedia !== false) {
      this.viewfinder = {},
      this.snapshots = [],
      this.canvas = null,
      this.context = null,
      this.video = null,
      this.events = {},

      this.element = document.querySelectorAll(element)[0];
      this.element.style.position = 'relative';

    } else {
      // should make this more graceful in future
      throw new Error("getUserMedia() is not supported in this browser");
    }

    return this;
  };

  /**
   * Wrap the jQuery stuff so as to minimise the impact of the framework on
   * the rest of the code.
   */
  SayCheese.prototype.on = function on(evt, handler) {
    return $(this).on(evt, handler);
  };

  SayCheese.prototype.off = function off(evt, handler) {
    return $(this).off(evt, handler);
  };

  SayCheese.prototype.trigger = function trigger(evt, data) {
    // bubbling up the DOM makes things go a bit crazy. This assumes
    // preventDefault
    return $(this).triggerHandler(evt, data);
  };

  SayCheese.prototype.getStreamUrl = function getStreamUrl(stream) {
    url = (function() {
      return (window.URL || window.webkitURL);
    })();

    return (url && url.createObjectURL) ? url.createObjectURL(stream) : stream;
  };

  SayCheese.prototype.createVideo = function createVideo() {
    this.video = document.createElement('video');
    this.video.autoplay = true;
  };

  SayCheese.prototype.setupCanvas = function setupCanvas() {
    this.canvas = document.createElement('canvas');
    this.canvas.width = this.video.offsetWidth;
    this.canvas.height = this.video.offsetHeight;

    this.canvas.style.position = 'absolute';
    this.canvas.style.top = this.video.offsetTop;
    this.canvas.style.left = this.video.offsetLeft;

    this.context = this.canvas.getContext('2d');

    this.element.appendChild(this.canvas);

    this.initViewfinder();

    return this.trigger('start');
  };

  /* The default viewfinder is just the exact size of the video */
  SayCheese.prototype.initViewfinder = function initViewfinder() {
    return this.viewfinder = {
      width: this.video.offsetWidth,
      height: this.video.offsetHeight
    };
  };

  SayCheese.prototype.takeSnapshot = function takeSnapshot() {
    var snapshot = document.createElement('canvas'),
        ctx      = snapshot.getContext('2d');

    snapshot.width  = this.viewfinder.width;
    snapshot.height = this.viewfinder.height;

    ctx.drawImage(this.video, 0, 0);

    this.snapshots.push(snapshot);
    this.trigger('snapshot', snapshot);

    ctx = null;
  };

  /* Start up the stream, if possible */
  SayCheese.prototype.start = function start() {
    var success = function success(stream) {
      this.createVideo();

      // video width and height don't exist until metadata is loaded
      this.video.addEventListener('loadedmetadata', this.setupCanvas.bind(this));
      this.video.src = this.getStreamUrl(stream);
      this.element.appendChild(this.video);
    }.bind(this);

    /* error is also called when someone denies access */
    var error = function error() {
      this.trigger('error', arguments);
    }.bind(this);

    return navigator.getUserMedia({ video: true }, success, error);
  };

  /* Stop it - TODO: figure out how to actually disable the stream */
  SayCheese.prototype.stop = function stop() {
    this.video = null;
    document.body.removeChild(video);
  };

  return SayCheese;

})(jQuery);
<commit_msg>refactor error handling, and fail without raising exceptions<commit_after>/*
 * Say Cheese!
 * Lee Machin, 2012
 * http://leemach.in, http://new-bamboo.co.uk
 *
 * Minimal javascript library for integrating a webcam and snapshots into your app.
 *
 * Handles starting up the webcam and rendering the element, and also capturing shots
 * in a separate canvas element.
 *
 * Depends on video and canvas, and of course, getUserMedia. It's unlikely to work
 * on anything but the newest browsers.
 */

var SayCheese = (function($) {

  var SayCheese;

  /* Check for the existence of the userMedia feature. */
  navigator.getUserMedia = (navigator.getUserMedia ||
                            navigator.webkitGetUserMedia ||
                            navigator.mozGetUserMedia ||
                            navigator.msGetUserMedia ||
                            false);

  function eventCoords(evt) {
    return { x: evt.offsetX || evt.layerX, y: evt.offsetY || evt.layerY };
  };

  SayCheese = function SayCheese(element) {
    this.viewfinder = {},
    this.snapshots = [],
    this.canvas = null,
    this.context = null,
    this.video = null,
    this.events = {},

    this.element = document.querySelectorAll(element)[0];
    this.element.style.position = 'relative';

    return this;
  };

  /**
   * Wrap the jQuery stuff so as to minimise the impact of the framework on
   * the rest of the code.
   */
  SayCheese.prototype.on = function on(evt, handler) {
    return $(this).on(evt, handler);
  };

  SayCheese.prototype.off = function off(evt, handler) {
    return $(this).off(evt, handler);
  };

  SayCheese.prototype.trigger = function trigger(evt, data) {
    // bubbling up the DOM makes things go a bit crazy. This assumes
    // preventDefault
    return $(this).triggerHandler(evt, data);
  };

  SayCheese.prototype.getStreamUrl = function getStreamUrl(stream) {
    url = (function() {
      return (window.URL || window.webkitURL);
    })();

    return (url && url.createObjectURL) ? url.createObjectURL(stream) : stream;
  };

  SayCheese.prototype.createVideo = function createVideo() {
    this.video = document.createElement('video');
    this.video.autoplay = true;
  };

  SayCheese.prototype.setupCanvas = function setupCanvas() {
    this.canvas = document.createElement('canvas');
    this.canvas.width = this.video.offsetWidth;
    this.canvas.height = this.video.offsetHeight;

    this.canvas.style.position = 'absolute';
    this.canvas.style.top = this.video.offsetTop;
    this.canvas.style.left = this.video.offsetLeft;

    this.context = this.canvas.getContext('2d');

    this.element.appendChild(this.canvas);

    this.initViewfinder();

    return this.trigger('start');
  };

  /* The default viewfinder is just the exact size of the video */
  SayCheese.prototype.initViewfinder = function initViewfinder() {
    return this.viewfinder = {
      width: this.video.offsetWidth,
      height: this.video.offsetHeight
    };
  };

  SayCheese.prototype.takeSnapshot = function takeSnapshot() {
    var snapshot = document.createElement('canvas'),
        ctx      = snapshot.getContext('2d');

    snapshot.width  = this.viewfinder.width;
    snapshot.height = this.viewfinder.height;

    ctx.drawImage(this.video, 0, 0);

    this.snapshots.push(snapshot);
    this.trigger('snapshot', snapshot);

    ctx = null;
  };

  /* Start up the stream, if possible */
  SayCheese.prototype.start = function start() {

    // fail fast and softly if browser not supported
    if (navigator.getUserMedia === false) {
      this.trigger('error', 'NOT_SUPPORTED');
      return false;
    }

    var success = function success(stream) {
      this.createVideo();

      // video width and height don't exist until metadata is loaded
      this.video.addEventListener('loadedmetadata', this.setupCanvas.bind(this));
      this.video.src = this.getStreamUrl(stream);
      this.element.appendChild(this.video);
    }.bind(this);

    /* error is also called when someone denies access */
    var error = function error(msg) {
      this.trigger('error', msg);
    }.bind(this);

    return navigator.getUserMedia({ video: true }, success, error);
  };

  /* Stop it - TODO: figure out how to actually disable the stream */
  SayCheese.prototype.stop = function stop() {
    this.video = null;
    document.body.removeChild(video);
  };

  return SayCheese;

})(jQuery);
<|endoftext|>


<commit_before>/*

	Ractive-decorators-minmaxwidth
	==============================

	Version 0.1.0.

    This decorator provides Element Media Queries by setting data-attributes for every matched min/max widths in the decorator options.
    It will also set the current width as a variable on the Ractive instance if you provide a keypath in the options.

	The resize-detection is based on http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/
	with a setInterval-fallback for for browsers with none of the following events on elements:
	    - over/underflow events
	    - OverflowEvent/overflowchanged event
	    - onresize (IE11 removed 'onresize' events in favor of Mutators. But the mutators doesn't trigger on css/non-js triggered changes..)

	A big thank you to https://github.com/sdecima, which research on IE11's lack of onresize events and the Mutators shortcomings made my life so much easier.

	==========================

	Troubleshooting: If you're using a module system in your app (AMD or
	something more nodey) then you may need to change the paths below,
	where it says `require( 'Ractive' )` or `define([ 'Ractive' ]...)`.

	==========================

	Usage: Include this file on your page below Ractive, e.g:

	Or, if you're using a module loader, require this module:

	    // requiring the plugin will 'activate' it - no need to use
	    // the return value
	    require( 'Ractive-decorators-minmaxwidth' );

    Add the decorator in your template with one or more of these options:

        * `min` Array of min-widths (or single width) to match against [optional]
        * `max` Array of max-widths (or single width) to match against [optional]
        * `keypath` Keypath to use for setting the current width in the Ractive instance [optional]

        {{my_width}}

    The decorator will add `data-min-width` and `data-max-width` attributes holding a space-delimited list of matched min/max values, and set the given keypath to the current width (not just if it matches one of the min/max values):

200

    You then use the `~=` attribute-selector to write styles targeting the specific min/max values:

        .item[data-min-width~="100"] {
        ...
        }

    It's recommended to use a className with the attribute-selector, so your CSS rules don't match more elements than it should.

    See the demo with more CSS examples: http://cfenzo.github.io/Ractive-decorators-minmaxwidth/

*/

(function ( global, factory ) {

	'use strict';

	// Common JS (i.e. browserify) environment
	if ( typeof module !== 'undefined' && module.exports && typeof require === 'function' ) {
		factory( require( 'Ractive' ) );
	}

	// AMD?
	else if ( typeof define === 'function' && define.amd ) {
		define([ 'Ractive' ], factory );
	}

	// browser global
	else if ( global.Ractive ) {
		factory( global.Ractive );
	}

	else {
		throw new Error( 'Could not find Ractive! It must be loaded before the Ractive-decorators-minmaxwidth plugin' );
	}

}( typeof window !== 'undefined' ? window : this, function ( Ractive ) {

	'use strict';

    var styles_added = [],
        stylesheet,
        no_flowevents = ( (!(window.ActiveXObject) && "ActiveXObject" in window) || !('UIEvent' in window) ), // this is ugly, but we have to live with it..
        _poller_interval = 250,
        _poller_runner,
        _poller_elements = [];

    // IE8 polyfill
    if(!Array.isArray) {
        Array.isArray = function (vArg) {
            var isArray;
            isArray = vArg instanceof Array;
            return isArray;
        };
    }

    function fireEvent(element, type, data, options){
        var event;
        options = options || {};
        event = document.createEvent('Event');
        event.initEvent(type, 'bubbles' in options ? options.bubbles : true, 'cancelable' in options ? options.cancelable : true);
        for (var z in data) event[z] = data[z];
        element.dispatchEvent(event);
    }

    function addSensorStyles(sensorClass){
        if(!styles_added[sensorClass]){
            var style = '.'+sensorClass+', .'+sensorClass+' > div {position: absolute;top: 0;left: 0;width: 100%;height: 100%;overflow: hidden;z-index: -1;}';
            if(!stylesheet){
                stylesheet = document.createElement('style');
                stylesheet.type = 'text/css';
                stylesheet.id = 'ractive_decorator_minmaxwidth_styles';
                document.getElementsByTagName("head")[0].appendChild(stylesheet);
            }

            if (stylesheet.styleSheet) {
                stylesheet.styleSheet.cssText = style;
            }else {
                stylesheet.appendChild(document.createTextNode(style));
            }
            styles_added[sensorClass] = true;
        }
    }

    function usePollResizer(element,supports_onresize){
        return (!supports_onresize && no_flowevents) || !(!element.namespaceURI || element.namespaceURI === 'http://www.w3.org/1999/xhtml');
    }
    function addResizePoller(element){
        if(_poller_elements.indexOf(element) === -1) _poller_elements.push(element);
        if(!_poller_runner) _poller_runner = window.setInterval(function(){
            _poller_elements.forEach(function(element){
                var _lastSize = element._lastSize || {width:0,height:0},
                    ow = element.offsetWidth,
                    oh = element.offsetHeight;

                if(ow !== _lastSize.width || oh !== _lastSize.height){
                    element._lastSize = {width:ow,height:oh};
                    fireEvent(element,'resize');
                }
            });
        },_poller_interval);
    }
    function removeResizePoller(element){
        var index = _poller_elements.indexOf(element);
        if(index > -1) _poller_elements.splice(index,1);
        if(_poller_elements.length < 1) window.clearInterval(_poller_runner);
    }

    function addFlowListener(element, type, fn){
        var flow = type == 'over';
        element.addEventListener('OverflowEvent' in window ? 'overflowchanged' : type + 'flow', function(e){
            if (e.type == (type + 'flow') ||
                ((e.orient === 0 && e.horizontalOverflow === flow) ||
                    (e.orient === 1 && e.verticalOverflow === flow) ||
                    (e.orient === 2 && e.horizontalOverflow === flow && e.verticalOverflow === flow))) {
                e.flow = type;
                return fn.call(this, e);
            }
        }, false);
    }

    function addResizeListener(element, fn, sensorClass){
        var supports_onresize = 'onresize' in element;
        if(usePollResizer(element,supports_onresize)){
            addResizePoller(element);
        }else if (!supports_onresize && !element._resizeSensor) {
            var sensor = element._resizeSensor = document.createElement('div');
            sensor.className = sensorClass || 'resize-sensor';
            sensor.innerHTML = '

';
            addSensorStyles(sensorClass);

            var x = 0, y = 0,
                first = sensor.firstElementChild.firstChild,
                last = sensor.lastElementChild.firstChild,
                matchFlow = function(event){
                    var change = false,
                        width = element.offsetWidth;
                    if (x != width) {
                        first.style.width = width - 1 + 'px';
                        last.style.width = width + 1 + 'px';
                        change = true;
                        x = width;
                    }
                    var height = element.offsetHeight;
                    if (y != height) {
                        first.style.height = height - 1 + 'px';
                        last.style.height = height + 1 + 'px';
                        change = true;
                        y = height;
                    }
                    if (change && event.currentTarget != element) fireEvent(element, 'resize');
                };

            if (window.getComputedStyle(element).position == 'static'){
                element.style.position = 'relative';
                element._resizeSensor._resetPosition = true;
            }
            addFlowListener(sensor, 'over', matchFlow);
            addFlowListener(sensor, 'under', matchFlow);
            addFlowListener(sensor.firstElementChild, 'over', matchFlow);
            addFlowListener(sensor.lastElementChild, 'under', matchFlow);
            element.appendChild(sensor);
            matchFlow({});
        }
        var events = element._resizeEvents || (element._resizeEvents = []);
        if (events.indexOf(fn) === -1) events.push(fn);
        if (!supports_onresize) element.addEventListener('resize', fn);
        element.onresize = function(e){
            events.forEach(function(fn){
                fn.call(element, e);
            });
        };
    }
    function removeResizeListener(element, fn){
        var index,
            supports_onresize = 'onresize' in element;

        index = element._resizeEvents.indexOf(fn);
        if (index > -1) element._resizeEvents.splice(index, 1);
        if (!element._resizeEvents.length) {
            var sensor = element._resizeSensor;
            if (sensor) {
                element.removeChild(sensor);
                if (sensor._resetPosition) element.style.position = 'static';
                try { delete element._resizeSensor; } catch(e) { /* delete arrays not supported on IE 7 and below */}
            }
            if (supports_onresize) element.onresize = null;
            try { delete element._resizeEvents; } catch(e) { /* delete arrays not supported on IE 7 and below */}
            if(usePollResizer(element,supports_onresize)) removeResizePoller(element); // only call removeResizePoller when there's no resize events left on the element
        }
        if(!supports_onresize) element.removeEventListener('resize', fn);
    }

    var minmaxwidth = function( node ){
        var breakpoints,
            keypath,
            min,
            max,
            R = node._ractive.root;

        if(arguments.length < 2) {
            throw new Error( 'No parameters provided for minmaxwidth decorator.' );
        }

        function on_modified(){
            var minWidths = [],
                maxWidths = [];
                breakpoints.forEach(function(width){
                    (node.offsetWidth>=parseInt(width)?minWidths:maxWidths).push(width);
                });
            node.setAttribute('data-min-width',minWidths.join(' '));
            node.setAttribute('data-max-width',maxWidths.join(' '));
            if(keypath) R.set(keypath, node.offsetWidth);
            node.className = node.className; // ugly IE8 hack to reset styles
        }

        if(Object.prototype.toString.call(arguments[1]).slice(8, -1).toLowerCase() === 'object'){
            breakpoints = arguments[1].breakpoints || [];
            min = arguments[1].min || [];
            max = arguments[1].max || [];
            keypath = arguments[1].keypath || false;

            if(!Array.isArray(breakpoints)) breakpoints = [breakpoints];
            if(!Array.isArray(min)) min = [min];
            if(!Array.isArray(max)) max = [max];

            min.concat(max).forEach(function(val){
               if(breakpoints.indexOf(val) === -1) breakpoints.push(val);
            });
        }else{
            // got array
            breakpoints = Array.isArray(arguments[1]) ? arguments[1] : [arguments[1]];
            if(arguments.length > 2 && typeof arguments[2] === 'string') keypath = arguments[2];
        }

        // add pretty events
        addResizeListener(node, on_modified, minmaxwidth.sensorClass);
        on_modified(); // run on initialization

        return {
            teardown: function () {
                // remove event listeners
                removeResizeListener(node);
            }
        };

    };

    // defaults
    minmaxwidth.sensorClass = 'resize-sensor';

    Ractive.decorators.minmaxwidth = minmaxwidth;
}));<commit_msg>Moved the initial call to on_modified to fix part of #5<commit_after>/*

	Ractive-decorators-minmaxwidth
	==============================

	Version 0.1.0.

    This decorator provides Element Media Queries by setting data-attributes for every matched min/max widths in the decorator options.
    It will also set the current width as a variable on the Ractive instance if you provide a keypath in the options.

	The resize-detection is based on http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/
	with a setInterval-fallback for for browsers with none of the following events on elements:
	    - over/underflow events
	    - OverflowEvent/overflowchanged event
	    - onresize (IE11 removed 'onresize' events in favor of Mutators. But the mutators doesn't trigger on css/non-js triggered changes..)

	A big thank you to https://github.com/sdecima, which research on IE11's lack of onresize events and the Mutators shortcomings made my life so much easier.

	==========================

	Troubleshooting: If you're using a module system in your app (AMD or
	something more nodey) then you may need to change the paths below,
	where it says `require( 'Ractive' )` or `define([ 'Ractive' ]...)`.

	==========================

	Usage: Include this file on your page below Ractive, e.g:

	Or, if you're using a module loader, require this module:

	    // requiring the plugin will 'activate' it - no need to use
	    // the return value
	    require( 'Ractive-decorators-minmaxwidth' );

    Add the decorator in your template with one or more of these options:

        * `min` Array of min-widths (or single width) to match against [optional]
        * `max` Array of max-widths (or single width) to match against [optional]
        * `keypath` Keypath to use for setting the current width in the Ractive instance [optional]

        {{my_width}}

    The decorator will add `data-min-width` and `data-max-width` attributes holding a space-delimited list of matched min/max values, and set the given keypath to the current width (not just if it matches one of the min/max values):

200

    You then use the `~=` attribute-selector to write styles targeting the specific min/max values:

        .item[data-min-width~="100"] {
        ...
        }

    It's recommended to use a className with the attribute-selector, so your CSS rules don't match more elements than it should.

    See the demo with more CSS examples: http://cfenzo.github.io/Ractive-decorators-minmaxwidth/

*/

(function ( global, factory ) {

	'use strict';

	// Common JS (i.e. browserify) environment
	if ( typeof module !== 'undefined' && module.exports && typeof require === 'function' ) {
		factory( require( 'Ractive' ) );
	}

	// AMD?
	else if ( typeof define === 'function' && define.amd ) {
		define([ 'Ractive' ], factory );
	}

	// browser global
	else if ( global.Ractive ) {
		factory( global.Ractive );
	}

	else {
		throw new Error( 'Could not find Ractive! It must be loaded before the Ractive-decorators-minmaxwidth plugin' );
	}

}( typeof window !== 'undefined' ? window : this, function ( Ractive ) {

	'use strict';

    var styles_added = [],
        stylesheet,
        no_flowevents = ( (!(window.ActiveXObject) && "ActiveXObject" in window) || !('UIEvent' in window) ), // this is ugly, but we have to live with it..
        _poller_interval = 250,
        _poller_runner,
        _poller_elements = [];

    // IE8 polyfill
    if(!Array.isArray) {
        Array.isArray = function (vArg) {
            var isArray;
            isArray = vArg instanceof Array;
            return isArray;
        };
    }

    function fireEvent(element, type, data, options){
        var event;
        options = options || {};
        event = document.createEvent('Event');
        event.initEvent(type, 'bubbles' in options ? options.bubbles : true, 'cancelable' in options ? options.cancelable : true);
        for (var z in data) event[z] = data[z];
        element.dispatchEvent(event);
    }

    function addSensorStyles(sensorClass){
        if(!styles_added[sensorClass]){
            var style = '.'+sensorClass+', .'+sensorClass+' > div {position: absolute;top: 0;left: 0;width: 100%;height: 100%;overflow: hidden;z-index: -1;}';
            if(!stylesheet){
                stylesheet = document.createElement('style');
                stylesheet.type = 'text/css';
                stylesheet.id = 'ractive_decorator_minmaxwidth_styles';
                document.getElementsByTagName("head")[0].appendChild(stylesheet);
            }

            if (stylesheet.styleSheet) {
                stylesheet.styleSheet.cssText = style;
            }else {
                stylesheet.appendChild(document.createTextNode(style));
            }
            styles_added[sensorClass] = true;
        }
    }

    function usePollResizer(element,supports_onresize){
        return (!supports_onresize && no_flowevents) || !(!element.namespaceURI || element.namespaceURI === 'http://www.w3.org/1999/xhtml');
    }
    function addResizePoller(element){
        if(_poller_elements.indexOf(element) === -1) _poller_elements.push(element);
        if(!_poller_runner) _poller_runner = window.setInterval(function(){
            _poller_elements.forEach(function(element){
                var _lastSize = element._lastSize || {width:0,height:0},
                    ow = element.offsetWidth,
                    oh = element.offsetHeight;

                if(ow !== _lastSize.width || oh !== _lastSize.height){
                    element._lastSize = {width:ow,height:oh};
                    fireEvent(element,'resize');
                }
            });
        },_poller_interval);
    }
    function removeResizePoller(element){
        var index = _poller_elements.indexOf(element);
        if(index > -1) _poller_elements.splice(index,1);
        if(_poller_elements.length < 1) window.clearInterval(_poller_runner);
    }

    function addFlowListener(element, type, fn){
        var flow = type == 'over';
        element.addEventListener('OverflowEvent' in window ? 'overflowchanged' : type + 'flow', function(e){
            if (e.type == (type + 'flow') ||
                ((e.orient === 0 && e.horizontalOverflow === flow) ||
                    (e.orient === 1 && e.verticalOverflow === flow) ||
                    (e.orient === 2 && e.horizontalOverflow === flow && e.verticalOverflow === flow))) {
                e.flow = type;
                return fn.call(this, e);
            }
        }, false);
    }

    function addResizeListener(element, fn, sensorClass){
        var supports_onresize = 'onresize' in element;
        if(usePollResizer(element,supports_onresize)){
            addResizePoller(element);
        }else if (!supports_onresize && !element._resizeSensor) {
            var sensor = element._resizeSensor = document.createElement('div');
            sensor.className = sensorClass || 'resize-sensor';
            sensor.innerHTML = '

';
            addSensorStyles(sensorClass);

            var x = 0, y = 0,
                first = sensor.firstElementChild.firstChild,
                last = sensor.lastElementChild.firstChild,
                matchFlow = function(event){
                    var change = false,
                        width = element.offsetWidth;
                    if (x != width) {
                        first.style.width = width - 1 + 'px';
                        last.style.width = width + 1 + 'px';
                        change = true;
                        x = width;
                    }
                    var height = element.offsetHeight;
                    if (y != height) {
                        first.style.height = height - 1 + 'px';
                        last.style.height = height + 1 + 'px';
                        change = true;
                        y = height;
                    }
                    if (change && event.currentTarget != element) fireEvent(element, 'resize');
                };

            if (window.getComputedStyle(element).position == 'static'){
                element.style.position = 'relative';
                element._resizeSensor._resetPosition = true;
            }
            addFlowListener(sensor, 'over', matchFlow);
            addFlowListener(sensor, 'under', matchFlow);
            addFlowListener(sensor.firstElementChild, 'over', matchFlow);
            addFlowListener(sensor.lastElementChild, 'under', matchFlow);
            element.appendChild(sensor);
            matchFlow({});
        }
        var events = element._resizeEvents || (element._resizeEvents = []);
        if (events.indexOf(fn) === -1) events.push(fn);
        if (!supports_onresize) element.addEventListener('resize', fn);
        element.onresize = function(e){
            events.forEach(function(fn){
                fn.call(element, e);
            });
        };
    }
    function removeResizeListener(element, fn){
        var index,
            supports_onresize = 'onresize' in element;

        index = element._resizeEvents.indexOf(fn);
        if (index > -1) element._resizeEvents.splice(index, 1);
        if (!element._resizeEvents.length) {
            var sensor = element._resizeSensor;
            if (sensor) {
                element.removeChild(sensor);
                if (sensor._resetPosition) element.style.position = 'static';
                try { delete element._resizeSensor; } catch(e) { /* delete arrays not supported on IE 7 and below */}
            }
            if (supports_onresize) element.onresize = null;
            try { delete element._resizeEvents; } catch(e) { /* delete arrays not supported on IE 7 and below */}
            if(usePollResizer(element,supports_onresize)) removeResizePoller(element); // only call removeResizePoller when there's no resize events left on the element
        }
        if(!supports_onresize) element.removeEventListener('resize', fn);
    }

    var minmaxwidth = function( node ){
        var breakpoints,
            keypath,
            min,
            max,
            R = node._ractive.root;

        if(arguments.length < 2) {
            throw new Error( 'No parameters provided for minmaxwidth decorator.' );
        }

        function on_modified(){
            var minWidths = [],
                maxWidths = [];
                breakpoints.forEach(function(width){
                    (node.offsetWidth>=parseInt(width)?minWidths:maxWidths).push(width);
                });
            node.setAttribute('data-min-width',minWidths.join(' '));
            node.setAttribute('data-max-width',maxWidths.join(' '));
            if(keypath) R.set(keypath, node.offsetWidth);
            node.className = node.className; // ugly IE8 hack to reset styles
        }

        if(Object.prototype.toString.call(arguments[1]).slice(8, -1).toLowerCase() === 'object'){
            breakpoints = arguments[1].breakpoints || [];
            min = arguments[1].min || [];
            max = arguments[1].max || [];
            keypath = arguments[1].keypath || false;

            if(!Array.isArray(breakpoints)) breakpoints = [breakpoints];
            if(!Array.isArray(min)) min = [min];
            if(!Array.isArray(max)) max = [max];

            min.concat(max).forEach(function(val){
               if(breakpoints.indexOf(val) === -1) breakpoints.push(val);
            });
        }else{
            // got array
            breakpoints = Array.isArray(arguments[1]) ? arguments[1] : [arguments[1]];
            if(arguments.length > 2 && typeof arguments[2] === 'string') keypath = arguments[2];
        }

        on_modified(); // run on initialization
        // add pretty events
        addResizeListener(node, on_modified, minmaxwidth.sensorClass);

        return {
            teardown: function () {
                // remove event listeners
                removeResizeListener(node);
            }
        };

    };

    // defaults
    minmaxwidth.sensorClass = 'resize-sensor';

    Ractive.decorators.minmaxwidth = minmaxwidth;
}));<|endoftext|>


<commit_before>var connectsdk = {};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Event emitter functions

var SimpleEventEmitter = {
    addListener: function (event, callback, context) {
        if (!event) { throw new Error("missing parameter: event"); }
        if (!callback) { throw new Error("missing parameter: callback"); }

        this._listeners = this._listeners || {};
        if (!this._listeners) this._listeners = {};
        if (!this._listeners[event]) this._listeners[event] = [];
        this._listeners[event].push({callback: callback, context: context});

        this.emit("_addListener", event);

        return this;
    },

    removeListener: function (event, callback, context) {
        if (this._listeners && this._listeners[event]) {
            this._listeners[event] = this._listeners[event].filter(function (l) {
                return (callback && callback !== l.callback) && (context && context !== l.context);
            });
        }

        this.emit("_removeListener", event);

        return this;
    },

    hasListeners: function (event) {
        if (event) {
            return (this._listeners && this._listeners[event] && this._listeners[event].length > 0);
        } else {
            for (event in this._listeners) {
                if (event[0] !== "_" && this._listeners.hasOwnProperty(event) && this._listeners[event].length > 0) {
                    return true;
                }
            }
            return false;
        }
    },

    emit: function (event) {
        var listeners = this._listeners && this._listeners[event];
        var args = Array.prototype.slice.call(arguments, 1);

        if (listeners) {
            listeners.forEach(function (l) {
                l.callback.apply(l.context || null, args);
            });
        }

        if (this["on" + event]) {
            this["on" + event].apply(null, args);
        }
    },

    on: function (event, callback, context) {
        return this.addListener(event, callback, context);
    },

    off: function (event, callback, context) {
        return this.removeListener(event, callback, context);
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// very simple class maker

var createClass = function (desc) {
    var constructor;

    if (desc.constructor) {
        constructor = desc.constructor;
        delete desc.constructor;
    } else {
        constructor = function () {};
        throw new Error("no constructor");
    }

    var prototype = constructor.prototype;

    if (desc.mixins) {
        desc.mixins.forEach(function (mixin) {
            for (var mixinProp in mixin) {
                if (mixin.hasOwnProperty(mixinProp)) {
                    prototype[mixinProp] = mixin[mixinProp];
                }
            }
        });
        delete desc.mixins;
    }

    if (desc.statics) {
        for (var staticProp in desc.statics) {
            if (desc.statics.hasOwnProperty(staticProp)) {
                constructor[staticProp] = desc.statics[staticProp];
            }
        }
        delete desc.statics;
    }

    for (var p in desc) {
        if (desc.hasOwnProperty(p)) {
            prototype[p] = desc[p];
        }
    }

    return constructor;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// connectsdk.ConnectManager

connectsdk.ConnectManager = createClass({
    mixins: [SimpleEventEmitter],

    statics: {
        PlatformType: {
            DEFAULT: "default",
            GOOGLE_CAST: "Google Cast",
            WEBOS_NATIVE: "webOS Native",
            WEBOS_WEB_APP: "webOS Web App"
        },

        EventType: {
            PLAY: "play",
            PAUSE: "pause",
            STOP: "stop",
            MESSAGE: "message"
        }
    },

    constructor: function() {
        this._detectPlatform();
    },

    registerVideoElement: function(element) {
        if (this.videoElement)
        {
            this.videoElement.removeEventListener('loadstart');
            this.videoElement.removeEventListener('playing');
            this.videoElement.removeEventListener('waiting');
            this.videoElement.removeEventListener('ended');
            this.videoElement.removeEventListener('play');
            this.videoElement.removeEventListener('pause');
        }

        this.videoElementStatus = 'idle';
        this.videoElement = element;

        if (this.videoElement)
        {
            var self = this;

            this.videoElement.addEventListener('loadstart', function() { self.videoElementStatus = 'buffering'; self.emit('videoStatusUpdate'); });
            this.videoElement.addEventListener('playing', function() { self.videoElementStatus = 'playing'; self.emit('videoStatusUpdate'); });
            this.videoElement.addEventListener('waiting', function() { self.videoElementStatus = 'buffering'; self.emit('videoStatusUpdate'); });
            this.videoElement.addEventListener('ended', function() { self.videoElementStatus = 'finished'; self.emit('videoStatusUpdate'); });
            this.videoElement.addEventListener('play'), function() { self.videoElementStatus = 'playing'; self.emit('videoStatusUpdate'); };
            this.videoElement.addEventListener('pause', function() { self.videoElementStatus = 'paused'; self.emit('videoStatusUpdate'); });
        }
    },

    registerAudioElement: function(element) {
        this.audioElement = element;
    },

    registerImageElement: function(element) {
        this.imageElement = element;
    },

    init: function() {
        if (this.platformType == connectsdk.ConnectManager.PlatformType.GOOGLE_CAST)
            this._initCastService();
        else if (this.platformType == connectsdk.ConnectManager.PlatformType.WEBOS_NATIVE || this.platformType == connectsdk.ConnectManager.PlatformType.WEBOS_WEB_APP)
            this._initWebOSService();
    },

    _detectPlatform: function() {
        if (navigator.userAgent.indexOf("CrKey") > 0 && cast != null)
            this.platformType = connectsdk.ConnectManager.PlatformType.GOOGLE_CAST;
        else if (window.PalmSystem)
        {
            if (window.PalmServiceBridge)
                this.platformType = connectsdk.ConnectManager.PlatformType.WEBOS_NATIVE;
            else
                this.platformType = connectsdk.ConnectManager.PlatformType.WEBOS_WEB_APP;
        } else
            this.platformType = connectsdk.ConnectManager.PlatformType.DEFAULT;
    },

    _initCastService: function() {
        var self = this;

        if (this.videoElement)
        {
            console.log('registering video element ' + this.videoElement);
            window.castMediaManager = new cast.receiver.MediaManager(this.videoElement);
        }

        window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();

        window.castMessageBus = window.castReceiverManager.getCastMessageBus("urn:x-cast:com.connectsdk");
        window.castMessageBus.onMessage = function(evt) {
            var message;

            try
            {
                message = JSON.parse(evt.data);
            } catch (ex)
            {
                message = evt.data;
            }

            self.emit("message", message);
        };

        window.castReceiverManager.onReady = function(evt) {
            self.emit('ready');
        };

        this.sendMessage = this._sendMessageCast;

        window.castReceiverManager.start();
    },

    _initWebOSService: function() {
        var self = this;

        window.addEventListener("keydown", function(evt) {
            switch (evt.keyCode)
            {
                case 415: // PLAY
                    console.log(self.platformType + " :: play command received");
                    self.videoElement.play();
                    self.emit(connectsdk.ConnectManager.EventType.PLAY);
                    break;

                case 19: // PAUSE
                    console.log(self.platformType + " :: pause command received");
                    self.videoElement.pause();
                    self.emit(connectsdk.ConnectManager.EventType.PAUSE);
                    break;

                case 413: // STOP
                    console.log(self.platformType + " :: stop command received");
                    self.emit(connectsdk.ConnectManager.EventType.STOP);
                    break;
            }
        });

        this.webOSAppChannels = new connectsdk.WebOSAppChannels();
        this.webOSAppChannels.connectManager = this;

        this.webOSAppChannels.on('message', function(message) {
            self.emit('message', message);
        });

        this.webOSAppChannels.on('ready', function(evt) {
            self.emit('ready');
        });

        this.webOSAppChannels.start();

        this.sendMessage = this._sendMessageWebOS;

        var mediaType = getParameterByName('mediaType');
        var mediaURL = getParameterByName('target');
        var mimeType = getParameterByName('mimeType');
        var title = getParameterByName('title');
        var description = getParameterByName('description');
        var iconSrc = getParameterByName('iconSrc');
        var shouldLoop = getParameterByName('shouldLoop') == 'true';

        if (mediaType == 'video')
        {
            console.log('test');
            this.videoElement.src = mediaURL;
            console.log(this.videoElement);
            console.log(this.videoElement.src);
        }
    },

    _sendMessageCast: function(message) {
        var messageString;

        try {
            messageString = JSON.stringify(message);
        } catch (ex) {
            messageString = message;
        }

        window.castMessageBus.broadcast(messageString);
    },

    _sendMessageWebOS: function(message) {
        var messageJSON;

        try {
            JSON.parse(message);
            messageJSON = message;
        } catch (ex) {
            messageJSON = { message: message };
        }

        this.webOSAppChannels.sendMessage(messageJSON);
    }
});

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helpers

function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(location.search);
    return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// connectsdk.WebOSAppChannels

connectsdk.WebOSAppChannels = createClass({
    mixins: [SimpleEventEmitter],

    stopRequested: false,
    ws: null,
    channels: [],

    constructor: function() {
        if (window.PalmServiceBridge) {
            var statusSubscription;
            var getChannelRequest;

            var GATEWAY_SERVICE = "com.webos.service.secondscreen.gateway";
            var CREATE_APPCHANNEL_URI = "luna://com.webos.service.secondscreen.gateway/app2app/createAppChannel";
            var REGISTER_SERVER_STATUS_URI = "luna://com.palm.bus/signal/registerServerStatus";

            var callService = function (uri, params, callback) {
                var request = new window.PalmServiceBridge();

                request.onservicecallback = function (responseString) {
                    callback(JSON.parse(responseString));
                };

                request.call(uri, JSON.stringify(params || {}));

                return request;
            };

            this.getAppChannelWebSocket = function (callback) {
                statusSubcription = callService(REGISTER_SERVER_STATUS_URI, {
                    "subscribe": true, serviceName: GATEWAY_SERVICE
                }, function (response) {
                    if (response && response.connected) {
                        getChannelRequest = callService(CREATE_APPCHANNEL_URI, {}, function (response) {
                            if (response.socketUrl) {
                                callback(new WebSocket(response.socketUrl));
                            }
                        });
                    }
                });
            };
        } else {
            // Look for "webOSAppChannelSocketUrl" parameter
            if (window.location.search) {
                console.log("found params: ", window.location.search);
                /**
                 * @author Konstantin Kitmanov
                 * May be freely distributed under the MIT license.
                 */
                var parse = function (str) {
                    var chunks = str.split('&'),
                        dict = {},
                        chunk;
                    for (var i = 0, len = chunks.length; i < len; i++) {
                        chunk = chunks[i].split('=');
                        dict[chunk[0]] = decodeURIComponent(chunk[1]);
                    }

                    return dict;
                };

                var parsed = parse(window.location.search.substr(1));

                if (parsed.webOSAppChannelSocketUrl) {
                    console.log("found websocket URL: ", parsed.webOSAppChannelSocketUrl);

                    this.getAppChannelWebSocket = function (callback) {
                        console.log("opened websocket connection")
                        callback(new WebSocket(parsed.webOSAppChannelSocketUrl));
                    };
                }
            }
        }
    },

    getAppChannelWebSocket: function (callback) {
        console.error("app channel socket not supported");
    },

    sendMessage: function(message) {
        var messageData = {
            type: "p2p",
            payload: message
        };

        this._send(messageData);
    },

    _send: function (message) {
        if (this.ws && message) {
            console.log("sending message: ", message);
            this.ws.send(JSON.stringify(message));
        }
    },

    _destroy: function () {
        if (this.ws) {
            this.ws = null;
        }
    },

    start: function () {
        stopRequested = false;
        var self = this;

        this.getAppChannelWebSocket(function (socket) {
            if (stopRequested) {
                self.stop();
                return;
            }

            self.ws = socket;

            self.ws.onopen = function (event) {
                self.connectManager.on('videoStatusUpdate', self._handleVideoStatusUpdate, self);
                self.emit('ready', event);
            };

            self.ws.onmessage = function (event) {
                var message = JSON.parse(event.data);
                var payload = message.payload;

                console.log("got message: " + JSON.stringify(message));

                if (message.type === "p2p") {
                    if (typeof payload !== 'undefined') {
                        self._processP2PMessage(message);
                    }
                } else if (message.type === "p2p.join-request") {
                    self._send({
                        type: "p2p.join-response",
                        payload: {
                            allowJoin: true,
                            requestId: payload.requestId
                        }
                    });
                } else if (message.type === "p2p.join") {
                    self.emit("join", {client: payload && payload.client});
                } else if (message.type === "p2p.depart") {
                    self.emit("depart", {client: payload && payload.client});
                }
            };

            self.ws.onclose = function () {
                self.ws = null;
            };
        });
    },

    stop: function () {
        this.stopRequested = true;

        if (this.ws) {
            this.connectManager.off('videoStatusUpdate');
            this.ws.close();
        }

        this._destroy();
    },

    _handleVideoStatusUpdate: function() {
        var playState = this.connectManager.videoElementStatus;
        var currentTime = 0;
        var duration = 0;

        if (this.connectManager.videoElement)
        {
            currentTime = this.connectManager.videoElement.currentTime;

            if (this.connectManager.videoElement.duration != NaN)
                duration = this.connectManager.videoElement.duration
        }

        if (playState == null)
            return;

        this.sendMessage({
                    contentType: 'mediaEvent',
                    mediaEvent: {
                        type: 'playState',
                        playState: playState,
                        position: currentTime,
                        duration: duration
                    }
                });
    },

    _processP2PMessage: function(message) {
        console.log('processing message payload ' + JSON.stringify(message.payload));
        var contentType = message.payload.contentType;
        var from = message.from;

        if (contentType === 'mediaCommand')
        {
            console.log('processing mediaCommand ' + JSON.stringify(message.payload.mediaCommand) + ' of type ' + message.payload.mediaCommand.type);

            var commandType = message.payload.mediaCommand.type;
            var requestId = message.payload.mediaCommand.requestId;
            var videoElement = this.connectManager.videoElement;

            if (commandType === 'seek')
            {
                var position = message.payload.mediaCommand.position;

                if (position)
                {
                    videoElement.currentTime = position;
                    this._handleVideoStatusUpdate();
                }
            }
            else if (commandType === 'getPosition')
            {
                this._send({
                            type: 'p2p',
                            to: from,
                            payload: {
                                contentType: 'mediaCommandResponse',
                                mediaCommandResponse: {
                                    type: commandType,
                                    position: videoElement.currentTime,
                                    requestId:requestId
                                }
                            }
                        });
            } else if (commandType === 'getDuration')
            {
                this._send({
                            type: 'p2p',
                            to: from,
                            payload: {
                                contentType: 'mediaCommandResponse',
                                mediaCommandResponse: {
                                    type: commandType,
                                    duration: videoElement.duration,
                                    requestId:requestId
                                }
                            }
                        });
            } else if (commandType === 'getPlayState')
            {

            }
        } else
        {
            this.emit('message', message.payload);
        }
    }
});
<commit_msg>Fixed webOS device detection, since window.PalmSystem is no longer accessible to web apps<commit_after>var connectsdk = {};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Event emitter functions

var SimpleEventEmitter = {
    addListener: function (event, callback, context) {
        if (!event) { throw new Error("missing parameter: event"); }
        if (!callback) { throw new Error("missing parameter: callback"); }

        this._listeners = this._listeners || {};
        if (!this._listeners) this._listeners = {};
        if (!this._listeners[event]) this._listeners[event] = [];
        this._listeners[event].push({callback: callback, context: context});

        this.emit("_addListener", event);

        return this;
    },

    removeListener: function (event, callback, context) {
        if (this._listeners && this._listeners[event]) {
            this._listeners[event] = this._listeners[event].filter(function (l) {
                return (callback && callback !== l.callback) && (context && context !== l.context);
            });
        }

        this.emit("_removeListener", event);

        return this;
    },

    hasListeners: function (event) {
        if (event) {
            return (this._listeners && this._listeners[event] && this._listeners[event].length > 0);
        } else {
            for (event in this._listeners) {
                if (event[0] !== "_" && this._listeners.hasOwnProperty(event) && this._listeners[event].length > 0) {
                    return true;
                }
            }
            return false;
        }
    },

    emit: function (event) {
        var listeners = this._listeners && this._listeners[event];
        var args = Array.prototype.slice.call(arguments, 1);

        if (listeners) {
            listeners.forEach(function (l) {
                l.callback.apply(l.context || null, args);
            });
        }

        if (this["on" + event]) {
            this["on" + event].apply(null, args);
        }
    },

    on: function (event, callback, context) {
        return this.addListener(event, callback, context);
    },

    off: function (event, callback, context) {
        return this.removeListener(event, callback, context);
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// very simple class maker

var createClass = function (desc) {
    var constructor;

    if (desc.constructor) {
        constructor = desc.constructor;
        delete desc.constructor;
    } else {
        constructor = function () {};
        throw new Error("no constructor");
    }

    var prototype = constructor.prototype;

    if (desc.mixins) {
        desc.mixins.forEach(function (mixin) {
            for (var mixinProp in mixin) {
                if (mixin.hasOwnProperty(mixinProp)) {
                    prototype[mixinProp] = mixin[mixinProp];
                }
            }
        });
        delete desc.mixins;
    }

    if (desc.statics) {
        for (var staticProp in desc.statics) {
            if (desc.statics.hasOwnProperty(staticProp)) {
                constructor[staticProp] = desc.statics[staticProp];
            }
        }
        delete desc.statics;
    }

    for (var p in desc) {
        if (desc.hasOwnProperty(p)) {
            prototype[p] = desc[p];
        }
    }

    return constructor;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// connectsdk.ConnectManager

connectsdk.ConnectManager = createClass({
    mixins: [SimpleEventEmitter],

    statics: {
        PlatformType: {
            DEFAULT: "default",
            GOOGLE_CAST: "Google Cast",
            WEBOS_NATIVE: "webOS Native",
            WEBOS_WEB_APP: "webOS Web App"
        },

        EventType: {
            PLAY: "play",
            PAUSE: "pause",
            STOP: "stop",
            MESSAGE: "message"
        }
    },

    constructor: function() {
        this._detectPlatform();
    },

    registerVideoElement: function(element) {
        if (this.videoElement)
        {
            this.videoElement.removeEventListener('loadstart');
            this.videoElement.removeEventListener('playing');
            this.videoElement.removeEventListener('waiting');
            this.videoElement.removeEventListener('ended');
            this.videoElement.removeEventListener('play');
            this.videoElement.removeEventListener('pause');
        }

        this.videoElementStatus = 'idle';
        this.videoElement = element;

        if (this.videoElement)
        {
            var self = this;

            this.videoElement.addEventListener('loadstart', function() { self.videoElementStatus = 'buffering'; self.emit('videoStatusUpdate'); });
            this.videoElement.addEventListener('playing', function() { self.videoElementStatus = 'playing'; self.emit('videoStatusUpdate'); });
            this.videoElement.addEventListener('waiting', function() { self.videoElementStatus = 'buffering'; self.emit('videoStatusUpdate'); });
            this.videoElement.addEventListener('ended', function() { self.videoElementStatus = 'finished'; self.emit('videoStatusUpdate'); });
            this.videoElement.addEventListener('play'), function() { self.videoElementStatus = 'playing'; self.emit('videoStatusUpdate'); };
            this.videoElement.addEventListener('pause', function() { self.videoElementStatus = 'paused'; self.emit('videoStatusUpdate'); });
        }
    },

    registerAudioElement: function(element) {
        this.audioElement = element;
    },

    registerImageElement: function(element) {
        this.imageElement = element;
    },

    init: function() {
        if (this.platformType == connectsdk.ConnectManager.PlatformType.GOOGLE_CAST)
            this._initCastService();
        else if (this.platformType == connectsdk.ConnectManager.PlatformType.WEBOS_NATIVE || this.platformType == connectsdk.ConnectManager.PlatformType.WEBOS_WEB_APP)
            this._initWebOSService();
    },

    _detectPlatform: function() {
        var userAgent = navigator.userAgent.toLowerCase();

        if (userAgent.indexOf('crkey') > 0 && cast != null)
            this.platformType = connectsdk.ConnectManager.PlatformType.GOOGLE_CAST;
        else if (userAgent.indexOf('lge') >= 0 && userAgent.indexOf('webos') >= 0)
        {
            if (window.PalmServiceBridge)
                this.platformType = connectsdk.ConnectManager.PlatformType.WEBOS_NATIVE;
            else
                this.platformType = connectsdk.ConnectManager.PlatformType.WEBOS_WEB_APP;
        } else
            this.platformType = connectsdk.ConnectManager.PlatformType.DEFAULT;
    },

    _initCastService: function() {
        var self = this;

        if (this.videoElement)
        {
            console.log('registering video element ' + this.videoElement);
            window.castMediaManager = new cast.receiver.MediaManager(this.videoElement);
        }

        window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();

        window.castMessageBus = window.castReceiverManager.getCastMessageBus("urn:x-cast:com.connectsdk");
        window.castMessageBus.onMessage = function(evt) {
            var message;

            try
            {
                message = JSON.parse(evt.data);
            } catch (ex)
            {
                message = evt.data;
            }

            self.emit("message", message);
        };

        window.castReceiverManager.onReady = function(evt) {
            self.emit('ready');
        };

        this.sendMessage = this._sendMessageCast;

        window.castReceiverManager.start();
    },

    _initWebOSService: function() {
        var self = this;

        window.addEventListener("keydown", function(evt) {
            switch (evt.keyCode)
            {
                case 415: // PLAY
                    console.log(self.platformType + " :: play command received");
                    self.videoElement.play();
                    self.emit(connectsdk.ConnectManager.EventType.PLAY);
                    break;

                case 19: // PAUSE
                    console.log(self.platformType + " :: pause command received");
                    self.videoElement.pause();
                    self.emit(connectsdk.ConnectManager.EventType.PAUSE);
                    break;

                case 413: // STOP
                    console.log(self.platformType + " :: stop command received");
                    self.emit(connectsdk.ConnectManager.EventType.STOP);
                    break;
            }
        });

        this.webOSAppChannels = new connectsdk.WebOSAppChannels();
        this.webOSAppChannels.connectManager = this;

        this.webOSAppChannels.on('message', function(message) {
            self.emit('message', message);
        });

        this.webOSAppChannels.on('ready', function(evt) {
            self.emit('ready');
        });

        this.webOSAppChannels.start();

        this.sendMessage = this._sendMessageWebOS;

        var mediaType = getParameterByName('mediaType');
        var mediaURL = getParameterByName('target');
        var mimeType = getParameterByName('mimeType');
        var title = getParameterByName('title');
        var description = getParameterByName('description');
        var iconSrc = getParameterByName('iconSrc');
        var shouldLoop = getParameterByName('shouldLoop') == 'true';

        if (mediaType == 'video')
        {
            console.log('test');
            this.videoElement.src = mediaURL;
            console.log(this.videoElement);
            console.log(this.videoElement.src);
        }
    },

    _sendMessageCast: function(message) {
        var messageString;

        try {
            messageString = JSON.stringify(message);
        } catch (ex) {
            messageString = message;
        }

        window.castMessageBus.broadcast(messageString);
    },

    _sendMessageWebOS: function(message) {
        var messageJSON;

        try {
            JSON.parse(message);
            messageJSON = message;
        } catch (ex) {
            messageJSON = { message: message };
        }

        this.webOSAppChannels.sendMessage(messageJSON);
    }
});

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helpers

function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(location.search);
    return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// connectsdk.WebOSAppChannels

connectsdk.WebOSAppChannels = createClass({
    mixins: [SimpleEventEmitter],

    stopRequested: false,
    ws: null,
    channels: [],

    constructor: function() {
        if (window.PalmServiceBridge) {
            var statusSubscription;
            var getChannelRequest;

            var GATEWAY_SERVICE = "com.webos.service.secondscreen.gateway";
            var CREATE_APPCHANNEL_URI = "luna://com.webos.service.secondscreen.gateway/app2app/createAppChannel";
            var REGISTER_SERVER_STATUS_URI = "luna://com.palm.bus/signal/registerServerStatus";

            var callService = function (uri, params, callback) {
                var request = new window.PalmServiceBridge();

                request.onservicecallback = function (responseString) {
                    callback(JSON.parse(responseString));
                };

                request.call(uri, JSON.stringify(params || {}));

                return request;
            };

            this.getAppChannelWebSocket = function (callback) {
                statusSubcription = callService(REGISTER_SERVER_STATUS_URI, {
                    "subscribe": true, serviceName: GATEWAY_SERVICE
                }, function (response) {
                    if (response && response.connected) {
                        getChannelRequest = callService(CREATE_APPCHANNEL_URI, {}, function (response) {
                            if (response.socketUrl) {
                                callback(new WebSocket(response.socketUrl));
                            }
                        });
                    }
                });
            };
        } else {
            // Look for "webOSAppChannelSocketUrl" parameter
            if (window.location.search) {
                console.log("found params: ", window.location.search);
                /**
                 * @author Konstantin Kitmanov
                 * May be freely distributed under the MIT license.
                 */
                var parse = function (str) {
                    var chunks = str.split('&'),
                        dict = {},
                        chunk;
                    for (var i = 0, len = chunks.length; i < len; i++) {
                        chunk = chunks[i].split('=');
                        dict[chunk[0]] = decodeURIComponent(chunk[1]);
                    }

                    return dict;
                };

                var parsed = parse(window.location.search.substr(1));

                if (parsed.webOSAppChannelSocketUrl) {
                    console.log("found websocket URL: ", parsed.webOSAppChannelSocketUrl);

                    this.getAppChannelWebSocket = function (callback) {
                        console.log("opened websocket connection")
                        callback(new WebSocket(parsed.webOSAppChannelSocketUrl));
                    };
                }
            }
        }
    },

    getAppChannelWebSocket: function (callback) {
        console.error("app channel socket not supported");
    },

    sendMessage: function(message) {
        var messageData = {
            type: "p2p",
            payload: message
        };

        this._send(messageData);
    },

    _send: function (message) {
        if (this.ws && message) {
            console.log("sending message: ", message);
            this.ws.send(JSON.stringify(message));
        }
    },

    _destroy: function () {
        if (this.ws) {
            this.ws = null;
        }
    },

    start: function () {
        stopRequested = false;
        var self = this;

        this.getAppChannelWebSocket(function (socket) {
            if (stopRequested) {
                self.stop();
                return;
            }

            self.ws = socket;

            self.ws.onopen = function (event) {
                self.connectManager.on('videoStatusUpdate', self._handleVideoStatusUpdate, self);
                self.emit('ready', event);
            };

            self.ws.onmessage = function (event) {
                var message = JSON.parse(event.data);
                var payload = message.payload;

                console.log("got message: " + JSON.stringify(message));

                if (message.type === "p2p") {
                    if (typeof payload !== 'undefined') {
                        self._processP2PMessage(message);
                    }
                } else if (message.type === "p2p.join-request") {
                    self._send({
                        type: "p2p.join-response",
                        payload: {
                            allowJoin: true,
                            requestId: payload.requestId
                        }
                    });
                } else if (message.type === "p2p.join") {
                    self.emit("join", {client: payload && payload.client});
                } else if (message.type === "p2p.depart") {
                    self.emit("depart", {client: payload && payload.client});
                }
            };

            self.ws.onclose = function () {
                self.ws = null;
            };
        });
    },

    stop: function () {
        this.stopRequested = true;

        if (this.ws) {
            this.connectManager.off('videoStatusUpdate');
            this.ws.close();
        }

        this._destroy();
    },

    _handleVideoStatusUpdate: function() {
        var playState = this.connectManager.videoElementStatus;
        var currentTime = 0;
        var duration = 0;

        if (this.connectManager.videoElement)
        {
            currentTime = this.connectManager.videoElement.currentTime;

            if (this.connectManager.videoElement.duration != NaN)
                duration = this.connectManager.videoElement.duration
        }

        if (playState == null)
            return;

        this.sendMessage({
                    contentType: 'mediaEvent',
                    mediaEvent: {
                        type: 'playState',
                        playState: playState,
                        position: currentTime,
                        duration: duration
                    }
                });
    },

    _processP2PMessage: function(message) {
        console.log('processing message payload ' + JSON.stringify(message.payload));
        var contentType = message.payload.contentType;
        var from = message.from;

        if (contentType === 'mediaCommand')
        {
            console.log('processing mediaCommand ' + JSON.stringify(message.payload.mediaCommand) + ' of type ' + message.payload.mediaCommand.type);

            var commandType = message.payload.mediaCommand.type;
            var requestId = message.payload.mediaCommand.requestId;
            var videoElement = this.connectManager.videoElement;

            if (commandType === 'seek')
            {
                var position = message.payload.mediaCommand.position;

                if (position)
                {
                    videoElement.currentTime = position;
                    this._handleVideoStatusUpdate();
                }
            }
            else if (commandType === 'getPosition')
            {
                this._send({
                            type: 'p2p',
                            to: from,
                            payload: {
                                contentType: 'mediaCommandResponse',
                                mediaCommandResponse: {
                                    type: commandType,
                                    position: videoElement.currentTime,
                                    requestId:requestId
                                }
                            }
                        });
            } else if (commandType === 'getDuration')
            {
                this._send({
                            type: 'p2p',
                            to: from,
                            payload: {
                                contentType: 'mediaCommandResponse',
                                mediaCommandResponse: {
                                    type: commandType,
                                    duration: videoElement.duration,
                                    requestId:requestId
                                }
                            }
                        });
            } else if (commandType === 'getPlayState')
            {

            }
        } else
        {
            this.emit('message', message.payload);
        }
    }
});
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var inherits = require('util').inherits;

var TChannelPeerState = require('./peer_state');

module.exports.TChannelPeerHealthyState = TChannelPeerHealthyState;

function TChannelPeerHealthyState(channel, peer) {
    if (!(this instanceof TChannelPeerHealthyState)) {
        return new TChannelPeerHealthyState(channel, peer);
    }
    var self = this;
    TChannelPeerState.call(self, channel, peer);
}

inherits(TChannelPeerHealthyState, TChannelPeerState);

TChannelPeerHealthyState.prototype.name = 'healthy';

TChannelPeerHealthyState.prototype.shouldRequest = function shouldRequest(/* req, options */) {
    var self = this;
    return 0.2 + self.channel.random() * 0.8;
};
<commit_msg>Take away PeerState constructor branching<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var inherits = require('util').inherits;

var TChannelPeerState = require('./peer_state');

module.exports.TChannelPeerHealthyState = TChannelPeerHealthyState;

function TChannelPeerHealthyState(channel, peer) {
    var self = this;
    TChannelPeerState.call(self, channel, peer);
}

inherits(TChannelPeerHealthyState, TChannelPeerState);

TChannelPeerHealthyState.prototype.name = 'healthy';

TChannelPeerHealthyState.prototype.shouldRequest = function shouldRequest(/* req, options */) {
    var self = this;
    return 0.2 + self.channel.random() * 0.8;
};
<|endoftext|>


<commit_before>/**
 * Grafana-Zabbix
 * Zabbix plugin for Grafana.
 * http://github.com/alexanderzobnin/grafana-zabbix
 *
 * Trigger panel.
 * This feature sponsored by CORE IT
 * http://www.coreit.fr
 *
 * Copyright 2015 Alexander Zobnin alexanderzobnin@gmail.com
 * Licensed under the Apache License, Version 2.0
 */

import _ from 'lodash';
import moment from 'moment';
import {MetricsPanelCtrl} from 'app/plugins/sdk';
import {triggerPanelEditor} from './editor';
import './css/panel_triggers.css!';

var defaultSeverity = [
  { priority: 0, severity: 'Not classified',  color: '#B7DBAB', show: true },
  { priority: 1, severity: 'Information',     color: '#82B5D8', show: true },
  { priority: 2, severity: 'Warning',         color: '#E5AC0E', show: true },
  { priority: 3, severity: 'Average',         color: '#C15C17', show: true },
  { priority: 4, severity: 'High',            color: '#BF1B00', show: true },
  { priority: 5, severity: 'Disaster',        color: '#890F02', show: true }
];

var panelDefaults = {
  datasource: null,
  triggers: {
    group: {filter: ""},
    host: {filter: ""},
    application: {filter: ""},
    trigger: {filter: ""}
  },
  hostField: true,
  statusField: false,
  severityField: false,
  lastChangeField: true,
  ageField: true,
  infoField: true,
  limit: 10,
  showTriggers: 'all triggers',
  sortTriggersBy: { text: 'last change', value: 'lastchange' },
  showEvents: { text: 'Problems', value: '1' },
  triggerSeverity: defaultSeverity,
  okEventColor: 'rgba(0, 245, 153, 0.45)',
};

var triggerStatusMap = {
  '0': 'OK',
  '1': 'Problem'
};

var defaultTimeFormat = "DD MMM YYYY HH:mm:ss";

class TriggerPanelCtrl extends MetricsPanelCtrl {

  /** @ngInject */
  constructor($scope, $injector, $q, $element, datasourceSrv, templateSrv) {
    super($scope, $injector);
    this.datasourceSrv = datasourceSrv;
    this.templateSrv = templateSrv;
    this.triggerStatusMap = triggerStatusMap;
    this.defaultTimeFormat = defaultTimeFormat;

    // Load panel defaults
    _.defaults(this.panel, panelDefaults);

    this.triggerList = [];
    this.refreshData();
  }

  /**
   * Override onInitMetricsPanelEditMode() method from MetricsPanelCtrl.
   * We don't need metric editor from Metrics Panel.
   */
  onInitMetricsPanelEditMode() {
    this.addEditorTab('Options', triggerPanelEditor, 2);
  }

  onMetricsPanelRefresh() {
    this.refreshData();
  }

  refreshData() {
    var self = this;

    // Load datasource
    return this.datasourceSrv.get(this.panel.datasource).then(datasource => {
      var zabbix = datasource.zabbixAPI;
      var queryProcessor = datasource.queryProcessor;
      var showEvents = self.panel.showEvents.value;
      var triggerFilter = self.panel.triggers;
      var groupFilter = self.templateSrv.replace(triggerFilter.group.filter);
      var hostFilter = self.templateSrv.replace(triggerFilter.host.filter);
      var appFilter = self.templateSrv.replace(triggerFilter.application.filter);
      var buildQuery = queryProcessor.buildTriggerQuery(groupFilter, hostFilter, appFilter);
      return buildQuery.then(query => {
        return zabbix.getTriggers(query.groupids,
                                  query.hostids,
                                  query.applicationids,
                                  showEvents)
          .then(triggers => {
            return _.map(triggers, trigger => {
              var triggerObj = trigger;

              // Format last change and age
              trigger.lastchangeUnix = Number(trigger.lastchange);
              var timestamp = moment.unix(trigger.lastchangeUnix);
              if (self.panel.customLastChangeFormat) {
                // User defined format
                triggerObj.lastchange = timestamp.format(self.panel.lastChangeFormat);
              } else {
                triggerObj.lastchange = timestamp.format(self.defaultTimeFormat);
              }
              triggerObj.age = timestamp.fromNow(true);

              // Set host that the trigger belongs
              if (trigger.hosts.length) {
                triggerObj.host = trigger.hosts[0].name;
              }

              // Set color
              if (trigger.value === '1') {
                triggerObj.color = self.panel.triggerSeverity[trigger.priority].color;
              } else {
                triggerObj.color = self.panel.okEventColor;
              }

              triggerObj.severity = self.panel.triggerSeverity[trigger.priority].severity;
              return triggerObj;
            });
          })
          .then(triggerList => {

            // Request acknowledges for trigger
            var eventids = _.map(triggerList, trigger => {
              return trigger.lastEvent.eventid;
            });

            return zabbix.getAcknowledges(eventids)
              .then(events => {

                // Map events to triggers
                _.each(triggerList, trigger => {
                  var event = _.find(events, event => {
                    return event.eventid === trigger.lastEvent.eventid;
                  });

                  if (event) {
                    trigger.acknowledges = _.map(event.acknowledges, ack => {
                      var time = new Date(+ack.clock * 1000);
                      ack.time = time.toLocaleString();
                      ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')';
                      return ack;
                    });
                  }
                });

                // Filter triggers by description
                var triggerFilter = self.panel.triggers.trigger.filter;
                if (triggerFilter) {
                  triggerList = filterTriggers(triggerList, triggerFilter);
                }

                // Filter acknowledged triggers
                if (self.panel.showTriggers === 'unacknowledged') {
                  triggerList = _.filter(triggerList, trigger => {
                    return !trigger.acknowledges;
                  });
                } else if (self.panel.showTriggers === 'acknowledged') {
                  triggerList = _.filter(triggerList, 'acknowledges');
                } else {
                  triggerList = triggerList;
                }

                // Filter triggers by severity
                triggerList = _.filter(triggerList, trigger => {
                  return self.panel.triggerSeverity[trigger.priority].show;
                });

                // Sort triggers
                if (self.panel.sortTriggersBy.value === 'priority') {
                  triggerList = _.sortBy(triggerList, 'priority').reverse();
                } else {
                  triggerList = _.sortBy(triggerList, 'lastchangeUnix').reverse();
                }

                // Limit triggers number
                self.triggerList  = _.first(triggerList, self.panel.limit);

                self.renderingCompleted();
              });
          });
      });
    });
  }

  switchComment(trigger) {
    trigger.showComment = !trigger.showComment;
  }

  switchAcknowledges(trigger) {
    trigger.showAcknowledges = !trigger.showAcknowledges;
  }
}

TriggerPanelCtrl.templateUrl = 'panel-triggers/module.html';

function filterTriggers(triggers, triggerFilter) {
  if (isRegex(triggerFilter)) {
    return _.filter(triggers, function(trigger) {
      return buildRegex(triggerFilter).test(trigger.description);
    });
  } else {
    return _.filter(triggers, function(trigger) {
      return trigger.description === triggerFilter;
    });
  }
}

function isRegex(str) {
  // Pattern for testing regex
  var regexPattern = /^\/(.*)\/([gmi]*)$/m;
  return regexPattern.test(str);
}

function buildRegex(str) {
  var regexPattern = /^\/(.*)\/([gmi]*)$/m;
  var matches = str.match(regexPattern);
  var pattern = matches[1];
  var flags = matches[2] !== "" ? matches[2] : undefined;
  return new RegExp(pattern, flags);
}

export {
  TriggerPanelCtrl,
  TriggerPanelCtrl as PanelCtrl
};
<commit_msg>Small panel-triggers/module.js refactor.<commit_after>/**
 * Grafana-Zabbix
 * Zabbix plugin for Grafana.
 * http://github.com/alexanderzobnin/grafana-zabbix
 *
 * Trigger panel.
 * This feature sponsored by CORE IT
 * http://www.coreit.fr
 *
 * Copyright 2015 Alexander Zobnin alexanderzobnin@gmail.com
 * Licensed under the Apache License, Version 2.0
 */

import _ from 'lodash';
import moment from 'moment';
import {MetricsPanelCtrl} from 'app/plugins/sdk';
import {triggerPanelEditor} from './editor';
import './css/panel_triggers.css!';

var defaultSeverity = [
  { priority: 0, severity: 'Not classified',  color: '#B7DBAB', show: true },
  { priority: 1, severity: 'Information',     color: '#82B5D8', show: true },
  { priority: 2, severity: 'Warning',         color: '#E5AC0E', show: true },
  { priority: 3, severity: 'Average',         color: '#C15C17', show: true },
  { priority: 4, severity: 'High',            color: '#BF1B00', show: true },
  { priority: 5, severity: 'Disaster',        color: '#890F02', show: true }
];

var panelDefaults = {
  datasource: null,
  triggers: {
    group: {filter: ""},
    host: {filter: ""},
    application: {filter: ""},
    trigger: {filter: ""}
  },
  hostField: true,
  statusField: false,
  severityField: false,
  lastChangeField: true,
  ageField: true,
  infoField: true,
  limit: 10,
  showTriggers: 'all triggers',
  sortTriggersBy: { text: 'last change', value: 'lastchange' },
  showEvents: { text: 'Problems', value: '1' },
  triggerSeverity: defaultSeverity,
  okEventColor: 'rgba(0, 245, 153, 0.45)',
};

var triggerStatusMap = {
  '0': 'OK',
  '1': 'Problem'
};

var defaultTimeFormat = "DD MMM YYYY HH:mm:ss";

class TriggerPanelCtrl extends MetricsPanelCtrl {

  /** @ngInject */
  constructor($scope, $injector, $q, $element, datasourceSrv, templateSrv) {
    super($scope, $injector);
    this.datasourceSrv = datasourceSrv;
    this.templateSrv = templateSrv;
    this.triggerStatusMap = triggerStatusMap;
    this.defaultTimeFormat = defaultTimeFormat;

    // Load panel defaults
    _.defaults(this.panel, panelDefaults);

    this.triggerList = [];
    this.refreshData();
  }

  /**
   * Override onInitMetricsPanelEditMode() method from MetricsPanelCtrl.
   * We don't need metric editor from Metrics Panel.
   */
  onInitMetricsPanelEditMode() {
    this.addEditorTab('Options', triggerPanelEditor, 2);
  }

  onMetricsPanelRefresh() {
    this.refreshData();
  }

  refreshData() {
    var self = this;

    // Load datasource
    return this.datasourceSrv.get(this.panel.datasource).then(datasource => {
      var zabbix = datasource.zabbixAPI;
      var queryProcessor = datasource.queryProcessor;
      var showEvents = self.panel.showEvents.value;
      var triggerFilter = self.panel.triggers;

      // Replace template variables
      var groupFilter = self.templateSrv.replace(triggerFilter.group.filter);
      var hostFilter = self.templateSrv.replace(triggerFilter.host.filter);
      var appFilter = self.templateSrv.replace(triggerFilter.application.filter);

      var buildQuery = queryProcessor.buildTriggerQuery(groupFilter, hostFilter, appFilter);
      return buildQuery.then(query => {
        return zabbix.getTriggers(query.groupids,
                                  query.hostids,
                                  query.applicationids,
                                  showEvents)
          .then(triggers => {
            return _.map(triggers, trigger => {
              var triggerObj = trigger;

              // Format last change and age
              trigger.lastchangeUnix = Number(trigger.lastchange);
              var timestamp = moment.unix(trigger.lastchangeUnix);
              if (self.panel.customLastChangeFormat) {
                // User defined format
                triggerObj.lastchange = timestamp.format(self.panel.lastChangeFormat);
              } else {
                triggerObj.lastchange = timestamp.format(self.defaultTimeFormat);
              }
              triggerObj.age = timestamp.fromNow(true);

              // Set host that the trigger belongs
              if (trigger.hosts.length) {
                triggerObj.host = trigger.hosts[0].name;
              }

              // Set color
              if (trigger.value === '1') {
                // Problem state
                triggerObj.color = self.panel.triggerSeverity[trigger.priority].color;
              } else {
                // OK state
                triggerObj.color = self.panel.okEventColor;
              }

              triggerObj.severity = self.panel.triggerSeverity[trigger.priority].severity;
              return triggerObj;
            });
          })
          .then(triggerList => {

            // Request acknowledges for trigger
            var eventids = _.map(triggerList, trigger => {
              return trigger.lastEvent.eventid;
            });

            return zabbix.getAcknowledges(eventids)
              .then(events => {

                // Map events to triggers
                _.each(triggerList, trigger => {
                  var event = _.find(events, event => {
                    return event.eventid === trigger.lastEvent.eventid;
                  });

                  if (event) {
                    trigger.acknowledges = _.map(event.acknowledges, ack => {
                      var time = new Date(+ack.clock * 1000);
                      ack.time = time.toLocaleString();
                      ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')';
                      return ack;
                    });
                  }
                });

                // Filter triggers by description
                var triggerFilter = self.panel.triggers.trigger.filter;
                if (triggerFilter) {
                  triggerList = filterTriggers(triggerList, triggerFilter);
                }

                // Filter acknowledged triggers
                if (self.panel.showTriggers === 'unacknowledged') {
                  triggerList = _.filter(triggerList, trigger => {
                    return !trigger.acknowledges;
                  });
                } else if (self.panel.showTriggers === 'acknowledged') {
                  triggerList = _.filter(triggerList, 'acknowledges');
                } else {
                  triggerList = triggerList;
                }

                // Filter triggers by severity
                triggerList = _.filter(triggerList, trigger => {
                  return self.panel.triggerSeverity[trigger.priority].show;
                });

                // Sort triggers
                if (self.panel.sortTriggersBy.value === 'priority') {
                  triggerList = _.sortBy(triggerList, 'priority').reverse();
                } else {
                  triggerList = _.sortBy(triggerList, 'lastchangeUnix').reverse();
                }

                // Limit triggers number
                self.triggerList  = _.first(triggerList, self.panel.limit);

                self.renderingCompleted();
              });
          });
      });
    });
  }

  switchComment(trigger) {
    trigger.showComment = !trigger.showComment;
  }

  switchAcknowledges(trigger) {
    trigger.showAcknowledges = !trigger.showAcknowledges;
  }
}

TriggerPanelCtrl.templateUrl = 'panel-triggers/module.html';

function filterTriggers(triggers, triggerFilter) {
  if (isRegex(triggerFilter)) {
    return _.filter(triggers, function(trigger) {
      return buildRegex(triggerFilter).test(trigger.description);
    });
  } else {
    return _.filter(triggers, function(trigger) {
      return trigger.description === triggerFilter;
    });
  }
}

function isRegex(str) {
  // Pattern for testing regex
  var regexPattern = /^\/(.*)\/([gmi]*)$/m;
  return regexPattern.test(str);
}

function buildRegex(str) {
  var regexPattern = /^\/(.*)\/([gmi]*)$/m;
  var matches = str.match(regexPattern);
  var pattern = matches[1];
  var flags = matches[2] !== "" ? matches[2] : undefined;
  return new RegExp(pattern, flags);
}

export {
  TriggerPanelCtrl,
  TriggerPanelCtrl as PanelCtrl
};
<|endoftext|>