<commit_before>/* ng-YouTubeAPI.js v1.1.0
 * https://github.com/patrickmarabeas/ng-YouTubeAPI.js
 *
 * Copyright 2013, Patrick Marabeas http://pulse-dev.com
 * Released under the MIT license
 * http://opensource.org/licenses/mit-license.php
 *
 * Date: 03/12/2013
 */

var module = angular.module( 'ngYouTubeAPI', [] );

module.run( [ '$window', '$document', '$rootScope', function( $window, $document, $rootScope ) {
	var tag = $document[0].createElement( 'script' );
	tag.src = "https://www.youtube.com/iframe_api";
	var firstScriptTag = $document[0].getElementsByTagName( 'script' )[0];
	firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );

//	$window.onYouTubePlayerAPIReady = function() {
//		$rootScope.$broadcast( 'onYouTubePlayerAPIReady' );
//	};

}]);

module.service( 'constructor', [ function() {
	return {
		players: [],
		construct: function( id, vid, vars ) {
			this.players[id] = new YT.Player( id, {
				height: '390',
				width: '640',
				videoId: vid,
//				playerVars: {
//
//				},
				playerVars: vars,
				events: {
					'onReady': function() {
					},
					'onStateChange': function() {
					}
				},
				title: 'sup'
			});
		}
	}
}]);

module.directive( 'ytPlayer', [ 'constructor', function( constructor ) {
	return {
		restrict: 'A',
		scope: {
			player: '@ytPlayer',
			vid: '@ytVid'
		},
		controller: [ '$scope', '$http', function( $scope, $http ) {

			$scope.$watch( 'vid', function() {

				$http( {
					method: 'JSONP',
					url: 'http://gdata.youtube.com/feeds/api/videos/' + $scope.vid + '?v=2&alt=json&callback=JSON_CALLBACK'
				}).success(function( data, status ) {

						/* template usage */
						$scope.title = data.entry.title.$t;
						$scope.description = data.entry.media$group.media$description.$t;
						$scope.author = data.entry.author[0].name.$t;
						$scope.viewCount = data.entry.yt$statistics.viewCount;
						$scope.published = data.entry.published.$t;
						$scope.likes = data.entry.yt$rating.numLikes;
						$scope.dislikes = data.entry.yt$rating.numDislikes;

						/* spy usage */
						constructor.players[$scope.player].title = data.entry.title.$t;
						constructor.players[$scope.player].description = data.entry.media$group.media$description.$t;
						constructor.players[$scope.player].author = data.entry.author[0].name.$t;
						constructor.players[$scope.player].viewCount = data.entry.yt$statistics.viewCount;
						constructor.players[$scope.player].published = data.entry.published.$t;
						constructor.players[$scope.player].likes = data.entry.yt$rating.numLikes;
						constructor.players[$scope.player].dislikes = data.entry.yt$rating.numDislikes;

					}).error( function( data, status ) {

					});

			});

		}],
		template: '
' +
			'

' +
			'
{{author}}
' +
			'
{{title}}
' +
			'
{{description}}
' +

			'
',
		replace: true,
		link: function( scope, element, attrs ) {

//			scope.$on( 'onYouTubePlayerAPIReady', function( events, args ) {
			window.onYouTubePlayerAPIReady = function() {
				var vars = eval("("+attrs.ytPlayervars+")");
				constructor.construct( attrs.ytPlayer, attrs.ytVid, vars );

				console.log(vars);
				console.log(constructor.players['mainPlayer']);
			};
//			});

			scope.$on( attrs.ytPlayer, function( events, args ) {

				constructor.players[events.name].loadVideoById( args.vid );

				scope.$apply( function() {
					scope.vid = args.vid;

				});

			});

		}
	}
}]);

module.directive( 'ytPlayto', [ '$rootScope', function( $rootScope ) {
	return {
		restrict: 'A',
		scope: {
			playto: '@ytPlayto',
			vid: '@ytVid'
		},
		controller: [ '$scope', '$attrs', '$http', function( $scope, $attrs, $http ) {

			$http( {
				method: 'JSONP',
				url: 'http://gdata.youtube.com/feeds/api/videos/' + $scope.vid + '?v=2&alt=json&callback=JSON_CALLBACK'
			}).success(function( data, status ) {

					console.log('success');

					$scope.title = data.entry.title.$t;
					$scope.description = data.entry.media$group.media$description.$t;
					$scope.author = data.entry.author[0].name.$t;
					$scope.viewCount = data.entry.yt$statistics.viewCount;
					$scope.published = data.entry.published.$t;
					$scope.likes = data.entry.yt$rating.numLikes;
					$scope.dislikes = data.entry.yt$rating.numDislikes;

				}).error( function() {

				})

		}],
		template: '<button>{{ title }}, {{ viewCount }}</button>',
		replace: true,
		link: function( scope, element, attrs ) {

			angular.element( element ).bind( "click", function() {

				$rootScope.$broadcast( scope.playto, {
					'vid': scope.vid
				});

			});

		}
	}
}]);

module.directive( 'ytSpy', [ 'constructor', function( constructor ) {
	return {
		scope: {
			spy: '@ytSpy',
			value: '@ytValue'
		},
		template: '{{data}}',
		replace: true,
		link: function( scope, element, attrs ) {

			angular.element( document ).ready( function() {

				scope.player = constructor.players[scope.spy];
				scope.data = scope.player[scope.value];
				scope.$apply();

				scope.$watch( 'player.' + scope.value, function() {
					scope.data = constructor.players[scope.spy][scope.value];
				});

			});

		}
	}
}]);<commit_msg>Broadcast had more use than previously thought...<commit_after>/* ng-YouTubeAPI.js v1.1.0
 * https://github.com/patrickmarabeas/ng-YouTubeAPI.js
 *
 * Copyright 2013, Patrick Marabeas http://pulse-dev.com
 * Released under the MIT license
 * http://opensource.org/licenses/mit-license.php
 *
 * Date: 03/12/2013
 */

var module = angular.module( 'ngYouTubeAPI', [] );

module.run( [ '$window', '$document', '$rootScope', function( $window, $document, $rootScope ) {
	var tag = $document[0].createElement( 'script' );
	tag.src = "https://www.youtube.com/iframe_api";
	var firstScriptTag = $document[0].getElementsByTagName( 'script' )[0];
	firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );

	$window.onYouTubePlayerAPIReady = function() {
		$rootScope.$broadcast( 'onYouTubePlayerAPIReady' );
	};

}]);

module.service( 'constructor', [ function() {
	return {
		players: [],
		construct: function( id, vid, vars ) {
			this.players[id] = new YT.Player( id, {
				height: '390',
				width: '640',
				videoId: vid,
//				playerVars: {
//
//				},
				playerVars: vars,
				events: {
					'onReady': function() {
					},
					'onStateChange': function() {
					}
				},
				title: 'sup'
			});
		}
	}
}]);

module.directive( 'ytPlayer', [ 'constructor', function( constructor ) {
	return {
		restrict: 'A',
		scope: {
			player: '@ytPlayer',
			vid: '@ytVid'
		},
		controller: [ '$scope', '$http', function( $scope, $http ) {

			$scope.$watch( 'vid', function() {

				$http( {
					method: 'JSONP',
					url: 'http://gdata.youtube.com/feeds/api/videos/' + $scope.vid + '?v=2&alt=json&callback=JSON_CALLBACK'
				}).success(function( data, status ) {

						/* template usage */
						$scope.title = data.entry.title.$t;
						$scope.description = data.entry.media$group.media$description.$t;
						$scope.author = data.entry.author[0].name.$t;
						$scope.viewCount = data.entry.yt$statistics.viewCount;
						$scope.published = data.entry.published.$t;
						$scope.likes = data.entry.yt$rating.numLikes;
						$scope.dislikes = data.entry.yt$rating.numDislikes;

						/* spy usage */
						constructor.players[$scope.player].title = data.entry.title.$t;
						constructor.players[$scope.player].description = data.entry.media$group.media$description.$t;
						constructor.players[$scope.player].author = data.entry.author[0].name.$t;
						constructor.players[$scope.player].viewCount = data.entry.yt$statistics.viewCount;
						constructor.players[$scope.player].published = data.entry.published.$t;
						constructor.players[$scope.player].likes = data.entry.yt$rating.numLikes;
						constructor.players[$scope.player].dislikes = data.entry.yt$rating.numDislikes;

					}).error( function( data, status ) {

					});

			});

		}],
		template: '
' +
			'

' +
			'
{{author}}
' +
			'
{{title}}
' +
			'
{{description}}
' +

			'
',
		replace: true,
		link: function( scope, element, attrs ) {

			scope.$on( 'onYouTubePlayerAPIReady', function( events, args ) {
//			window.onYouTubePlayerAPIReady = function() {
				var vars = eval("("+attrs.ytPlayervars+")");
				constructor.construct( attrs.ytPlayer, attrs.ytVid, vars );

				console.log(vars);
				console.log(constructor.players['mainPlayer']);
//			};
			});

			scope.$on( attrs.ytPlayer, function( events, args ) {

				constructor.players[events.name].loadVideoById( args.vid );

				scope.$apply( function() {
					scope.vid = args.vid;

				});

			});

		}
	}
}]);

module.directive( 'ytPlayto', [ '$rootScope', function( $rootScope ) {
	return {
		restrict: 'A',
		scope: {
			playto: '@ytPlayto',
			vid: '@ytVid'
		},
		controller: [ '$scope', '$attrs', '$http', function( $scope, $attrs, $http ) {

			$http( {
				method: 'JSONP',
				url: 'http://gdata.youtube.com/feeds/api/videos/' + $scope.vid + '?v=2&alt=json&callback=JSON_CALLBACK'
			}).success(function( data, status ) {

					console.log('success');

					$scope.title = data.entry.title.$t;
					$scope.description = data.entry.media$group.media$description.$t;
					$scope.author = data.entry.author[0].name.$t;
					$scope.viewCount = data.entry.yt$statistics.viewCount;
					$scope.published = data.entry.published.$t;
					$scope.likes = data.entry.yt$rating.numLikes;
					$scope.dislikes = data.entry.yt$rating.numDislikes;

				}).error( function() {

				})

		}],
		template: '<button>{{ title }}, {{ viewCount }}</button>',
		replace: true,
		link: function( scope, element, attrs ) {

			angular.element( element ).bind( "click", function() {

				$rootScope.$broadcast( scope.playto, {
					'vid': scope.vid
				});

			});

		}
	}
}]);

module.directive( 'ytSpy', [ 'constructor', function( constructor ) {
	return {
		scope: {
			spy: '@ytSpy',
			value: '@ytValue'
		},
		template: '{{data}}',
		replace: true,
		link: function( scope, element, attrs ) {

			angular.element( document ).ready( function() {

				scope.player = constructor.players[scope.spy];
				scope.data = scope.player[scope.value];
				scope.$apply();

				scope.$watch( 'player.' + scope.value, function() {
					scope.data = constructor.players[scope.spy][scope.value];
				});

			});

		}
	}
}]);<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013 Akkroo Solutions Ltd
 *
 */

(function (root, factory) {
    if (typeof exports === 'object') {
        // CommonJS
        factory(exports, require('underscore'));
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['exports', 'underscore'], factory);
    } else {
        // Browser globals
        factory((root.JSMongoQuery = {}), root._);
    }
}(this, function (exports, _, undefined) {

	// PHP shims.  Most are just renamed underscore functions.
	var count = _.size,
		array_intersect = _.intersection,
		array_unique = _.uniq,

		empty = _.isEmpty,
		is_callable = _.isFunction,
		is_array = _.isArray,
		is_bool = _.isBoolean,
		is_string = _.isString,
		is_int = function(n) { return _.isNumber(n) && n % 1 == 0; },
		in_array = function(needle, haystack) { return _.contains(haystack, needle); },
		array_diff = function(a1, a2){ return _.difference(a2, a1); },
		array_slice = function(a, offset, length) { return a.slice(offset, length === undefined ? undefined : offset + length); },
		substr = function(string, start, length) { return string.substr(start, length); },
		explode = function(sep, e) { return e.split(sep); },
		implode = function(sep, e) { return e.join(sep); },
		call_user_func = function(callback) {
			return callback.apply(null, Array.prototype.slice.call(arguments, 1));
		},
		array_merge = function(a1,a2) { return a1.concat(a2); };
	// recreate
	var Exception = function(e, code, previous) {
		this.exception = e;
		this.code = code;
		this.previous = previous;
		this.toString = function(){return e;}
	};

	JSMongoQuery = {
		/**
		 * Execute a Mongo query on a document
		 *
		 * @param $query		A boolean value or an array defining a query
		 * @param $document	The document to query
		 * @param $options	Any options:
		 *	'unknownOperatorCallback' - a callback to be called if an operator can't be found.  The function definition is function(operator, operatorValue, field, document). return true or false.
		 * @return boolean
		 * @throws Exception
		 */
		executeQuery: function executeQuery($query, $document, $options) {
			$options = $options || {};
			if($options.debug && $options.logger) {
				$options.logger.debug('executeQuery called', {query : $query, document : $document, options : $options});
			}

			if(!_.isObject($query)) return !!$query;
			if(!_.isObject($document)) throw new Exception("document should be an object");

			return JSMongoQuery._executeQuery($query, $document, $options);
		},

		/**
		 * Internal execute query
		 *
		 * This expects an array from the query and has an additional logical operator (for the root query object the logical operator is always $and so this is not required)
		 *
		 * @param $query			The query array
		 * @param $document			The document array to query
		 * @param $options			Options array
		 * @param $logicalOperator	Operator string
		 * @return boolean
		 * @throws Exception
		 */
		_executeQuery: function _executeQuery($query, $document, $options, $logicalOperator) {
			if(!$logicalOperator) {
				$logicalOperator = '$and';
			}
			if($logicalOperator !== '$and' && !count($query))
				throw new Exception($logicalOperator+' requires nonempty array');
			if($options.debug && $options.logger) {
				$options.logger.debug('_executeQuery called', {query:$query, document:$document, options:$options, logicalOperator:$logicalOperator});
			}
			var $pass, result = null;
			// use for to enable returning inside loop
			_.every($query, function($q, $k) {
				$pass = true;
				if(is_string($k) && substr($k, 0, 1) === '$') {
					// key is an operator at this level, except $not, which can be at any level
					if($k === '$not')
						$pass = !JSMongoQuery._executeQuery($q, $document, $options);
					else
						$pass = JSMongoQuery._executeQuery($q, $document, $options, $k);
				} else if($logicalOperator === '$and') { // special case for $and
					if(is_int($k)) { // $q is an object of query objects
						$pass = JSMongoQuery._executeQuery($q, $document, $options);
					} else if(_.isObject($q)) { // query is array, run all queries on field.  All queries must match. e.g { 'age': { $gt: 24, $lt: 52 } }
						$pass = JSMongoQuery._executeQueryOnElement($q, $k, $document, $options);
					} else {
						// key value means equality
						$pass = JSMongoQuery._executeOperatorOnElement('$e', $q, $k, $document, $options);
					}
				} else { // $q is array of query objects e.g '$or' => [{'fullName' => 'Nick'}]
					$pass = JSMongoQuery._executeQuery($q, $document, $options, '$and');
				}

				if ($options.debug && $options.logger) {
					$options.logger.debug('_executeQuery executed query part',
					{query : $q, result: $pass, document : $document, logicalOperator : $logicalOperator});
				}

				switch($logicalOperator) {
					case '$and': // if any fail, query fails
						if(!$pass) { result = false; return false; }
						break;
					case '$or': // if one succeeds, query succeeds
						if($pass) { result = true; return false; }
						break;
					case '$nor': // if one succeeds, query fails
						if($pass) { result = false; return false; }
						break;
					default:
						if ($options.logger) {
							$options.logger.warning('_executeQuery could not find logical operator',
							{query : $query, document : $document, logicalOperator : $logicalOperator});
						}
						result = false;
						return false;
				}
				return true;
			});

			// Will be set if we exited the loop early, so we should use it as the
			// return value for the function.
			if (result !== null) {
				return result;
			}

			switch($logicalOperator) {
				case '$and': // all succeeded, query succeeds
					return true;
				case '$or': // all failed, query fails
					return false;
				case '$nor': // all failed, query succeeded
					return true;
				default:
					if($options.logger) {
						$options.logger.warning('_executeQuery could not find logical operator', {query : $query, document : $document, logicalOperator : $logicalOperator});
					}
					return false;
			}
			throw new Exception('Reached end of _executeQuery without returning a value');
		},

		/**
		 * Execute a query object on an element
		 *
		 * @param $query
		 * @param $field
		 * @param $document
		 * @return boolean
		 * @throws Exception
		 */
		_executeQueryOnElement: function _executeQueryOnElement($query, $element, $document, $options) {
			if($options && $options.debug && $options.logger) {
				$options.logger.debug('_executeQueryOnElement called', {query : $query, element : $element, document : $document, options : $options});
			}
			// iterate through query operators
			return _.every($query, function($opVal, $op) {
				return JSMongoQuery._executeOperatorOnElement($op, $opVal, $element, $document, $options);
			});
		},

		/**
		* Check if an operator is equal to a value
		*
		* Equality includes direct equality, regular expression match, and checking if the operator value is one of the values in an array value
		*
		* @param $v
		* @param $operatorValue
		* @return boolean
		*/
		_isEqual: function isEqual($v, $operatorValue) {
			if(is_array($v)) return in_array($operatorValue, $v);
			var $matches;
			if(is_string($operatorValue) && ($matches = $operatorValue.match(/^\/(.*?)\/([a-z]*)$/i)))
				return (new RegExp($matches[1], $matches[2])).test($v);
			return _.isEqual($operatorValue, $v);
		},

		/**
		 * Execute a Mongo Operator on an element
		 *
		 * @param $operator		The operator to perform
		 * @param $operatorValue	The value to provide the operator
		 * @param $element		The target element.  Can be an object path eg price.shoes
		 * @param $document		The document in which to find the element
		 * @param $options		Options
		 * @return boolean				The result
		 * @throws Exception			Exceptions on invalid operators, invalid unknown operator callback, and invalid operator values
		 */
		_executeOperatorOnElement: function _executeOperatorOnElement($operator, $operatorValue, $element, $document, $options) {
			if($options && $options.debug && $options.logger) {
				$options.logger.debug('_executeOperatorOnElement called', {operatorValue : $operatorValue, element : $element, document : $document, options : $options});
			}

			if($operator === '$not') {
				return !JSMongoQuery._executeQueryOnElement($operatorValue, $element, $document, $options);
			}

			var $elementSpecifier = explode('.',$element),
				$v = $document,
				$exists = true,
				$result = null;
			_.every($elementSpecifier, function($es, $index) {
				if(empty($v)) {
					$exists = false;
					return false;
				}
				if(_.isArray($v)) {
					// value from document is an array, so we need to iterate through array and test the query on all elements of the array
					// if any elements match, then return true
					var $newSpecifier = implode('.', array_slice($elementSpecifier, $index));
					$result = _.some($v, function($item) {
						return JSMongoQuery._executeOperatorOnElement($operator, $operatorValue, $newSpecifier, $item, $options);
					});
					return false;
				}
				if($v[$es] === undefined) {
					$exists = false;
					return false;
				}
				$v = $v[$es];
				return true;
			});

			if($result !== null) {
				return $result;
			}

			switch($operator) {
				case '$all':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw '$all requires array';
					if(count($operatorValue) === 0) return false;
					if(!is_array($v)) {
						if(count($operatorValue) === 1)
							return $v === $operatorValue[0];
						return false;
					}
					return count(array_intersect($v, $operatorValue)) === count($operatorValue);
				case '$e': // non-mongo operator to allow same method call
					if(!$exists) return false;
					return JSMongoQuery._isEqual($v, $operatorValue);
				case '$in':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw new Exception('$in requires array');
					if(count($operatorValue) === 0) return false;
					if(is_array($v)) return count(array_diff($v, $operatorValue)) < count($operatorValue);
					return in_array($v, $operatorValue);
				case '$lt':		return $exists && $v < $operatorValue;
				case '$lte':	return $exists && $v <= $operatorValue;
				case '$gt':		return $exists && $v > $operatorValue;
				case '$gte':	return $exists && $v >= $operatorValue;
				case '$ne':		return (!$exists && $operatorValue !== null) || ($exists && !JSMongoQuery._isEqual($v, $operatorValue));
				case '$nin':
					if(!$exists) return true;
					if(!is_array($operatorValue)) throw new Exception('$nin requires array');
					if(count($operatorValue) === 0) return true;
					if(is_array($v)) {
						return !(count(array_diff($v, $operatorValue)) < count($operatorValue));
					}
					return !in_array($v, $operatorValue);

				case '$exists':	return ($operatorValue && $exists) || (!$operatorValue && !$exists);
				case '$mod':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw new Exception('$mod requires array');
					if(count($operatorValue) !== 2) throw new Exception('$mod requires two parameters in array: divisor and remainder');
					return $v % $operatorValue[0] === $operatorValue[1];

				default:
					if(empty($options['unknownOperatorCallback']) || !is_callable($options['unknownOperatorCallback']))
						throw new Exception('Operator '+$operator+' is unknown');

					var $res = call_user_func($options['unknownOperatorCallback'], $operator, $operatorValue, $element, $document);
					if($res === null)
						throw new Exception('Operator '+$operator+' is unknown');
					if(!is_bool($res))
						throw new Exception('Return value of unknownOperatorCallback must be boolean, actual value '.$res);
					return $res;
			}
			throw new Exception('Didn\'t return in switch');
		},

		/**
		 * Iterate through the query looking for field identifiers.  Append $append to the end of the identifier.
		 *
		 * @param $query
		 * @param $append
		 * @return	The new query
		 */
		appendFieldSpecifier: function appendFieldSpecifier($query, $append) {
			_.each($query, function($v, $k) {
				if(is_array($v))
					$query[$k] = JSMongoQuery.appendFieldSpecifier($v, $append);
				if(is_int($k) || $k[0] === '$') return;
				$query[$k+'.'+$append] = $v;
				delete $query[$k];
			});
			return $query;
		},

		/**
		 * Get the fields this query depends on
		 *
		 * @param $query	The query to analyse
		 * @return array	An array of fields this query depends on
		 */
		getDependentFields: function getDependentFields($query) {
			var $fields = [];
			_.each($query, function($v, $k) {
				if(is_array($v))
					$fields = array_merge($fields, JSMongoQuery.getDependentFields($v));
				if(is_int($k) || $k.charAt(0) === '$') return;
				$fields.push($k);
			});
			return array_unique($fields);
		}

	};

	_.extend(exports, JSMongoQuery);
}));<commit_msg>reformat<commit_after>/*
 * Copyright (C) 2013 Akkroo Solutions Ltd
 *
 */

(function (root, factory) {
    if (typeof exports === 'object') {
		// CommonJS
		factory(exports, require('underscore'));
	} else if (typeof define === 'function' && define.amd) {
		// AMD. Register as an anonymous module.
		define(['exports', 'underscore'], factory);
	} else {
		// Browser globals
		factory((root.JSMongoQuery = {}), root._);
	}
}(this, function (exports, _, undefined) {

	// PHP shims.  Most are just renamed underscore functions.
	var count = _.size,
		array_intersect = _.intersection,
		array_unique = _.uniq,

		empty = _.isEmpty,
		is_callable = _.isFunction,
		is_array = _.isArray,
		is_bool = _.isBoolean,
		is_string = _.isString,
		is_int = function(n) { return _.isNumber(n) && n % 1 == 0; },
		in_array = function(needle, haystack) { return _.contains(haystack, needle); },
		array_diff = function(a1, a2){ return _.difference(a2, a1); },
		array_slice = function(a, offset, length) { return a.slice(offset, length === undefined ? undefined : offset + length); },
		substr = function(string, start, length) { return string.substr(start, length); },
		explode = function(sep, e) { return e.split(sep); },
		implode = function(sep, e) { return e.join(sep); },
		call_user_func = function(callback) {
			return callback.apply(null, Array.prototype.slice.call(arguments, 1));
		},
		array_merge = function(a1,a2) { return a1.concat(a2); };
	// recreate
	var Exception = function(e, code, previous) {
		this.exception = e;
		this.code = code;
		this.previous = previous;
		this.toString = function(){return e;}
	};

	JSMongoQuery = {
		/**
		 * Execute a Mongo query on a document
		 *
		 * @param $query		A boolean value or an array defining a query
		 * @param $document	The document to query
		 * @param $options	Any options:
		 *	'unknownOperatorCallback' - a callback to be called if an operator can't be found.  The function definition is function(operator, operatorValue, field, document). return true or false.
		 * @return boolean
		 * @throws Exception
		 */
		executeQuery: function executeQuery($query, $document, $options) {
			$options = $options || {};
			if($options.debug && $options.logger) {
				$options.logger.debug('executeQuery called', {query : $query, document : $document, options : $options});
			}

			if(!_.isObject($query)) return !!$query;
			if(!_.isObject($document)) throw new Exception("document should be an object");

			return JSMongoQuery._executeQuery($query, $document, $options);
		},

		/**
		 * Internal execute query
		 *
		 * This expects an array from the query and has an additional logical operator (for the root query object the logical operator is always $and so this is not required)
		 *
		 * @param $query			The query array
		 * @param $document			The document array to query
		 * @param $options			Options array
		 * @param $logicalOperator	Operator string
		 * @return boolean
		 * @throws Exception
		 */
		_executeQuery: function _executeQuery($query, $document, $options, $logicalOperator) {
			if(!$logicalOperator) {
				$logicalOperator = '$and';
			}
			if($logicalOperator !== '$and' && !count($query))
				throw new Exception($logicalOperator+' requires nonempty array');
			if($options.debug && $options.logger) {
				$options.logger.debug('_executeQuery called', {query:$query, document:$document, options:$options, logicalOperator:$logicalOperator});
			}
			var $pass, result = null;
			// use for to enable returning inside loop
			_.every($query, function($q, $k) {
				$pass = true;
				if(is_string($k) && substr($k, 0, 1) === '$') {
					// key is an operator at this level, except $not, which can be at any level
					if($k === '$not')
						$pass = !JSMongoQuery._executeQuery($q, $document, $options);
					else
						$pass = JSMongoQuery._executeQuery($q, $document, $options, $k);
				} else if($logicalOperator === '$and') { // special case for $and
					if(is_int($k)) { // $q is an object of query objects
						$pass = JSMongoQuery._executeQuery($q, $document, $options);
					} else if(_.isObject($q)) { // query is array, run all queries on field.  All queries must match. e.g { 'age': { $gt: 24, $lt: 52 } }
						$pass = JSMongoQuery._executeQueryOnElement($q, $k, $document, $options);
					} else {
						// key value means equality
						$pass = JSMongoQuery._executeOperatorOnElement('$e', $q, $k, $document, $options);
					}
				} else { // $q is array of query objects e.g '$or' => [{'fullName' => 'Nick'}]
					$pass = JSMongoQuery._executeQuery($q, $document, $options, '$and');
				}

				if ($options.debug && $options.logger) {
					$options.logger.debug('_executeQuery executed query part',
					{query : $q, result: $pass, document : $document, logicalOperator : $logicalOperator});
				}

				switch($logicalOperator) {
					case '$and': // if any fail, query fails
						if(!$pass) { result = false; return false; }
						break;
					case '$or': // if one succeeds, query succeeds
						if($pass) { result = true; return false; }
						break;
					case '$nor': // if one succeeds, query fails
						if($pass) { result = false; return false; }
						break;
					default:
						if ($options.logger) {
							$options.logger.warning('_executeQuery could not find logical operator',
							{query : $query, document : $document, logicalOperator : $logicalOperator});
						}
						result = false;
						return false;
				}
				return true;
			});

			// Will be set if we exited the loop early, so we should use it as the
			// return value for the function.
			if (result !== null) {
				return result;
			}

			switch($logicalOperator) {
				case '$and': // all succeeded, query succeeds
					return true;
				case '$or': // all failed, query fails
					return false;
				case '$nor': // all failed, query succeeded
					return true;
				default:
					if($options.logger) {
						$options.logger.warning('_executeQuery could not find logical operator', {query : $query, document : $document, logicalOperator : $logicalOperator});
					}
					return false;
			}
			throw new Exception('Reached end of _executeQuery without returning a value');
		},

		/**
		 * Execute a query object on an element
		 *
		 * @param $query
		 * @param $field
		 * @param $document
		 * @return boolean
		 * @throws Exception
		 */
		_executeQueryOnElement: function _executeQueryOnElement($query, $element, $document, $options) {
			if($options && $options.debug && $options.logger) {
				$options.logger.debug('_executeQueryOnElement called', {query : $query, element : $element, document : $document, options : $options});
			}
			// iterate through query operators
			return _.every($query, function($opVal, $op) {
				return JSMongoQuery._executeOperatorOnElement($op, $opVal, $element, $document, $options);
			});
		},

		/**
		* Check if an operator is equal to a value
		*
		* Equality includes direct equality, regular expression match, and checking if the operator value is one of the values in an array value
		*
		* @param $v
		* @param $operatorValue
		* @return boolean
		*/
		_isEqual: function isEqual($v, $operatorValue) {
			if(is_array($v)) return in_array($operatorValue, $v);
			var $matches;
			if(is_string($operatorValue) && ($matches = $operatorValue.match(/^\/(.*?)\/([a-z]*)$/i)))
				return (new RegExp($matches[1], $matches[2])).test($v);
			return _.isEqual($operatorValue, $v);
		},

		/**
		 * Execute a Mongo Operator on an element
		 *
		 * @param $operator		The operator to perform
		 * @param $operatorValue	The value to provide the operator
		 * @param $element		The target element.  Can be an object path eg price.shoes
		 * @param $document		The document in which to find the element
		 * @param $options		Options
		 * @return boolean				The result
		 * @throws Exception			Exceptions on invalid operators, invalid unknown operator callback, and invalid operator values
		 */
		_executeOperatorOnElement: function _executeOperatorOnElement($operator, $operatorValue, $element, $document, $options) {
			if($options && $options.debug && $options.logger) {
				$options.logger.debug('_executeOperatorOnElement called', {operatorValue : $operatorValue, element : $element, document : $document, options : $options});
			}

			if($operator === '$not') {
				return !JSMongoQuery._executeQueryOnElement($operatorValue, $element, $document, $options);
			}

			var $elementSpecifier = explode('.',$element),
				$v = $document,
				$exists = true,
				$result = null;
			_.every($elementSpecifier, function($es, $index) {
				if(empty($v)) {
					$exists = false;
					return false;
				}
				if(_.isArray($v)) {
					// value from document is an array, so we need to iterate through array and test the query on all elements of the array
					// if any elements match, then return true
					var $newSpecifier = implode('.', array_slice($elementSpecifier, $index));
					$result = _.some($v, function($item) {
						return JSMongoQuery._executeOperatorOnElement($operator, $operatorValue, $newSpecifier, $item, $options);
					});
					return false;
				}
				if($v[$es] === undefined) {
					$exists = false;
					return false;
				}
				$v = $v[$es];
				return true;
			});

			if($result !== null) {
				return $result;
			}

			switch($operator) {
				case '$all':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw '$all requires array';
					if(count($operatorValue) === 0) return false;
					if(!is_array($v)) {
						if(count($operatorValue) === 1)
							return $v === $operatorValue[0];
						return false;
					}
					return count(array_intersect($v, $operatorValue)) === count($operatorValue);
				case '$e': // non-mongo operator to allow same method call
					if(!$exists) return false;
					return JSMongoQuery._isEqual($v, $operatorValue);
				case '$in':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw new Exception('$in requires array');
					if(count($operatorValue) === 0) return false;
					if(is_array($v)) return count(array_diff($v, $operatorValue)) < count($operatorValue);
					return in_array($v, $operatorValue);
				case '$lt':		return $exists && $v < $operatorValue;
				case '$lte':	return $exists && $v <= $operatorValue;
				case '$gt':		return $exists && $v > $operatorValue;
				case '$gte':	return $exists && $v >= $operatorValue;
				case '$ne':		return (!$exists && $operatorValue !== null) || ($exists && !JSMongoQuery._isEqual($v, $operatorValue));
				case '$nin':
					if(!$exists) return true;
					if(!is_array($operatorValue)) throw new Exception('$nin requires array');
					if(count($operatorValue) === 0) return true;
					if(is_array($v)) {
						return !(count(array_diff($v, $operatorValue)) < count($operatorValue));
					}
					return !in_array($v, $operatorValue);

				case '$exists':	return ($operatorValue && $exists) || (!$operatorValue && !$exists);
				case '$mod':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw new Exception('$mod requires array');
					if(count($operatorValue) !== 2) throw new Exception('$mod requires two parameters in array: divisor and remainder');
					return $v % $operatorValue[0] === $operatorValue[1];

				default:
					if(empty($options['unknownOperatorCallback']) || !is_callable($options['unknownOperatorCallback']))
						throw new Exception('Operator '+$operator+' is unknown');

					var $res = call_user_func($options['unknownOperatorCallback'], $operator, $operatorValue, $element, $document);
					if($res === null)
						throw new Exception('Operator '+$operator+' is unknown');
					if(!is_bool($res))
						throw new Exception('Return value of unknownOperatorCallback must be boolean, actual value '.$res);
					return $res;
			}
			throw new Exception('Didn\'t return in switch');
		},

		/**
		 * Iterate through the query looking for field identifiers.  Append $append to the end of the identifier.
		 *
		 * @param $query
		 * @param $append
		 * @return	The new query
		 */
		appendFieldSpecifier: function appendFieldSpecifier($query, $append) {
			_.each($query, function($v, $k) {
				if(is_array($v))
					$query[$k] = JSMongoQuery.appendFieldSpecifier($v, $append);
				if(is_int($k) || $k[0] === '$') return;
				$query[$k+'.'+$append] = $v;
				delete $query[$k];
			});
			return $query;
		},

		/**
		 * Get the fields this query depends on
		 *
		 * @param $query	The query to analyse
		 * @return array	An array of fields this query depends on
		 */
		getDependentFields: function getDependentFields($query) {
			var $fields = [];
			_.each($query, function($v, $k) {
				if(is_array($v))
					$fields = array_merge($fields, JSMongoQuery.getDependentFields($v));
				if(is_int($k) || $k.charAt(0) === '$') return;
				$fields.push($k);
			});
			return array_unique($fields);
		}

	};

	_.extend(exports, JSMongoQuery);
}));<|endoftext|>


<commit_before>/*global nodeca, _, $, Modernizr, Backbone, window, Faye*/

"use strict";

module.exports = function () {
  var
    // jQuery $elements
    $fontname, $users_count, $glyphs, $import,
    // models
    fonts, result, session,
    // ui views
    toolbar, tabs, selector, preview, editor;

  // check browser's capabilities
  if (!Modernizr.fontface) {
    nodeca.logger.error("bad browser");
    $('#err-bad-browser').modal({backdrop: 'static', keyboard: false});
    return;
  }

  //
  // Models
  //

  // list of all fonts
  fonts = new (Backbone.Collection.extend({
    model: nodeca.client.fontomas.models.font
  }))(nodeca.shared.fontomas.embedded_fonts);

  // special collection of selected glyphs (cache) with
  // extra model logic (validate, config creation and
  // download requesting), but which can still be used
  // as a normal collection for the views
  result = new nodeca.client.fontomas.models.result;

  //
  // Views (UI)
  //

  toolbar   = new nodeca.client.fontomas.ui.toolbar;
  tabs      = new nodeca.client.fontomas.ui.tabs;
  selector  = new nodeca.client.fontomas.ui.panes.selector({model: fonts});
  preview   = new nodeca.client.fontomas.ui.panes.preview({model: result});
  editor    = new nodeca.client.fontomas.ui.panes.codes_editor({model: result});

  //
  // Initialization
  //

  fonts.each(function (f) {
    f.eachGlyph(function (g) {
      toolbar.addKeywords(g.get('source').search || []);
      g.on('change:selected', function (g, val) {
        result[val ? 'add' : 'remove'](g);
      });
    });
  });

  toolbar.on('click:download', function () {
    result.startDownload($('#result-fontname').val());
  });

  toolbar.on('change:glyph-size', _.debounce(function (size) {
    selector.changeGlyphSize(size);
  }, 250));

  // perform glyphs search
  $glyphs = $('.glyph');
  toolbar.on('change:search', function (q) {
    q = $.trim(q);

    if (0 === q.length) {
      $glyphs.show();
      return;
    }

    $glyphs.hide().filter(function () {
      var model = $(this).data('model');
      return model && 0 <= model.keywords.indexOf(q);
    }).show();
  });

  // update selected glyphs count
  result.on('add remove', function () {
    var count = result.length;

    toolbar.setGlyphsCount(count);
    tabs.setGlyphsCount(count);
  });

  // show selector tab after  load complete
  tabs.activate('#selector');

  // Attach tooltip handler to matching elements
  $('._tip').tooltip();

  // Attach collapse handler to matching elements
  $('._collapser').ndCollapser();

  //
  // Initialize clear (selections) button
  //

  $('#reset-app-selections').click(function (event) {
    // do not change location
    event.preventDefault();

    fonts.each(function (f) {
      f.eachGlyph(function (g) {
        g.toggle('selected', false);
      });
    });
  });

  //
  // Initialize reset everything button
  //

  $('#reset-app-all').click(function (event) {
    // do not change location
    event.preventDefault();

    fonts.each(function (f) {
      f.eachGlyph(function (g) {
        g.toggle('selected', false);
        g.unset('code');
        g.unset('css');
      });
    });
  });

  //
  // Fontname
  //

  $fontname = $('#result-fontname');
  $fontname.on('keyup change', function () {
    var $el = $(this);
    $el.val($el.val().replace(/[^a-z0-9\-_]+/g, ''));
  });

  //
  // Sessions
  //

  // Session manager instance
  session = new nodeca.client.fontomas.sessions({
    fontnameElement:  $fontname,
    fontsList:        fonts
  });

  var save_session = _.debounce(function () {
    session.save();
  }, 5000);

  // save current state upon fontname change
  $fontname.change(save_session);

  // change current state when some of glyph properties were changed
  fonts.each(function (f) {
    f.on('before:batch-select', function () {
      nodeca.client.fontomas.sessions.disable();
    });

    f.on('after:batch-select', function () {
      nodeca.client.fontomas.sessions.enable();
      save_session();
    });

    f.eachGlyph(function (g) {
      g.on('change:selected change:code change:css', save_session);
    });
  });

  session.load();

  if ('development' === nodeca.runtime.env) {
    // export some internal collections for debugging
    window.fontello_fonts   = fonts;
    window.fontello_result  = result;
  }

  //
  // Initialize config reader
  //

  $import = $('#import-app-config');

  $import.click(function (event) {
    event.preventDefault();

    if (!window.FileReader) {
      nodeca.client.fontomas.util.notify('error',
        nodeca.client.fontomas.render('error:no-config-import'));
      return false;
    }

    $('#import-app-config-file').click();
    return false;
  });

  // handle file upload
  $('#import-app-config-file').change(function (event) {
    var file = (event.target.files || [])[0], reader;

    nodeca.logger.debug('Import config requested', file);

    if (!file || 'application/json' !== file.type) {
      nodeca.client.fontomas.util.notify('error',
        nodeca.client.fontomas.render('error:invalid-config-file'));
      return;
    }

    reader = new window.FileReader();

    reader.onload = function (event) {
      var config;

      try {
        config = JSON.parse(event.target.result);
      } catch (err) {
        nodeca.client.fontomas.util.notify('error',
          nodeca.client.fontomas.render('error:read-config-failed', {
            error: (err.message || err.toString())
          }));
        return;
      }

      nodeca.logger.debug('Config successfully parsed', config);
      session.readConfig(config);
    };

    reader.readAsBinaryString(file);
  });

  //
  // Initialize Faye
  //

  nodeca.io.init();

  nodeca.io.on('rpc:version-mismatch', function (/* versions */) {
    nodeca.client.fontomas.util.notify('error', {layout: 'bottom'},
      nodeca.client.fontomas.render('error:rpc:server-mismatch'));
  });

  //
  // live update of amount of online clients
  //

  $users_count = $('#stats-online');

  nodeca.io.on('connected', function after_io_conn() {
    nodeca.server.fontomas.online(function (err, msg) {
      $users_count.text(msg.data.users);
    });
  });

  nodeca.io.subscribe('/stats/users_online', function (count) {
    $users_count.text(count);
  }).fail(function (err) {
    nodeca.logger.error('Failed subscribe for stats updates: ' + err);
  });
};
<commit_msg>Fix reset buttons. Reduce session save delay.<commit_after>/*global nodeca, _, $, Modernizr, Backbone, window, Faye*/

"use strict";

module.exports = function () {
  var
    // jQuery $elements
    $fontname, $users_count, $glyphs, $import,
    // models
    fonts, result, session,
    // ui views
    toolbar, tabs, selector, preview, editor;

  // check browser's capabilities
  if (!Modernizr.fontface) {
    nodeca.logger.error("bad browser");
    $('#err-bad-browser').modal({backdrop: 'static', keyboard: false});
    return;
  }

  //
  // Models
  //

  // list of all fonts
  fonts = new (Backbone.Collection.extend({
    model: nodeca.client.fontomas.models.font
  }))(nodeca.shared.fontomas.embedded_fonts);

  // special collection of selected glyphs (cache) with
  // extra model logic (validate, config creation and
  // download requesting), but which can still be used
  // as a normal collection for the views
  result = new nodeca.client.fontomas.models.result;

  //
  // Views (UI)
  //

  toolbar   = new nodeca.client.fontomas.ui.toolbar;
  tabs      = new nodeca.client.fontomas.ui.tabs;
  selector  = new nodeca.client.fontomas.ui.panes.selector({model: fonts});
  preview   = new nodeca.client.fontomas.ui.panes.preview({model: result});
  editor    = new nodeca.client.fontomas.ui.panes.codes_editor({model: result});

  //
  // Initialization
  //

  fonts.each(function (f) {
    f.eachGlyph(function (g) {
      toolbar.addKeywords(g.get('source').search || []);
      g.on('change:selected', function (g, val) {
        result[val ? 'add' : 'remove'](g);
      });
    });
  });

  toolbar.on('click:download', function () {
    result.startDownload($('#result-fontname').val());
  });

  toolbar.on('change:glyph-size', _.debounce(function (size) {
    selector.changeGlyphSize(size);
  }, 250));

  // perform glyphs search
  $glyphs = $('.glyph');
  toolbar.on('change:search', function (q) {
    q = $.trim(q);

    if (0 === q.length) {
      $glyphs.show();
      return;
    }

    $glyphs.hide().filter(function () {
      var model = $(this).data('model');
      return model && 0 <= model.keywords.indexOf(q);
    }).show();
  });

  // update selected glyphs count
  result.on('add remove', function () {
    var count = result.length;

    toolbar.setGlyphsCount(count);
    tabs.setGlyphsCount(count);
  });

  // show selector tab after  load complete
  tabs.activate('#selector');

  // Attach tooltip handler to matching elements
  $('._tip').tooltip();

  // Attach collapse handler to matching elements
  $('._collapser').ndCollapser();

  //
  // Fontname
  //

  $fontname = $('#result-fontname');
  $fontname.on('keyup change', function () {
    var $el = $(this);
    $el.val($el.val().replace(/[^a-z0-9\-_]+/g, ''));
  });

  //
  // Sessions
  //

  // Session manager instance
  session = new nodeca.client.fontomas.sessions({
    fontnameElement:  $fontname,
    fontsList:        fonts
  });

  var save_session = _.debounce(function () {
    session.save();
  }, 2000);

  // save current state upon fontname change
  $fontname.change(save_session);

  // change current state when some of glyph properties were changed
  fonts.each(function (f) {
    f.on('before:batch-select', function () {
      nodeca.client.fontomas.sessions.disable();
    });

    f.on('after:batch-select', function () {
      nodeca.client.fontomas.sessions.enable();
      save_session();
    });

    f.eachGlyph(function (g) {
      g.on('change:selected change:code change:css', save_session);
    });
  });

  session.load();

  //
  // Initialize clear (selections) button
  //

  $('#reset-app-selections').click(function (event) {
    // do not change location
    event.preventDefault();

    fonts.each(function (f) {
      f.eachGlyph(function (g) {
        g.toggle('selected', false);
      });
    });

    save_session();
  });

  //
  // Initialize reset everything button
  //

  $('#reset-app-all').click(function (event) {
    // do not change location
    event.preventDefault();

    fonts.each(function (f) {
      f.eachGlyph(function (g) {
        g.toggle('selected', false);
        g.unset('code');
        g.unset('css');
      });
    });

    $fontname.val('');
    save_session();
  });

  if ('development' === nodeca.runtime.env) {
    // export some internal collections for debugging
    window.fontello_fonts   = fonts;
    window.fontello_result  = result;
  }

  //
  // Initialize config reader
  //

  $import = $('#import-app-config');

  $import.click(function (event) {
    event.preventDefault();

    if (!window.FileReader) {
      nodeca.client.fontomas.util.notify('error',
        nodeca.client.fontomas.render('error:no-config-import'));
      return false;
    }

    $('#import-app-config-file').click();
    return false;
  });

  // handle file upload
  $('#import-app-config-file').change(function (event) {
    var file = (event.target.files || [])[0], reader;

    nodeca.logger.debug('Import config requested', file);

    if (!file || 'application/json' !== file.type) {
      nodeca.client.fontomas.util.notify('error',
        nodeca.client.fontomas.render('error:invalid-config-file'));
      return;
    }

    reader = new window.FileReader();

    reader.onload = function (event) {
      var config;

      try {
        config = JSON.parse(event.target.result);
      } catch (err) {
        nodeca.client.fontomas.util.notify('error',
          nodeca.client.fontomas.render('error:read-config-failed', {
            error: (err.message || err.toString())
          }));
        return;
      }

      nodeca.logger.debug('Config successfully parsed', config);
      session.readConfig(config);
    };

    reader.readAsBinaryString(file);
  });

  //
  // Initialize Faye
  //

  nodeca.io.init();

  nodeca.io.on('rpc:version-mismatch', function (/* versions */) {
    nodeca.client.fontomas.util.notify('error', {layout: 'bottom'},
      nodeca.client.fontomas.render('error:rpc:server-mismatch'));
  });

  //
  // live update of amount of online clients
  //

  $users_count = $('#stats-online');

  nodeca.io.on('connected', function after_io_conn() {
    nodeca.server.fontomas.online(function (err, msg) {
      $users_count.text(msg.data.users);
    });
  });

  nodeca.io.subscribe('/stats/users_online', function (count) {
    $users_count.text(count);
  }).fail(function (err) {
    nodeca.logger.error('Failed subscribe for stats updates: ' + err);
  });
};
<|endoftext|>


<commit_before>/* just created to avoid errors. */
<commit_msg>bug fix in Icon<commit_after>import React from 'react';
import { Path } from 'react-native-svg';

export default {
	"checkbox-checked": {
		svg: <Path d="M31.616 4l-16.38 20.605L4 12.947" /> ,
		viewBox: "0 0 35 28"
	},
	"chevron-down": {
		svg: <Path d="M16.54 2.428l-7.65 7.8-7.35-7.8" /> ,
		viewBox: "0 0 19 12"
	},
	"chevron-up": {
		svg: <Path d="M16.857 9.894l-7.65-7.8-7.35 7.8" /> ,
		viewBox: "0 0 19 12"
	}
}<|endoftext|>


<commit_before>const continuationBit = 1 << 7;
const lowBitsMask = ~continuationBit & 0xff;

/**
 * Consume a readable stream of unsigned LEB128 encoded bytes, writing the
 * results into the provided buffer
 *
 * @template {ArrayBufferView} T
 * @param {ReadableStream<Uint8Array>} stream A stream of concatenated unsigned
 * LEB128 encoded values
 * @param {T} buffer An ArrayBuffer view to write the decoded values to
 * @returns {T} the passed in `buffer`
 */
export async function decompress(stream, buffer) {
    const reader = stream.getReader();

    let index = 0;
    let result = 0;
    let shift = 0;

    while (true) {
        const { done, value } = await reader.read();

        if (done) {
            return buffer;
        }

        for (const byte of value) {
            result |= (byte & lowBitsMask) << shift;

            if ((byte & continuationBit) === 0) {
                buffer[index++] = result;
                result = shift = 0;
                continue;
            }

            shift += 7;
        }
    }
}
<commit_msg>Specialise decompress for Uint32Array only<commit_after>const continuationBit = 1 << 7;
const lowBitsMask = ~continuationBit & 0xff;

/**
 * Consume a readable stream of unsigned LEB128 encoded bytes, writing the
 * results into the provided buffer
 *
 * @param {ReadableStream<Uint8Array>} stream A stream of concatenated unsigned
 * LEB128 encoded values
 * @param {Uint32Array} buffer A preallocated Uint32Array to write the decoded
 * values into
 */
export async function decompress(stream, buffer) {
    const reader = stream.getReader();

    let index = 0;
    let result = 0;
    let shift = 0;

    while (true) {
        const { done, value } = await reader.read();

        if (done) {
            return;
        }

        for (const byte of value) {
            result |= (byte & lowBitsMask) << shift;

            if ((byte & continuationBit) === 0) {
                buffer[index++] = result;
                result = shift = 0;
                continue;
            }

            shift += 7;
        }
    }
}
<|endoftext|>


<commit_before>// Install this.socket.io-client
// io object exposed from injected this.socket.io.js

// import io from 'socket.io-client';

class Viewer {
  constructor(
    ID_of_NodeToRenderVideo // location on the DOM where the live feed will be rendered
  ) {
    // initiate new torrent connection
    this.client = new WebTorrent()
    // grab DOM elements where the torrent video will be rendered too
    this.ID_of_NodeToRenderVideo = ID_of_NodeToRenderVideo;
    this.$play1 = document.getElementById('player1');
    this.$play2 = document.getElementById('player2');
    this.$play3 = document.getElementById('player3');
    this.isPlay1Playing = false;
    this.isPlay2Playing = false;
    this.firstIteration = 0;
    this.socket = io.connect();
    this.childLimit = 1
    this.connToParent; // connection to parent - client node that's closer to server
    this.connToChild; // connection to child - client moving farther away from server
  }

  // send message on RTC connection
  sendBySocket(event, ...args) {
    this.socket.emit(event, ...args);
  }

  setUpInitialConnection() {
    // document.createElement('video');
    this.socket.on('connect', () => {
      console.log('working');
    });

    // start playing next in video tag trio
    this.socket.on('magnetURI', (magnetURI) => {
      // begin downloading the torrents and render them to page, alternate between three torrents
      if (this.isPlay1Playing && this.isPlay2Playing) {
        this.startDownloadingThird(magnetURI);
      } else if (this.isPlay1Playing) {
        this.startDownloadingSecond(magnetURI);
      } else {
        this.startDownloadingFirst(magnetURI);
      }
    });

    // if sockets are full, get torrent info from server thru WebRTC
    this.socket.on('full', (msg, disconnect) => {
      // addText(msg);
      if (disconnect) {
        console.log('Sockets full, creating WebRTC connection...');

        // create new WebRTC connection to connect to a parent
        // will disconnect once WebRTC connection established
        this.connToParent = this.createPeerConn();
        // begin negotiation process w/ an offer
        this.initOffer();
      }
    });

    /**
     * WebRTC workflow handlers
     */

    // Callee: receives offer for a connection
    this.socket.on('offer', this.receiveOffer.bind(this));
    // Caller: receives answer after sending offer
    this.socket.on('answer', this.receiveAnswer.bind(this));
    // Both peers: add new ICE candidates as they come in
    this.socket.on('candidate', this.handleNewIceCandidate.bind(this));

    // this.socket.on('disconnect', () => {});
  }

  // Create WebRTC connection to a peer
  createPeerConn() {
    const conn = new RTCPeerConnection({
      iceServers: [
        // STUN servers
        { url: 'stun:stun.l.google.com:19302' },
        { url: 'stun:stun1.l.google.com:19302' },
        { url: 'stun:stun2.l.google.com:19302' },
        { url: 'stun:stun3.l.google.com:19302' },
        { url: 'stun:stun4.l.google.com:19302' },
        // TODO: developer-provided TURN servers go here
      ]
    });
    console.log('Starting WebRTC signaling...');

    /**
     * Useful diagrams for WebRTC signaling process:
     *
     * 1. Initiating negotiation:
     * https://mdn.mozillademos.org/files/12363/WebRTC%20-%20Signaling%20Diagram.svg
     * -Caller creates offer
     *
     * 2. Exchanging ICE candidates:
     * https://mdn.mozillademos.org/files/12365/WebRTC%20-%20ICE%20Candidate%20Exchange.svg
     *
     */

    // Handle connection state changes
    conn.onconnectionstatechange = this.connectionStateHandler;

    // when ICE candidates need to be sent to callee
    conn.onicecandidate = this.iceCandidateHandler;

    return conn;
  }

  connectionStateHandler() {
    console.log(conn.connectionState);

    if (conn.connectionState === 'connected') {
      // disconnect socket.io connection
      this.socket.disconnect();
    }
  }

  // Caller: begin connection to parent client
  // RTC negotiationneeded handler
  initOffer() {
    console.log('Initiating offer...');
    // create offer to parent
    this.connToParent.createOffer()
      // set local description of caller
      .then(offer => this.connToParent.setLocalDescription(offer))
      // send offer along to peer
      .then(() => {
        const offer = this.connToParent.localDescription;
        this.sendBySocket('offer', offer);

        // TODO: post to server so non-socket clients can transmit session info w/o sockets?
      })
      .catch(this.logError);
  }

  // Callee: receive offer from new child peer
  // this.socket 'offer' handler
  receiveOffer(callerId, offer) {
    console.log('Receiving offer to connect...');
    // create child connection
    this.connToChild = this.createPeerConn();

    // set remote end's info
    // return Promise that resolves after creating and setting answer as local description
    // sending answer will be handled by this.socket.io
    return this.connToChild.setRemoteDescription(offer)
      // create answer to offer
      .then(() => this.connToChild.createAnswer())
      // set local description of callee
      .then((answer) => this.connToChild.setLocalDescription(answer))
      // send answer to caller
      .then(() => {
        console.log('Set local description');
        // console.log('Local:', this.connToChild.localDescription);
        // console.log('Remote:', this.connToChild.remoteDescription);
        const answer = this.connToChild.localDescription;
        this.sendBySocket('answer', callerId, answer);
      })
      .catch(this.logError);
  }

  // Callee: as a parent/caller, receive answer from child/callee
  // this.socket 'answer' handler
  receiveAnswer(answer) {
    console.log('Receiving answer from offer...');
    // set info from remote end
    return this.connToParent.setRemoteDescription(answer)
      .then(() => {
        console.log('Set remote description');
        // console.log('Local:', this.connToParent.localDescription);
        // console.log('Remote:', this.connToParent.remoteDescription);
      })
      .catch(this.logError);
  }

  // Caller: send ICE candidate to callee
  // RTC onicecandidate handler
  iceCandidateHandler(event) {
    console.log('Sending ICE candidates...');
    if (event.candidate) {
      // send child peer ICE candidate
      this.sendBySocket('candidate', event.candidate);
    }
  }
  // Callee: receive an ICE candidate from caller
  // this.socket ICE candidate handler
  handleNewIceCandidate(candidate) {
    const iceCandidate = new RTCIceCandidate(candidate);

    // add ICE candidate from caller (parent)
    this.connToParent.addIceCandidate(candidate)
      .catch(this.logError);
  }

  // close connections and free up resources
  closeConnToParent(conn) {
    this.connToParent.close();
    this.connToParent = null;
    // tell other peer to close connection as well
    sendBySocket('close');
  }

  closeConnToChild(conn) {
    this.connToChild.close();
    this.connToChild = null;
    // tell other peer to close connection as well
    sendBySocket('close');
  }

  // torrentId will change whenever the viewer is notified of the new magnet via websockets or WebRTC
  // this will also trigger event to check if this.isPlay1Playing true or false
  // and then it will either run the first download or the second download, torrent ID must be different

  // Function for downloading the torrent
  startDownloadingFirst(magnetURI) {
    this.firstIteration += 1;
    let firstIteration = this.firstIteration;
    let $play1 = this.$play1;
    let $play2 = this.$play2;

    console.log('first Iteration', firstIteration)

    this.isPlay1Playing = true;

    this.client.add(magnetURI, function (torrent) {

      /* Look for the file that ends in .webm and render it, in the future we can
       * add additional file types for scaling. E.g other video formats or even VR!
       */
      let file1 = torrent.files.find(function (file) {
        return file.name.endsWith('.webm')
      })

      // Stream the file in the browser
      if (firstIteration === 1) {
        window.setTimeout(() => { file1.renderTo('video#player1') }, 4000);
        firstIteration += 1;
      } else {
        file1.renderTo('video#player1', { autoplay: false })
      }
    })

    // listen to when video 1 ends, immediately play the other video
    $play1.onended = function (e) {
      $play2.play();

      $play2.removeAttribute('hidden');

      $play1.setAttribute('hidden', true);
    };
  }

  // Function for downloading the second torrent
  startDownloadingSecond(magnetURI) {
    this.isPlay2Playing = true;
    let $play2 = this.$play2;
    let $play3 = this.$play3;

    this.client.add(magnetURI, function (torrent) {

      /* Look for the file that ends in .webm and render it, in the future we can
       * add additional file types for scaling. E.g other video formats or even VR!
       */
      let file2 = torrent.files.find(function (file) {
        return file.name.endsWith('.webm')
      })

      // Stream the second file, but currently invisible and not playing
      file2.renderTo('video#player2', { autoplay: false })
    })

    // listen to when video 2 ends, immediately play the other video
    $play2.onended = function (e) {
      $play3.play();

      $play3.removeAttribute('hidden');

      $play2.setAttribute('hidden', true);
    };
  }

  startDownloadingThird(magnetURI) {
    this.isPlay1Playing = this.isPlay2Playing = false;

    let $play1 = this.$play1;
    let $play3 = this.$play3;

    this.client.add(magnetURI, function (torrent) {

      /* Look for the file that ends in .webm and render it, in the future we can
       * add additional file types for scaling. E.g other video formats or even VR!
       */
      let file3 = torrent.files.find(function (file) {
        return file.name.endsWith('.webm')
      })

      // Stream the second file, but currently invisible and not playing
      file3.renderTo('video#player3', { autoplay: false })
    })

    // listen to when video 3 ends, immediately play the other video
    $play3.onended = function (e) {
      $play1.play();
      console.log('am i working?')
      $play1.removeAttribute('hidden');

      $play3.setAttribute('hidden', true);
    }
  }

  logError(err) {
    throw err;
  }
}

// export default Viewer

<commit_msg>Fall back on Mozilla RTC Connection constructor<commit_after>// Install this.socket.io-client
// io object exposed from injected this.socket.io.js

// import io from 'socket.io-client';

// set peer connection to Mozilla PeerConnection if in Firefox
RTCPeerConnection = RTCPeerConnection || mozRTCPeerConnection;

class Viewer {
  constructor(
    ID_of_NodeToRenderVideo // location on the DOM where the live feed will be rendered
  ) {
    // initiate new torrent connection
    this.client = new WebTorrent()
    // grab DOM elements where the torrent video will be rendered too
    this.ID_of_NodeToRenderVideo = ID_of_NodeToRenderVideo;
    this.$play1 = document.getElementById('player1');
    this.$play2 = document.getElementById('player2');
    this.$play3 = document.getElementById('player3');
    this.isPlay1Playing = false;
    this.isPlay2Playing = false;
    this.firstIteration = 0;
    this.socket = io.connect();
    this.childLimit = 1
    this.connToParent; // connection to parent - client node that's closer to server
    this.connToChild; // connection to child - client moving farther away from server
  }

  // send message on RTC connection
  sendBySocket(event, ...args) {
    this.socket.emit(event, ...args);
  }

  setUpInitialConnection() {
    // document.createElement('video');
    this.socket.on('connect', () => {
      console.log('working');
    });

    // start playing next in video tag trio
    this.socket.on('magnetURI', (magnetURI) => {
      // begin downloading the torrents and render them to page, alternate between three torrents
      if (this.isPlay1Playing && this.isPlay2Playing) {
        this.startDownloadingThird(magnetURI);
      } else if (this.isPlay1Playing) {
        this.startDownloadingSecond(magnetURI);
      } else {
        this.startDownloadingFirst(magnetURI);
      }
    });

    // if sockets are full, get torrent info from server thru WebRTC
    this.socket.on('full', (msg, disconnect) => {
      // addText(msg);
      if (disconnect) {
        console.log('Sockets full, creating WebRTC connection...');

        // create new WebRTC connection to connect to a parent
        // will disconnect once WebRTC connection established
        this.connToParent = this.createPeerConn();
        // begin negotiation process w/ an offer
        this.initOffer();
      }
    });

    /**
     * WebRTC workflow handlers
     */

    // Callee: receives offer for a connection
    this.socket.on('offer', this.receiveOffer.bind(this));
    // Caller: receives answer after sending offer
    this.socket.on('answer', this.receiveAnswer.bind(this));
    // Both peers: add new ICE candidates as they come in
    this.socket.on('candidate', this.handleNewIceCandidate.bind(this));

    // this.socket.on('disconnect', () => {});
  }

  // Create WebRTC connection to a peer
  createPeerConn() {
    const conn = new RTCPeerConnection({
      iceServers: [
        // STUN servers
        { url: 'stun:stun.l.google.com:19302' },
        { url: 'stun:stun1.l.google.com:19302' },
        { url: 'stun:stun2.l.google.com:19302' },
        { url: 'stun:stun3.l.google.com:19302' },
        { url: 'stun:stun4.l.google.com:19302' },
        // TODO: developer-provided TURN servers go here
      ]
    });
    console.log('Starting WebRTC signaling...');

    /**
     * Useful diagrams for WebRTC signaling process:
     *
     * 1. Initiating negotiation:
     * https://mdn.mozillademos.org/files/12363/WebRTC%20-%20Signaling%20Diagram.svg
     * -Caller creates offer
     *
     * 2. Exchanging ICE candidates:
     * https://mdn.mozillademos.org/files/12365/WebRTC%20-%20ICE%20Candidate%20Exchange.svg
     *
     */

    // Handle connection state changes
    conn.onconnectionstatechange = this.connectionStateHandler;

    // when ICE candidates need to be sent to callee
    conn.onicecandidate = this.iceCandidateHandler;

    return conn;
  }

  connectionStateHandler() {
    console.log(conn.connectionState);

    if (conn.connectionState === 'connected') {
      // disconnect socket.io connection
      this.socket.disconnect();
    }
  }

  // Caller: begin connection to parent client
  // RTC negotiationneeded handler
  initOffer() {
    console.log('Initiating offer...');
    // create offer to parent
    this.connToParent.createOffer()
      // set local description of caller
      .then(offer => this.connToParent.setLocalDescription(offer))
      // send offer along to peer
      .then(() => {
        const offer = this.connToParent.localDescription;
        this.sendBySocket('offer', offer);

        // TODO: post to server so non-socket clients can transmit session info w/o sockets?
      })
      .catch(this.logError);
  }

  // Callee: receive offer from new child peer
  // this.socket 'offer' handler
  receiveOffer(callerId, offer) {
    console.log('Receiving offer to connect...');
    // create child connection
    this.connToChild = this.createPeerConn();

    // set remote end's info
    // return Promise that resolves after creating and setting answer as local description
    // sending answer will be handled by this.socket.io
    return this.connToChild.setRemoteDescription(offer)
      // create answer to offer
      .then(() => this.connToChild.createAnswer())
      // set local description of callee
      .then((answer) => this.connToChild.setLocalDescription(answer))
      // send answer to caller
      .then(() => {
        console.log('Set local description');
        // console.log('Local:', this.connToChild.localDescription);
        // console.log('Remote:', this.connToChild.remoteDescription);
        const answer = this.connToChild.localDescription;
        this.sendBySocket('answer', callerId, answer);
      })
      .catch(this.logError);
  }

  // Callee: as a parent/caller, receive answer from child/callee
  // this.socket 'answer' handler
  receiveAnswer(answer) {
    console.log('Receiving answer from offer...');
    // set info from remote end
    return this.connToParent.setRemoteDescription(answer)
      .then(() => {
        console.log('Set remote description');
        // console.log('Local:', this.connToParent.localDescription);
        // console.log('Remote:', this.connToParent.remoteDescription);
      })
      .catch(this.logError);
  }

  // Caller: send ICE candidate to callee
  // RTC onicecandidate handler
  iceCandidateHandler(event) {
    console.log('Sending ICE candidates...');
    if (event.candidate) {
      // send child peer ICE candidate
      this.sendBySocket('candidate', event.candidate);
    }
  }
  // Callee: receive an ICE candidate from caller
  // this.socket ICE candidate handler
  handleNewIceCandidate(candidate) {
    const iceCandidate = new RTCIceCandidate(candidate);

    // add ICE candidate from caller (parent)
    this.connToParent.addIceCandidate(candidate)
      .catch(this.logError);
  }

  // close connections and free up resources
  closeConnToParent(conn) {
    this.connToParent.close();
    this.connToParent = null;
    // tell other peer to close connection as well
    sendBySocket('close');
  }

  closeConnToChild(conn) {
    this.connToChild.close();
    this.connToChild = null;
    // tell other peer to close connection as well
    sendBySocket('close');
  }

  // torrentId will change whenever the viewer is notified of the new magnet via websockets or WebRTC
  // this will also trigger event to check if this.isPlay1Playing true or false
  // and then it will either run the first download or the second download, torrent ID must be different

  // Function for downloading the torrent
  startDownloadingFirst(magnetURI) {
    this.firstIteration += 1;
    let firstIteration = this.firstIteration;
    let $play1 = this.$play1;
    let $play2 = this.$play2;

    console.log('first Iteration', firstIteration)

    this.isPlay1Playing = true;

    this.client.add(magnetURI, function (torrent) {

      /* Look for the file that ends in .webm and render it, in the future we can
       * add additional file types for scaling. E.g other video formats or even VR!
       */
      let file1 = torrent.files.find(function (file) {
        return file.name.endsWith('.webm')
      })

      // Stream the file in the browser
      if (firstIteration === 1) {
        window.setTimeout(() => { file1.renderTo('video#player1') }, 4000);
        firstIteration += 1;
      } else {
        file1.renderTo('video#player1', { autoplay: false })
      }
    })

    // listen to when video 1 ends, immediately play the other video
    $play1.onended = function (e) {
      $play2.play();

      $play2.removeAttribute('hidden');

      $play1.setAttribute('hidden', true);
    };
  }

  // Function for downloading the second torrent
  startDownloadingSecond(magnetURI) {
    this.isPlay2Playing = true;
    let $play2 = this.$play2;
    let $play3 = this.$play3;

    this.client.add(magnetURI, function (torrent) {

      /* Look for the file that ends in .webm and render it, in the future we can
       * add additional file types for scaling. E.g other video formats or even VR!
       */
      let file2 = torrent.files.find(function (file) {
        return file.name.endsWith('.webm')
      })

      // Stream the second file, but currently invisible and not playing
      file2.renderTo('video#player2', { autoplay: false })
    })

    // listen to when video 2 ends, immediately play the other video
    $play2.onended = function (e) {
      $play3.play();

      $play3.removeAttribute('hidden');

      $play2.setAttribute('hidden', true);
    };
  }

  startDownloadingThird(magnetURI) {
    this.isPlay1Playing = this.isPlay2Playing = false;

    let $play1 = this.$play1;
    let $play3 = this.$play3;

    this.client.add(magnetURI, function (torrent) {

      /* Look for the file that ends in .webm and render it, in the future we can
       * add additional file types for scaling. E.g other video formats or even VR!
       */
      let file3 = torrent.files.find(function (file) {
        return file.name.endsWith('.webm')
      })

      // Stream the second file, but currently invisible and not playing
      file3.renderTo('video#player3', { autoplay: false })
    })

    // listen to when video 3 ends, immediately play the other video
    $play3.onended = function (e) {
      $play1.play();
      console.log('am i working?')
      $play1.removeAttribute('hidden');

      $play3.setAttribute('hidden', true);
    }
  }

  logError(err) {
    throw err;
  }
}

// export default Viewer

<|endoftext|>