<commit_before>define(['app'], function (app) {

"use strict";
//require("app", "dragAndDrop")

app.controller("presentationController",
	["$rootScope", "$location" , "$scope", "$q", "$window", "IStorage", "underscore",
	 "schemaTypes", "$compile", "$timeout","lstringFilter", "$routeParams",
	 function ($rootScope, $location, $scope, $q, $window, storage, _,
	  schemaTypes,$compile,$timeout,lstringFilter, $routeParams) {

		var current_page = 'theUser';

        function load_page(id) {

            // Fetch JSON for page data associated with given ID
            var page_data = _.where($scope.PAGES, {name : id})[0];
			current_page = id;
            clear_page();
            set_page_text(page_data.text, 'page_data.name', page_data.template);
            if (page_data.choices) {
                for (var choice in page_data.choices) {
                    var choice_data = page_data.choices[choice];
                    add_choices(choice_data.text, choice_data.value);
                }
            }

			if (page_data.navigation) {
                for (var navigation in page_data.navigation) {
                    var navigation_data = page_data.navigation[navigation];
                    add_navigation(navigation_data.text, navigation_data.target);
                }
            }
			if(page_data.points){
				if(page_data.points[0].user){
					if(!$scope.userPoints)
						$scope.userPoints = [];
					var pointCal = $scope.userPoints;
					_.forEach(_.pairs(page_data.points[0].user[0]), function(data){
						var existing = _.where(pointCal, {type : data[0]});
						if(existing.length == 0)
							pointCal.push({type : data[0], score : data[1]});
						else
							existing[0].score = existing[0].score + data[1]
					})
					$timeout(function(){
						$scope.userPoints = pointCal
					},10);
				}

				if(page_data.points[0].provider){
					if(!$scope.providerPoints)
						$scope.providerPoints = [];

					var pointCal1 = $scope.providerPoints;
					_.forEach(_.pairs(page_data.points[0].provider[0]), function(data){
						var existing1 = _.where(pointCal1, {type : data[0]});
						if(existing1.length == 0)
							pointCal1.push({type : data[0], score : data[1]});
						else
							existing1[0].score = existing1[0].score + data[1]
					})

					$timeout(function(){
						$scope.providerPoints = pointCal1
					},10);
				}
			}

        }

        function set_page_text(text, name,template) {
			//"

            $("#page_text").append("
" + text + "
");
			$('#presentation').html('');
        }

        function add_choices(text, value) {
            $("#response").append("<input class=\"choice\" type=\"checkbox\" name=\"" + value + "\" value=\"" + value + "\" />" + text
								+ "
");

        }

        function add_navigation(text, target) {
            $("#response").append("<button class=navigation data-target=" + target + ">" + text + "</button>");

        }

        function clear_page() {
            $("#page_text").empty();
            $("#response").empty();
        }

        // Page data
		// $scope.choicesMade = [];//['blaise fonseca', 'Schnell fonseca'];
        $scope.PAGES = [
						   {
							  'name' : 'theUser',
						      'text':'Slide - The User',
							  'template' : 'presentations/images/theatre-access/theatre-access.001.jpg',
						      'type':'choice',
						      'navigation':[
						         {
						            'text':'Continue.',
						            'target':'providerDesignatingCheckpoints'
								}],
							  'points' : [{	'user' : [{'r' : 50,'b' : 37,'c' : 58}],
											'provider' : [{'r' : 50,'b' : 37,'c' : 58}]}]
						   },
						   {
							  'name' : 'providerDesignatingCheckpoints',
						      'text':'Slide - Provider Designating Checkpoints',
							  'template' : 'presentations/images/theatre-access/theatre-access.002.jpg',
						      'type':'choice',
						      'navigation':[
						         {
						            'text':'Continue.',
						            'target':1
								},
								{
						            'text':'back.',
						            'target':'theUser'
								}],
						      'choices':[
						         {
						            'text':'You designate the national patent office responsible for magic genetic resources.',
						            'value':'national patent office',
							  		'points' : [{	'user' : [{'Relationsship' : 570,'Type2' : 337,'Mat' : 958}],
											'provider' : [{'Relationsship' : 550,'Type2' : 37,'Mat' : 58}]}]
								},{
						            'text':'You designate the WIZARD WORLD Review, a premier magic journal.',
						            'value':'WIZARD WORLD Review',
							  		'points' : [{	'user' : [{'Relationsship' : 530,'Type2' : 3457,'Mat' : 548}],
											'provider' : [{'Relationsship' : 5,'Type2' : 3,'Mat' : 5}]}]
								}],
							  'points' : [{	'user' : [{'Relationsship' : 40,'Type2' : 37,'Mat' : 58}],
											'provider' : [{'Relationsship' : 50,'Type2' : 37,'Mat' : 58}]
										 }]
						   }
						];

			///blaise fonseca

		    load_page(current_page);
            $('#response').on('click', '.navigation', function () {
                var target = $(this).data('target');
                load_page(target);
            });

			$scope.choicesMade = [];
			$('#response').on('change', '.choice', function () {
				var val = this.value;//.replace('_yes','').replace('_no', '');
				var negate = false;

				if(!this.selected)
					negate = true;
				$timeout(function(){$scope.choicesMade.push(val); $scope.choicesMade = _.uniq($scope.choicesMade);},10);

	            var page_data = _.where($scope.PAGES, {name : current_page})[0];
				var choicespoints = _.where(page_data.choices, {value : val});
				if(choicespoints.length > 0){

						if(!$scope.userChoicePoints)
							$scope.userChoicePoints = [];
						var pointCal = $scope.userChoicePoints;
						$scope.calculatePoints(choicespoints[0].points[0].user[0], pointCal, negate)
						$timeout(function(){
							$scope.userChoicePoints = pointCal
						},10);

						if(!$scope.providerChoicePoints)
							$scope.providerChoicePoints = [];
						var pointCal1 = $scope.providerChoicePoints;
						$scope.calculatePoints(choicespoints[0].points[0].provider[0], pointCal1, negate)
						$timeout(function(){
							$scope.providerChoicePoints = pointCal1
						},10);
				}

            });

			$scope.calculatePoints = function(pointArray, pointCal, negate){
				_.forEach(_.pairs(pointArray), function(data){
					var existing = _.where(pointCal, {type : data[0]});
					if(existing.length == 0)
						pointCal.push({type : data[0], score : (negate ? -1 : 1)  * data[1]});
					else
						existing[0].score = existing[0].score + (negate ? -1 : 1)  * data[1]
				})
			}

			$scope.sumPoints = function(existingPoints, type, entity){

				if(entity == 'user'){
					if($scope.userChoicePoints){
						var points = _.where($scope.userChoicePoints, {type : type})[0];
						return existingPoints + points.score;
					}
					return existingPoints;
				}

				if(entity == 'provider'){
					if($scope.providerChoicePoints){
						var points = _.where($scope.providerChoicePoints, {type : type})[0];
						console.log(points, 'provider');
						return existingPoints + points.score ;
					}
					return existingPoints;
				}
			}

   }]);
});
<commit_msg>choice points<commit_after>define(['app'], function (app) {

"use strict";
//require("app", "dragAndDrop")

app.controller("presentationController",
	["$rootScope", "$location" , "$scope", "$q", "$window", "IStorage", "underscore",
	 "schemaTypes", "$compile", "$timeout","lstringFilter", "$routeParams",
	 function ($rootScope, $location, $scope, $q, $window, storage, _,
	  schemaTypes,$compile,$timeout,lstringFilter, $routeParams) {

		var current_page = 'theUser';

        function load_page(id) {

            // Fetch JSON for page data associated with given ID
            var page_data = _.where($scope.PAGES, {name : id})[0];
			current_page = id;
            clear_page();
            set_page_text(page_data.text, 'page_data.name', page_data.template);
            if (page_data.choices) {
                for (var choice in page_data.choices) {
                    var choice_data = page_data.choices[choice];
                    add_choices(choice_data.text, choice_data.value);
                }
            }

			if (page_data.navigation) {
                for (var navigation in page_data.navigation) {
                    var navigation_data = page_data.navigation[navigation];
                    add_navigation(navigation_data.text, navigation_data.target);
                }
            }
			if(page_data.points){
				if(page_data.points[0].user){
					if(!$scope.userPoints)
						$scope.userPoints = [];
					var pointCal = $scope.userPoints;
					_.forEach(_.pairs(page_data.points[0].user[0]), function(data){
						var existing = _.where(pointCal, {type : data[0]});
						if(existing.length == 0)
							pointCal.push({type : data[0], score : data[1]});
						else
							existing[0].score = existing[0].score + data[1]
					})
					$timeout(function(){
						$scope.userPoints = pointCal
					},10);
				}

				if(page_data.points[0].provider){
					if(!$scope.providerPoints)
						$scope.providerPoints = [];

					var pointCal1 = $scope.providerPoints;
					_.forEach(_.pairs(page_data.points[0].provider[0]), function(data){
						var existing1 = _.where(pointCal1, {type : data[0]});
						if(existing1.length == 0)
							pointCal1.push({type : data[0], score : data[1]});
						else
							existing1[0].score = existing1[0].score + data[1]
					})

					$timeout(function(){
						$scope.providerPoints = pointCal1
					},10);
				}
			}

        }

        function set_page_text(text, name,template) {
			//"

            $("#page_text").append("
" + text + "
");
			$('#presentation').html('');
        }

        function add_choices(text, value) {
            $("#response").append("<input class=\"choice\" type=\"checkbox\" name=\"" + value + "\" value=\"" + value + "\" />" + text
								+ "
");

        }

        function add_navigation(text, target) {
            $("#response").append("<button class=navigation data-target=" + target + ">" + text + "</button>");

        }

        function clear_page() {
            $("#page_text").empty();
            $("#response").empty();
        }

        // Page data
		// $scope.choicesMade = [];//['blaise fonseca', 'Schnell fonseca'];
        $scope.PAGES = [
						   {
							  'name' : 'theUser',
						      'text':'Slide - The User',
							  'template' : 'presentations/images/theatre-access/theatre-access.001.jpg',
						      'type':'choice',
						      'navigation':[
						         {
						            'text':'Continue.',
						            'target':'providerDesignatingCheckpoints'
								}],
							  'points' : [{	'user' : [{'r' : 50,'b' : 37,'c' : 58}],'provider' : [{'r' : 50,'b' : 37,'c' : 58}]}]
						   },
						   {
							  'name' : 'providerDesignatingCheckpoints',
						      'text':'Slide - Provider Designating Checkpoints',
							  'template' : 'presentations/images/theatre-access/theatre-access.002.jpg',
						      'type':'choice',
						      'navigation':[
						         {
						            'text':'Continue.',
						            'target':1
								},
								{
						            'text':'back.',
						            'target':'theUser'
								}],
						      'choices':[
						         {
						            'text':'You designate the national patent office responsible for magic genetic resources.',
						            'value':'national patent office',
							  		'points' : [{'user' : [{'r' : 50,'b' : 37,'c' : 58}],'provider' : [{'r' : 50,'b' : 37,'c' : 58}]}]
								},{
						            'text':'You designate the WIZARD WORLD Review, a premier magic journal.',
						            'value':'WIZARD WORLD Review',
							  		'points' :[{'user' : [{'r' : 50,'b' : 37,'c' : 58}],'provider' : [{'r' : 50,'b' : 37,'c' : 58}]}]
								}],
							  'points' :[{'user' : [{'r' : 50,'b' : 37,'c' : 58}],'provider' : [{'r' : 50,'b' : 37,'c' : 58}]}]
						   }
						];

			///blaise fonseca

		    load_page(current_page);
            $('#response').on('click', '.navigation', function () {
                var target = $(this).data('target');
                load_page(target);
            });

			$scope.choicesMade = [];
			$('#response').on('change', '.choice', function () {
				var val = this.value;//.replace('_yes','').replace('_no', '');
				var negate = false;

				if(!this.checked)
					negate = true;
				$timeout(function(){
					if(!negate)
						$scope.choicesMade.push(val);
					else{
						$scope.choicesMade.remove(val);
					}
					// $scope.choicesMade = _.uniq($scope.choicesMade);
					 console.log($scope.choicesMade);
				},10);

	            var page_data = _.where($scope.PAGES, {name : current_page})[0];
				var choicespoints = _.where(page_data.choices, {value : val});
				if(choicespoints.length > 0){

						if(!$scope.userPoints)
							$scope.userPoints = [];
						var pointCal = $scope.userPoints;
						$scope.calculatePoints(choicespoints[0].points[0].user[0], pointCal, negate)
						$timeout(function(){
							$scope.userPoints = pointCal
						},10);

						if(!$scope.providerPoints)
							$scope.providerPoints = [];
						var pointCal1 = $scope.providerPoints;
						$scope.calculatePoints(choicespoints[0].points[0].provider[0], pointCal1, negate)
						$timeout(function(){
							$scope.providerPoints = pointCal1
						},10);
				}

            });

			$scope.calculatePoints = function(pointArray, pointCal, negate){
				_.forEach(_.pairs(pointArray), function(data){
					var existing = _.where(pointCal, {type : data[0]});
					if(existing.length == 0)
						pointCal.push({type : data[0], score : (negate ? -1 : 1)  * data[1]});
					else
						existing[0].score = existing[0].score + (negate ? -1 : 1)  * data[1]
				})
			}

			$scope.sumPoints = function(existingPoints, type, entity){

				if(entity == 'user'){
					if($scope.userChoicePoints){
						var points = _.where($scope.userChoicePoints, {type : type})[0];
						return existingPoints + points.score;
					}
					return existingPoints;
				}

				if(entity == 'provider'){
					if($scope.providerChoicePoints){
						var points = _.where($scope.providerChoicePoints, {type : type})[0];
						console.log(points, 'provider');
						return existingPoints + points.score ;
					}
					return existingPoints;
				}
			}

			Object.defineProperty(Array.prototype, "remove", {
			    enumerable: false,
			    value: function (item) {
			        var removeCounter = 0;

			        for (var index = 0; index < this.length; index++) {
			            if (this[index] === item) {
			                this.splice(index, 1);
			                removeCounter++;
			                index--;
			            }
			        }
			        return removeCounter;
			    }
			});

   }]);
});
<|endoftext|>


<commit_before>Cordova = {};

if (Meteor.isCordova) {

    NotificationClient = function(options) {

        if (!options || !options.senderId) {
            throw new Meteor.Error('required_options', 'senderId must be supplied as an option as a minimum');
        }

        var instance = {};

        var successHandler = options.successHandler || function(data) {
            console.log("Success: " + JSON.stringify(data));
        };

        var errorHandler = options.errorHandler || function(e) {
            console.log("Error " + e);
        };

        var messageHandler = options.messageHandler || function(payload, foreground, coldstart) {
            if (!payload) return null;
            if (foreground && !coldstart) {
                navigator.notification.alert(
                    payload.message,
                    options.alertCallback,
                    payload.title
                );
            } else {
                window.plugin.notification.local.add(
                    _.extend(options.notificationOptions, {
                        message: payload.message,
                        title: payload.title,
                        autoCancel: true
                    })
                );
            }
        };

        Cordova.onNotificationGCM = options.onNotificationGCM || function(res) {
            if (res.event === 'registered') {
                if (res.regid) {
                    Meteor.call('cordova-notifications/updateRegid', res.regid, options.registeredCallback);
                }
            } else if (res.event === 'message') {
                messageHandler(res.payload, res.foreground, res.coldstart);
            }
        }

        Tracker.autorun(function(c) {

            if (Meteor.user()) {
                if (device.platform.toLowerCase() === 'android') {
                    window.plugins.pushNotification.register(successHandler, errorHandler, {
                        "senderID": options.senderId.toString(),
                        "ecb": "Cordova.onNotificationGCM"
                    });
                } else {
                    // TODO: APN HANDLER REGISTRATION HERE
                }
            }
        });

        return instance

    }

} else if (Meteor.isServer) {

    NotificationClient = function(options) {

        if (!options || !options.gcmAuthorization || !options.senderId) {
            return false;
        }

        var Future = Npm.require('fibers/future'),
            instance = {};

        instance.sendNotification = function(users, data) {

            if (typeof users === 'string')
                users = Meteor.users.find(users).fetch();
            else if (typeof users === "object" && users._id)
                users = [users];
            else if (users instanceof Mongo.Cursor)
                users = users.fetch()
            else if (!users instanceof Array)
                throw new Meteor.Error('bad_users_argument', 'Supplied user(s) data is not one of: user id, user object, cursor, array of user objects.');

            var regids = _.without(
                    _.pluck(users, 'regid'),
                    undefined),
                payload = {
                    registration_ids: regids,
                    data: data
                },
                headers = {
                    'Content-Type': 'application/json',
                    'Authorization': 'key=' + options.gcmAuthorization
                },
                url = "https://android.googleapis.com/gcm/send",
                fut = new Future();

            if (regids.length) {
                HTTP.post(url, {
                        headers: headers,
                        data: payload
                    },
                    function(err, res) {
                        if (err) {
                            fut.throw(err);
                        } else {
                            fut.return({
                                response: res,
                                userCount: regids.length
                            });
                        }
                    }
                );
            } else {
                fut.return(null);
            }

            return fut.wait();

        };

        Meteor.methods({
            'cordova-notifications/updateRegid': function(regid) {
                check && check(regid, String);
                Meteor.users.update(this.userId, {
                    $set: {
                        regid: regid
                    }
                });
            }
        });

        if (options.removeOnLogout) {

            Meteor.users.find({
                "status.online": true
            }).observe({
                removed: function(user) {
                    Meteor.users.update(user._id, {
                        $unset: {
                            regid: true
                        }
                    });
                }
            });

        }

        return instance;

    }

} else {

    NotificationClient = function() {};

}
<commit_msg>minor bug fix<commit_after>Cordova = {};

if (Meteor.isCordova) {

    NotificationClient = function(options) {

        if (!options || !options.senderId) {
            throw new Meteor.Error('required_options', 'senderId must be supplied as an option as a minimum');
        }

        var instance = {};

        var successHandler = options.successHandler || function(data) {
            console.log("Success: " + JSON.stringify(data));
        };

        var errorHandler = options.errorHandler || function(e) {
            console.log("Error " + e);
        };

        var messageHandler = options.messageHandler || function(payload, foreground, coldstart) {
            if (!payload) return null;
            if (foreground && !coldstart) {
                navigator.notification.alert(
                    payload.message,
                    options.alertCallback,
                    payload.title
                );
            } else {
                window.plugin.notification.local.add(
                    _.extend({}, options.notificationOptions, {
                        message: payload.message,
                        title: payload.title,
                        autoCancel: true
                    })
                );
            }
        };

        Cordova.onNotificationGCM = options.onNotificationGCM || function(res) {
            if (res.event === 'registered') {
                if (res.regid) {
                    Meteor.call('cordova-notifications/updateRegid', res.regid, options.registeredCallback);
                }
            } else if (res.event === 'message') {
                messageHandler(res.payload, res.foreground, res.coldstart);
            }
        }

        Tracker.autorun(function(c) {

            if (Meteor.user()) {
                if (device.platform.toLowerCase() === 'android') {
                    window.plugins.pushNotification.register(successHandler, errorHandler, {
                        "senderID": options.senderId.toString(),
                        "ecb": "Cordova.onNotificationGCM"
                    });
                } else {
                    // TODO: APN HANDLER REGISTRATION HERE
                }
            }
        });

        return instance

    }

} else if (Meteor.isServer) {

    NotificationClient = function(options) {

        if (!options || !options.gcmAuthorization || !options.senderId) {
            return false;
        }

        var Future = Npm.require('fibers/future'),
            instance = {};

        instance.sendNotification = function(users, data) {

            if (typeof users === 'string')
                users = Meteor.users.find(users).fetch();
            else if (typeof users === "object" && users._id)
                users = [users];
            else if (users instanceof Mongo.Cursor)
                users = users.fetch()
            else if (!users instanceof Array)
                throw new Meteor.Error('bad_users_argument', 'Supplied user(s) data is not one of: user id, user object, cursor, array of user objects.');

            var regids = _.without(
                    _.pluck(users, 'regid'),
                    undefined),
                payload = {
                    registration_ids: regids,
                    data: data
                },
                headers = {
                    'Content-Type': 'application/json',
                    'Authorization': 'key=' + options.gcmAuthorization
                },
                url = "https://android.googleapis.com/gcm/send",
                fut = new Future();

            if (regids.length) {
                HTTP.post(url, {
                        headers: headers,
                        data: payload
                    },
                    function(err, res) {
                        if (err) {
                            fut.throw(err);
                        } else {
                            fut.return({
                                response: res,
                                userCount: regids.length
                            });
                        }
                    }
                );
            } else {
                fut.return(null);
            }

            return fut.wait();

        };

        Meteor.methods({
            'cordova-notifications/updateRegid': function(regid) {
                check && check(regid, String);
                Meteor.users.update(this.userId, {
                    $set: {
                        regid: regid
                    }
                });
            }
        });

        if (options.removeOnLogout) {

            Meteor.users.find({
                "status.online": true
            }).observe({
                removed: function(user) {
                    Meteor.users.update(user._id, {
                        $unset: {
                            regid: true
                        }
                    });
                }
            });

        }

        return instance;

    }

} else {

    NotificationClient = function() {};

}
<|endoftext|>


<commit_before>/*
 * This file is part of the Spludo Framework.
 * Copyright (c) 2009 DracoBlue, http://dracoblue.net/
 *
 * Licensed under the terms of MIT License. For the full copyright and license
 * information, please see the LICENSE file in the root folder.
 */

var sys = require('sys');

Logging = function() {};
Logging.prototype = {
    'log' : function() {
        sys.debug("LOG : " + sys.inspect(arguments));
    },
    'info' : function() {
        sys.debug("INFO: " + sys.inspect(arguments));
    }
}
<commit_msg>Refactored Logging. It now uses a single method, which is also capable of formating a String and a single argument prettier.<commit_after>/*
 * This file is part of the Spludo Framework.
 * Copyright (c) 2009 DracoBlue, http://dracoblue.net/
 *
 * Licensed under the terms of MIT License. For the full copyright and license
 * information, please see the LICENSE file in the root folder.
 */

var sys = require('sys');

var logWithPrefix = function(prefix, args) {
    var message = "null";
    if (args.length > 1) {
        message = sys.inspect(args);
    }

    if (args.length === 1) {
        message = (typeof args[0] === "string") ? args[0] : sys.inspect(args[0]);
    }

    sys.debug(prefix + ': ' + message);

}

Logging = function() {};
Logging.prototype = {
    'log' : function() {
        logWithPrefix("LOG ", arguments);
    },
    'info' : function() {
        logWithPrefix("INFO", arguments);
    }
}
<|endoftext|>


<commit_before>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2011 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Core JavaScript library for Blockly.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * The top level namespace used to access the Blockly library.
 * @namespace Blockly
 */
goog.provide('Blockly');

goog.require('Blockly.BlockSvg.render');
goog.require('Blockly.Events');
goog.require('Blockly.FieldAngle');
goog.require('Blockly.FieldCheckbox');
goog.require('Blockly.FieldColour');
// Date picker commented out since it increases footprint by 60%.
// Add it only if you need it.
//goog.require('Blockly.FieldDate');
goog.require('Blockly.FieldDropdown');
goog.require('Blockly.FieldImage');
goog.require('Blockly.FieldTextInput');
goog.require('Blockly.FieldNumber');
goog.require('Blockly.FieldVariable');
goog.require('Blockly.Generator');
goog.require('Blockly.Msg');
goog.require('Blockly.Procedures');
goog.require('Blockly.Toolbox');
goog.require('Blockly.Touch');
goog.require('Blockly.WidgetDiv');
goog.require('Blockly.WorkspaceSvg');
goog.require('Blockly.constants');
goog.require('Blockly.inject');
goog.require('Blockly.utils');
goog.require('goog.color');
goog.require('goog.userAgent');

// Turn off debugging when compiled.
var CLOSURE_DEFINES = {'goog.DEBUG': false};

/**
 * The main workspace most recently used.
 * Set by Blockly.WorkspaceSvg.prototype.markFocused
 * @type {Blockly.Workspace}
 */
Blockly.mainWorkspace = null;

/**
 * Currently selected block.
 * @type {Blockly.Block}
 */
Blockly.selected = null;

/**
 * Currently highlighted connection (during a drag).
 * @type {Blockly.Connection}
 * @private
 */
Blockly.highlightedConnection_ = null;

/**
 * Connection on dragged block that matches the highlighted connection.
 * @type {Blockly.Connection}
 * @private
 */
Blockly.localConnection_ = null;

/**
 * All of the connections on blocks that are currently being dragged.
 * @type {!Array.<!Blockly.Connection>}
 * @private
 */
Blockly.draggingConnections_ = [];

/**
 * Contents of the local clipboard.
 * @type {Element}
 * @private
 */
Blockly.clipboardXml_ = null;

/**
 * Source of the local clipboard.
 * @type {Blockly.WorkspaceSvg}
 * @private
 */
Blockly.clipboardSource_ = null;

/**
 * Is the mouse dragging a block?
 * DRAG_NONE - No drag operation.
 * DRAG_STICKY - Still inside the sticky DRAG_RADIUS.
 * DRAG_FREE - Freely draggable.
 * @private
 */
Blockly.dragMode_ = Blockly.DRAG_NONE;

/**
 * Map from function names to callbacks, for deciding what to do when a button
 * is clicked.
 * @type {!Object<string, function(!Blockly.FlyoutButton)}
 */
Blockly.flyoutButtonCallbacks_ = {};

/**
 * Register a callback function associated with a given key, for clicks on
 * buttons and labels in the flyout.
 * For instance, a button specified by the XML
 * <button text="create variable" callbackKey="CREATE_VARIABLE"></button>
 * should be matched by a call to
 * registerButtonCallback("CREATE_VARIABLE", yourCallbackFunction).
 * @param {string} key The name to use to look up this function.
 * @param {function(!Blockly.FlyoutButton)} func The function to call when the
 *     given button is clicked.
 */
Blockly.registerButtonCallback = function(key, func) {
  Blockly.flyoutButtonCallbacks_[key] = func;
};

/**
 * Convert a hue (HSV model) into an RGB hex triplet.
 * @param {number} hue Hue on a colour wheel (0-360).
 * @return {string} RGB code, e.g. '#5ba65b'.
 */
Blockly.hueToRgb = function(hue) {
  return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION,
      Blockly.HSV_VALUE * 255);
};

/**
 * Returns the dimensions of the specified SVG image.
 * @param {!Element} svg SVG image.
 * @return {!Object} Contains width and height properties.
 */
Blockly.svgSize = function(svg) {
  return {width: svg.cachedWidth_,
          height: svg.cachedHeight_};
};

/**
 * Size the workspace when the contents change.  This also updates
 * scrollbars accordingly.
 * @param {!Blockly.WorkspaceSvg} workspace The workspace to resize.
 */
Blockly.resizeSvgContents = function(workspace) {
  workspace.resizeContents();
};

/**
 * Size the SVG image to completely fill its container. Call this when the view
 * actually changes sizes (e.g. on a window resize/device orientation change).
 * See Blockly.resizeSvgContents to resize the workspace when the contents
 * change (e.g. when a block is added or removed).
 * Record the height/width of the SVG image.
 * @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG.
 */
Blockly.svgResize = function(workspace) {
  var mainWorkspace = workspace;
  while (mainWorkspace.options.parentWorkspace) {
    mainWorkspace = mainWorkspace.options.parentWorkspace;
  }
  var svg = mainWorkspace.getParentSvg();
  var div = svg.parentNode;
  if (!div) {
    // Workspace deleted, or something.
    return;
  }
  var width = div.offsetWidth;
  var height = div.offsetHeight;
  if (svg.cachedWidth_ != width) {
    svg.setAttribute('width', width + 'px');
    svg.cachedWidth_ = width;
  }
  if (svg.cachedHeight_ != height) {
    svg.setAttribute('height', height + 'px');
    svg.cachedHeight_ = height;
  }
  mainWorkspace.resize();
};

/**
 * Handle a key-down on SVG drawing surface.
 * @param {!Event} e Key down event.
 * @private
 */
Blockly.onKeyDown_ = function(e) {
  if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) {
    // No key actions on readonly workspaces.
    // When focused on an HTML text input widget, don't trap any keys.
    return;
  }
  var deleteBlock = false;
  if (e.keyCode == 27) {
    // Pressing esc closes the context menu.
    Blockly.hideChaff();
  } else if (e.keyCode == 8 || e.keyCode == 46) {
    // Delete or backspace.
    // Stop the browser from going back to the previous page.
    // Do this first to prevent an error in the delete code from resulting in
    // data loss.
    e.preventDefault();
    if (Blockly.selected && Blockly.selected.isDeletable()) {
      deleteBlock = true;
    }
  } else if (e.altKey || e.ctrlKey || e.metaKey) {
    if (Blockly.selected &&
        Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
      if (e.keyCode == 67) {
        // 'c' for copy.
        Blockly.hideChaff();
        Blockly.copy_(Blockly.selected);
      } else if (e.keyCode == 88) {
        // 'x' for cut.
        Blockly.copy_(Blockly.selected);
        deleteBlock = true;
      }
    }
    if (e.keyCode == 86) {
      // 'v' for paste.
      if (Blockly.clipboardXml_) {
        Blockly.Events.setGroup(true);
        Blockly.clipboardSource_.paste(Blockly.clipboardXml_);
        Blockly.Events.setGroup(false);
      }
    } else if (e.keyCode == 90) {
      // 'z' for undo 'Z' is for redo.
      Blockly.hideChaff();
      Blockly.mainWorkspace.undo(e.shiftKey);
    }
  }
  if (deleteBlock) {
    // Common code for delete and cut.
    Blockly.Events.setGroup(true);
    Blockly.hideChaff();
    var heal = Blockly.dragMode_ != Blockly.DRAG_FREE;
    Blockly.selected.dispose(heal, true);
    if (Blockly.highlightedConnection_) {
      Blockly.highlightedConnection_.unhighlight();
      Blockly.highlightedConnection_ = null;
    }
    Blockly.Events.setGroup(false);
  }
};

/**
 * Stop binding to the global mouseup and mousemove events.
 * @private
 */
Blockly.terminateDrag_ = function() {
  Blockly.BlockSvg.terminateDrag();
  Blockly.Flyout.terminateDrag_();
};

/**
 * Copy a block onto the local clipboard.
 * @param {!Blockly.Block} block Block to be copied.
 * @private
 */
Blockly.copy_ = function(block) {
  var xmlBlock = Blockly.Xml.blockToDom(block);
  if (Blockly.dragMode_ != Blockly.DRAG_FREE) {
    Blockly.Xml.deleteNext(xmlBlock);
  }
  // Encode start position in XML.
  var xy = block.getRelativeToSurfaceXY();
  xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x);
  xmlBlock.setAttribute('y', xy.y);
  Blockly.clipboardXml_ = xmlBlock;
  Blockly.clipboardSource_ = block.workspace;
};

/**
 * Duplicate this block and its children.
 * @param {!Blockly.Block} block Block to be copied.
 * @private
 */
Blockly.duplicate_ = function(block) {
  // Save the clipboard.
  var clipboardXml = Blockly.clipboardXml_;
  var clipboardSource = Blockly.clipboardSource_;

  // Create a duplicate via a copy/paste operation.
  Blockly.copy_(block);
  block.workspace.paste(Blockly.clipboardXml_);

  // Restore the clipboard.
  Blockly.clipboardXml_ = clipboardXml;
  Blockly.clipboardSource_ = clipboardSource;
};

/**
 * Cancel the native context menu, unless the focus is on an HTML input widget.
 * @param {!Event} e Mouse down event.
 * @private
 */
Blockly.onContextMenu_ = function(e) {
  if (!Blockly.isTargetInput_(e)) {
    // When focused on an HTML text input widget, don't cancel the context menu.
    e.preventDefault();
  }
};

/**
 * Close tooltips, context menus, dropdown selections, etc.
 * @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
 */
Blockly.hideChaff = function(opt_allowToolbox) {
  Blockly.Tooltip.hide();
  Blockly.WidgetDiv.hide();
  if (!opt_allowToolbox) {
    var workspace = Blockly.getMainWorkspace();
    if (workspace.toolbox_ &&
        workspace.toolbox_.flyout_ &&
        workspace.toolbox_.flyout_.autoClose) {
      workspace.toolbox_.clearSelection();
    }
  }
};

/**
 * When something in Blockly's workspace changes, call a function.
 * @param {!Function} func Function to call.
 * @return {!Array.<!Array>} Opaque data that can be passed to
 *     removeChangeListener.
 * @deprecated April 2015
 */
Blockly.addChangeListener = function(func) {
  // Backwards compatability from before there could be multiple workspaces.
  console.warn('Deprecated call to Blockly.addChangeListener, ' +
               'use workspace.addChangeListener instead.');
  return Blockly.getMainWorkspace().addChangeListener(func);
};

/**
 * Returns the main workspace.  Returns the last used main workspace (based on
 * focus).  Try not to use this function, particularly if there are multiple
 * Blockly instances on a page.
 * @return {!Blockly.Workspace} The main workspace.
 */
Blockly.getMainWorkspace = function() {
  return Blockly.mainWorkspace;
};

/**
 * Wrapper to window.alert() that app developers may override to
 * provide alternatives to the modal browser window.
 * @param {string} message The message to display to the user.
 * @param {function()=} opt_callback The callback when the alert is dismissed.
 */
Blockly.alert = function(message, opt_callback) {
  window.alert(message);
  if (opt_callback) {
    opt_callback();
  }
};

/**
 * Wrapper to window.confirm() that app developers may override to
 * provide alternatives to the modal browser window.
 * @param {string} message The message to display to the user.
 * @param {!function(boolean)} callback The callback for handling user response.
 */
Blockly.confirm = function(message, callback) {
  callback(window.confirm(message));
};

/**
 * Wrapper to window.prompt() that app developers may override to provide
 * alternatives to the modal browser window. Built-in browser prompts are
 * often used for better text input experience on mobile device. We strongly
 * recommend testing mobile when overriding this.
 * @param {string} message The message to display to the user.
 * @param {string} defaultValue The value to initialize the prompt with.
 * @param {!function(string)} callback The callback for handling user reponse.
 */
Blockly.prompt = function(message, defaultValue, callback) {
  callback(window.prompt(message, defaultValue));
};

// IE9 does not have a console.  Create a stub to stop errors.
if (!goog.global['console']) {
  goog.global['console'] = {
    'log': function() {},
    'warn': function() {}
  };
}

// Export symbols that would otherwise be renamed by Closure compiler.
if (!goog.global['Blockly']) {
  goog.global['Blockly'] = {};
}
goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace;
goog.global['Blockly']['addChangeListener'] = Blockly.addChangeListener;
<commit_msg>Add ability to define blocks with a json array<commit_after>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2011 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Core JavaScript library for Blockly.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * The top level namespace used to access the Blockly library.
 * @namespace Blockly
 */
goog.provide('Blockly');

goog.require('Blockly.BlockSvg.render');
goog.require('Blockly.Events');
goog.require('Blockly.FieldAngle');
goog.require('Blockly.FieldCheckbox');
goog.require('Blockly.FieldColour');
// Date picker commented out since it increases footprint by 60%.
// Add it only if you need it.
//goog.require('Blockly.FieldDate');
goog.require('Blockly.FieldDropdown');
goog.require('Blockly.FieldImage');
goog.require('Blockly.FieldTextInput');
goog.require('Blockly.FieldNumber');
goog.require('Blockly.FieldVariable');
goog.require('Blockly.Generator');
goog.require('Blockly.Msg');
goog.require('Blockly.Procedures');
goog.require('Blockly.Toolbox');
goog.require('Blockly.Touch');
goog.require('Blockly.WidgetDiv');
goog.require('Blockly.WorkspaceSvg');
goog.require('Blockly.constants');
goog.require('Blockly.inject');
goog.require('Blockly.utils');
goog.require('goog.color');
goog.require('goog.userAgent');

// Turn off debugging when compiled.
var CLOSURE_DEFINES = {'goog.DEBUG': false};

/**
 * The main workspace most recently used.
 * Set by Blockly.WorkspaceSvg.prototype.markFocused
 * @type {Blockly.Workspace}
 */
Blockly.mainWorkspace = null;

/**
 * Currently selected block.
 * @type {Blockly.Block}
 */
Blockly.selected = null;

/**
 * Currently highlighted connection (during a drag).
 * @type {Blockly.Connection}
 * @private
 */
Blockly.highlightedConnection_ = null;

/**
 * Connection on dragged block that matches the highlighted connection.
 * @type {Blockly.Connection}
 * @private
 */
Blockly.localConnection_ = null;

/**
 * All of the connections on blocks that are currently being dragged.
 * @type {!Array.<!Blockly.Connection>}
 * @private
 */
Blockly.draggingConnections_ = [];

/**
 * Contents of the local clipboard.
 * @type {Element}
 * @private
 */
Blockly.clipboardXml_ = null;

/**
 * Source of the local clipboard.
 * @type {Blockly.WorkspaceSvg}
 * @private
 */
Blockly.clipboardSource_ = null;

/**
 * Is the mouse dragging a block?
 * DRAG_NONE - No drag operation.
 * DRAG_STICKY - Still inside the sticky DRAG_RADIUS.
 * DRAG_FREE - Freely draggable.
 * @private
 */
Blockly.dragMode_ = Blockly.DRAG_NONE;

/**
 * Map from function names to callbacks, for deciding what to do when a button
 * is clicked.
 * @type {!Object<string, function(!Blockly.FlyoutButton)}
 */
Blockly.flyoutButtonCallbacks_ = {};

/**
 * Register a callback function associated with a given key, for clicks on
 * buttons and labels in the flyout.
 * For instance, a button specified by the XML
 * <button text="create variable" callbackKey="CREATE_VARIABLE"></button>
 * should be matched by a call to
 * registerButtonCallback("CREATE_VARIABLE", yourCallbackFunction).
 * @param {string} key The name to use to look up this function.
 * @param {function(!Blockly.FlyoutButton)} func The function to call when the
 *     given button is clicked.
 */
Blockly.registerButtonCallback = function(key, func) {
  Blockly.flyoutButtonCallbacks_[key] = func;
};

/**
 * Convert a hue (HSV model) into an RGB hex triplet.
 * @param {number} hue Hue on a colour wheel (0-360).
 * @return {string} RGB code, e.g. '#5ba65b'.
 */
Blockly.hueToRgb = function(hue) {
  return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION,
      Blockly.HSV_VALUE * 255);
};

/**
 * Returns the dimensions of the specified SVG image.
 * @param {!Element} svg SVG image.
 * @return {!Object} Contains width and height properties.
 */
Blockly.svgSize = function(svg) {
  return {width: svg.cachedWidth_,
          height: svg.cachedHeight_};
};

/**
 * Size the workspace when the contents change.  This also updates
 * scrollbars accordingly.
 * @param {!Blockly.WorkspaceSvg} workspace The workspace to resize.
 */
Blockly.resizeSvgContents = function(workspace) {
  workspace.resizeContents();
};

/**
 * Size the SVG image to completely fill its container. Call this when the view
 * actually changes sizes (e.g. on a window resize/device orientation change).
 * See Blockly.resizeSvgContents to resize the workspace when the contents
 * change (e.g. when a block is added or removed).
 * Record the height/width of the SVG image.
 * @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG.
 */
Blockly.svgResize = function(workspace) {
  var mainWorkspace = workspace;
  while (mainWorkspace.options.parentWorkspace) {
    mainWorkspace = mainWorkspace.options.parentWorkspace;
  }
  var svg = mainWorkspace.getParentSvg();
  var div = svg.parentNode;
  if (!div) {
    // Workspace deleted, or something.
    return;
  }
  var width = div.offsetWidth;
  var height = div.offsetHeight;
  if (svg.cachedWidth_ != width) {
    svg.setAttribute('width', width + 'px');
    svg.cachedWidth_ = width;
  }
  if (svg.cachedHeight_ != height) {
    svg.setAttribute('height', height + 'px');
    svg.cachedHeight_ = height;
  }
  mainWorkspace.resize();
};

/**
 * Handle a key-down on SVG drawing surface.
 * @param {!Event} e Key down event.
 * @private
 */
Blockly.onKeyDown_ = function(e) {
  if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) {
    // No key actions on readonly workspaces.
    // When focused on an HTML text input widget, don't trap any keys.
    return;
  }
  var deleteBlock = false;
  if (e.keyCode == 27) {
    // Pressing esc closes the context menu.
    Blockly.hideChaff();
  } else if (e.keyCode == 8 || e.keyCode == 46) {
    // Delete or backspace.
    // Stop the browser from going back to the previous page.
    // Do this first to prevent an error in the delete code from resulting in
    // data loss.
    e.preventDefault();
    if (Blockly.selected && Blockly.selected.isDeletable()) {
      deleteBlock = true;
    }
  } else if (e.altKey || e.ctrlKey || e.metaKey) {
    if (Blockly.selected &&
        Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
      if (e.keyCode == 67) {
        // 'c' for copy.
        Blockly.hideChaff();
        Blockly.copy_(Blockly.selected);
      } else if (e.keyCode == 88) {
        // 'x' for cut.
        Blockly.copy_(Blockly.selected);
        deleteBlock = true;
      }
    }
    if (e.keyCode == 86) {
      // 'v' for paste.
      if (Blockly.clipboardXml_) {
        Blockly.Events.setGroup(true);
        Blockly.clipboardSource_.paste(Blockly.clipboardXml_);
        Blockly.Events.setGroup(false);
      }
    } else if (e.keyCode == 90) {
      // 'z' for undo 'Z' is for redo.
      Blockly.hideChaff();
      Blockly.mainWorkspace.undo(e.shiftKey);
    }
  }
  if (deleteBlock) {
    // Common code for delete and cut.
    Blockly.Events.setGroup(true);
    Blockly.hideChaff();
    var heal = Blockly.dragMode_ != Blockly.DRAG_FREE;
    Blockly.selected.dispose(heal, true);
    if (Blockly.highlightedConnection_) {
      Blockly.highlightedConnection_.unhighlight();
      Blockly.highlightedConnection_ = null;
    }
    Blockly.Events.setGroup(false);
  }
};

/**
 * Stop binding to the global mouseup and mousemove events.
 * @private
 */
Blockly.terminateDrag_ = function() {
  Blockly.BlockSvg.terminateDrag();
  Blockly.Flyout.terminateDrag_();
};

/**
 * Copy a block onto the local clipboard.
 * @param {!Blockly.Block} block Block to be copied.
 * @private
 */
Blockly.copy_ = function(block) {
  var xmlBlock = Blockly.Xml.blockToDom(block);
  if (Blockly.dragMode_ != Blockly.DRAG_FREE) {
    Blockly.Xml.deleteNext(xmlBlock);
  }
  // Encode start position in XML.
  var xy = block.getRelativeToSurfaceXY();
  xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x);
  xmlBlock.setAttribute('y', xy.y);
  Blockly.clipboardXml_ = xmlBlock;
  Blockly.clipboardSource_ = block.workspace;
};

/**
 * Duplicate this block and its children.
 * @param {!Blockly.Block} block Block to be copied.
 * @private
 */
Blockly.duplicate_ = function(block) {
  // Save the clipboard.
  var clipboardXml = Blockly.clipboardXml_;
  var clipboardSource = Blockly.clipboardSource_;

  // Create a duplicate via a copy/paste operation.
  Blockly.copy_(block);
  block.workspace.paste(Blockly.clipboardXml_);

  // Restore the clipboard.
  Blockly.clipboardXml_ = clipboardXml;
  Blockly.clipboardSource_ = clipboardSource;
};

/**
 * Cancel the native context menu, unless the focus is on an HTML input widget.
 * @param {!Event} e Mouse down event.
 * @private
 */
Blockly.onContextMenu_ = function(e) {
  if (!Blockly.isTargetInput_(e)) {
    // When focused on an HTML text input widget, don't cancel the context menu.
    e.preventDefault();
  }
};

/**
 * Close tooltips, context menus, dropdown selections, etc.
 * @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
 */
Blockly.hideChaff = function(opt_allowToolbox) {
  Blockly.Tooltip.hide();
  Blockly.WidgetDiv.hide();
  if (!opt_allowToolbox) {
    var workspace = Blockly.getMainWorkspace();
    if (workspace.toolbox_ &&
        workspace.toolbox_.flyout_ &&
        workspace.toolbox_.flyout_.autoClose) {
      workspace.toolbox_.clearSelection();
    }
  }
};

/**
 * When something in Blockly's workspace changes, call a function.
 * @param {!Function} func Function to call.
 * @return {!Array.<!Array>} Opaque data that can be passed to
 *     removeChangeListener.
 * @deprecated April 2015
 */
Blockly.addChangeListener = function(func) {
  // Backwards compatability from before there could be multiple workspaces.
  console.warn('Deprecated call to Blockly.addChangeListener, ' +
               'use workspace.addChangeListener instead.');
  return Blockly.getMainWorkspace().addChangeListener(func);
};

/**
 * Returns the main workspace.  Returns the last used main workspace (based on
 * focus).  Try not to use this function, particularly if there are multiple
 * Blockly instances on a page.
 * @return {!Blockly.Workspace} The main workspace.
 */
Blockly.getMainWorkspace = function() {
  return Blockly.mainWorkspace;
};

/**
 * Wrapper to window.alert() that app developers may override to
 * provide alternatives to the modal browser window.
 * @param {string} message The message to display to the user.
 * @param {function()=} opt_callback The callback when the alert is dismissed.
 */
Blockly.alert = function(message, opt_callback) {
  window.alert(message);
  if (opt_callback) {
    opt_callback();
  }
};

/**
 * Wrapper to window.confirm() that app developers may override to
 * provide alternatives to the modal browser window.
 * @param {string} message The message to display to the user.
 * @param {!function(boolean)} callback The callback for handling user response.
 */
Blockly.confirm = function(message, callback) {
  callback(window.confirm(message));
};

/**
 * Wrapper to window.prompt() that app developers may override to provide
 * alternatives to the modal browser window. Built-in browser prompts are
 * often used for better text input experience on mobile device. We strongly
 * recommend testing mobile when overriding this.
 * @param {string} message The message to display to the user.
 * @param {string} defaultValue The value to initialize the prompt with.
 * @param {!function(string)} callback The callback for handling user reponse.
 */
Blockly.prompt = function(message, defaultValue, callback) {
  callback(window.prompt(message, defaultValue));
};

/**
 * Helper function for defining a block from json.  The resulting function has
 * the correct value of jsonDef at the point in code where jsonInit is called.
 * @param {Object} jsonDef The JSON definition of a block.
 * @return {function} A function that calls jsonInit with the correct value
 *     of jsonDef.
 */
Blockly.jsonInitFactory_ = function(jsonDef) {
  return function() {
    this.jsonInit(jsonDef);
  };
};

/**
 * Define blocks from an array of JSON block definitions, as might be generated
 * by the Blockly Developer Tools.
 * @param {!Array<Object>} jsonArray An array of JSON block definitions.
 */
Blockly.defineBlocksWithJsonArray = function(jsonArray) {
  for (var index = 0; index < jsonArray.length; index++) {
    var elem = jsonArray[index];
    Blockly.Blocks[elem.type] = {
      init: Blockly.jsonInitFactory_(elem)
    };
  }
};

// IE9 does not have a console.  Create a stub to stop errors.
if (!goog.global['console']) {
  goog.global['console'] = {
    'log': function() {},
    'warn': function() {}
  };
}

// Export symbols that would otherwise be renamed by Closure compiler.
if (!goog.global['Blockly']) {
  goog.global['Blockly'] = {};
}
goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace;
goog.global['Blockly']['addChangeListener'] = Blockly.addChangeListener;
<|endoftext|>


<commit_before>const Immutable = require('immutable');

let Store = Immutable.Map({
    listeningElements: [],
    firstDownPosition: {
        x: 0,
        y: 0
    },
    keyboardPressing: "",
    selectedElements: new Set()
});
let selectedElementsHandler = () => {};
let deselectedAllElementsHandler = () => {};

const selectBoxDOM = document.createElement('div');
selectBoxDOM.className = 'select-box';

const handleMouseDown = event => {
    let downInElements = false;
    const firstDownPosition = {
        x: event.clientX,
        y: event.clientY
    }

    Store = Store.set('firstDownPosition', firstDownPosition);
    if (Store.get('keyboardPressing') === "") {
        Store = Store.set('selectedElements', new Set());
        deselectedAllElementsHandler();
    }

    Store.get('listeningElements').toJS().forEach(element => {
        if (
            firstDownPosition.x > element.topLeftX
            && firstDownPosition.x < element.bottomRightX
            && firstDownPosition.y > element.topLeftY
            && firstDownPosition.y < element.bottomRightY
        )
            downInElements = true;
    });

    if (!downInElements)
        document.body.addEventListener('mousemove', handleMouseMove);
}

const handleMouseMove = event => {

    const firstDownPosition = Store.get('firstDownPosition');
    const keyboardPressing = Store.get('keyboardPressing');

    const selectBoxDOM = document.getElementsByClassName('select-box')[0];
    let selectBox = selectBoxDOM.getBoundingClientRect();
    let newSelectedElements = new Set();
    let deselectedElements = new Set();
    let selectedElements = Store.get('selectedElements');

    selectBoxDOM.style.display = "block";
    selectBoxDOM.style.left = (event.clientX < firstDownPosition.x ? event.clientX : firstDownPosition.x) + 'px';
    selectBoxDOM.style.top = (event.clientY < firstDownPosition.y ? event.clientY : firstDownPosition.y) + 'px';
    selectBoxDOM.style.width = (event.clientX > firstDownPosition.x ? event.clientX - firstDownPosition.x : firstDownPosition.x - event.clientX) + 'px';
    selectBoxDOM.style.height = (event.clientY > firstDownPosition.y ? event.clientY - firstDownPosition.y : firstDownPosition.y - event.clientY) + 'px';

    selectBox = {
        topLeftX: +selectBox.left.toFixed(0),
        topLeftY: +selectBox.top.toFixed(0),
        topRightX: +selectBox.left.toFixed(0) + +selectBox.width.toFixed(0),
        topRightY: +selectBox.top.toFixed(0),
        bottomLeftX: +selectBox.left.toFixed(0),
        bottomLeftY: +selectBox.top.toFixed(0) + (+selectBox.height.toFixed(0)),
        bottomRightX: +selectBox.left.toFixed(0) + (+selectBox.width.toFixed(0)),
        bottomRightY: +selectBox.top.toFixed(0) + (+selectBox.height.toFixed(0))
    }

    Store.get('listeningElements').toJS().forEach(element => {
        if ((
            element.topLeftX > selectBox.topLeftX
            && element.topLeftY > selectBox.topLeftY
            && element.topLeftX < selectBox.bottomRightX
            && element.topLeftY < selectBox.bottomRightY
        ) || (
            element.topRightX > selectBox.bottomLeftX
            && element.topRightY < selectBox.bottomLeftY
            && element.topRightX < selectBox.topRightX
            && element.topRightY > selectBox.topRightY
        ) || (
            element.bottomLeftX > selectBox.topLeftX
            && element.bottomLeftY > selectBox.topLeftY
            && element.bottomLeftX < selectBox.bottomRightX
            && element.bottomLeftY < selectBox.bottomRightY
        ) || (
            element.bottomRightX > selectBox.topLeftX
            && element.bottomRightY > selectBox.topLeftY
            && element.bottomRightX < selectBox.bottomRightX
            && element.bottomRightY < selectBox.bottomRightY
        ) || (
            element.topLeftX < selectBox.topLeftX
            && element.topLeftY > selectBox.topLeftY
            && element.topRightX > selectBox.bottomRightX
            && element.topRightY < selectBox.bottomRightY
        ) || (
            element.topRightX > selectBox.topLeftX
            && element.topRightY < selectBox.topLeftY
            && element.bottomRightX < selectBox.bottomRightX
            && element.bottomRightY > selectBox.bottomRightY
        ) || (
            element.bottomLeftX < selectBox.topLeftX
            && element.bottomLeftY > selectBox.topLeftY
            && element.bottomRightX > selectBox.bottomRightX
            && element.bottomRightY < selectBox.bottomRightY
        ) || (
            element.topLeftX > selectBox.topLeftX
            && element.topLeftY < selectBox.topLeftY
            && element.bottomLeftX < selectBox.bottomRightX
            && element.bottomLeftY > selectBox.bottomRightY
        )) {
            if (keyboardPressing === "Alt") {
                deselectedElements = deselectedElements.add(element.id);
            } else if ( !selectedElements.has(element.id) ) {
                newSelectedElements = newSelectedElements.add(element.id);
            }
        } else if (selectedElements.has(element.id) && keyboardPressing === "") {
            deselectedElements = deselectedElements.add(element.id);
        }
    });

    if (newSelectedElements.size || deselectedElements.size) {
        selectedElements = new Set([...selectedElements, ...newSelectedElements]);
        selectedElements = [...selectedElements].filter(
            element => !deselectedElements.has(element)
        )
        selectedElements = new Set(selectedElements);
        Store = Store.set('selectedElements', selectedElements);
        selectedElementsHandler(Array.from(selectedElements));
    }
}

const handleMouseUp = event => {

    document.body.removeEventListener('mousemove', handleMouseMove);
    if (Store.get('selectedElements').size === 0)
        deselectedAllElementsHandler();

    Store = Store.set('firstDownPosition', {
        x: 0,
        y: 0
    });

    selectBoxDOM.removeAttribute('style');
}

const addSelectbleElements = (elements, flag) => {
    Store = Store.set('listeningElements', Immutable.List(
        elements.map(element => {
            const elementBoundingClientRect = element.getBoundingClientRect()
            return {
                id: element.dataset[flag],
                topLeftX: +elementBoundingClientRect.left.toFixed(0),
                topLeftY: +elementBoundingClientRect.top.toFixed(0),
                topRightX: +elementBoundingClientRect.left.toFixed(0) + +elementBoundingClientRect.width.toFixed(0),
                topRightY: +elementBoundingClientRect.top.toFixed(0),
                bottomLeftX: +elementBoundingClientRect.left.toFixed(0),
                bottomLeftY: +elementBoundingClientRect.top.toFixed(0) + (+elementBoundingClientRect.height.toFixed(0)),
                bottomRightX: +elementBoundingClientRect.left.toFixed(0) + (+elementBoundingClientRect.width.toFixed(0)),
                bottomRightY: +elementBoundingClientRect.top.toFixed(0) + (+elementBoundingClientRect.height.toFixed(0))
            }
        })
    ));
}

const handleKeyDown = event => {
    Store = Store.set('keyboardPressing', event.keyIdentifier);
}

const handleKeyUp = event => {
    Store = Store.set('keyboardPressing', "");
}

const startListen = () => {

    window.addEventListener('keydown', handleKeyDown);
    window.addEventListener('keyup', handleKeyUp);

    document.body.appendChild(selectBoxDOM);
    document.body.addEventListener('mousedown', handleMouseDown);
    document.body.addEventListener('mouseup', handleMouseUp);
}

const endListen = () => {

    window.removeEventListener('keydown', handleKeyDown);
    window.removeEventListener('keyup', handleKeyUp);

    document.body.removeEventListener('mousedown', handleMouseDown);
    document.body.removeEventListener('mouseup', handleMouseUp);
    if (document.getElementsByClassName('select-box')[0])
        document.body.removeChild(selectBoxDOM);

    selectedElementsHandler = () => {};
    deselectedAllElementsHandler = () => {};
}

module.exports = {
    addSelectbleElements,
    startListen,
    endListen,
    setSelectedElementsHandler: func => {
        selectedElementsHandler = func;
    },
    setDeselecteAllElementsHandler: func => {
        deselectedAllElementsHandler = func
    }
}<commit_msg>update rectangle_selection/index.js<commit_after>const Immutable = require('immutable');

let Store = Immutable.Map({
    listeningElements: [],
    firstDownPosition: {
        x: 0,
        y: 0
    },
    keyboardPressing: "",
    selectedElements: new Set()
});
let selectedElementsHandler = () => {};
let deselectedAllElementsHandler = () => {};

const selectBoxDOM = document.createElement('div');
selectBoxDOM.className = 'select-box';

const handleMouseDown = event => {
    let downInElements = false;
    const firstDownPosition = {
        x: event.clientX,
        y: event.clientY
    }

    Store = Store.set('firstDownPosition', firstDownPosition);
    if (Store.get('keyboardPressing') === "")
        Store = Store.set('selectedElements', new Set());

    Store.get('listeningElements').toJS().forEach(element => {
        if (
            firstDownPosition.x > element.topLeftX
            && firstDownPosition.x < element.bottomRightX
            && firstDownPosition.y > element.topLeftY
            && firstDownPosition.y < element.bottomRightY
        )
            downInElements = true;
    });

    if (!downInElements) {
        deselectedAllElementsHandler();
        document.body.addEventListener('mousemove', handleMouseMove);
    }
}

const handleMouseMove = event => {

    const firstDownPosition = Store.get('firstDownPosition');
    const keyboardPressing = Store.get('keyboardPressing');

    const selectBoxDOM = document.getElementsByClassName('select-box')[0];
    let selectBox = selectBoxDOM.getBoundingClientRect();
    let newSelectedElements = new Set();
    let deselectedElements = new Set();
    let selectedElements = Store.get('selectedElements');

    selectBoxDOM.style.display = "block";
    selectBoxDOM.style.left = (event.clientX < firstDownPosition.x ? event.clientX : firstDownPosition.x) + 'px';
    selectBoxDOM.style.top = (event.clientY < firstDownPosition.y ? event.clientY : firstDownPosition.y) + 'px';
    selectBoxDOM.style.width = (event.clientX > firstDownPosition.x ? event.clientX - firstDownPosition.x : firstDownPosition.x - event.clientX) + 'px';
    selectBoxDOM.style.height = (event.clientY > firstDownPosition.y ? event.clientY - firstDownPosition.y : firstDownPosition.y - event.clientY) + 'px';

    selectBox = {
        topLeftX: +selectBox.left.toFixed(0),
        topLeftY: +selectBox.top.toFixed(0),
        topRightX: +selectBox.left.toFixed(0) + +selectBox.width.toFixed(0),
        topRightY: +selectBox.top.toFixed(0),
        bottomLeftX: +selectBox.left.toFixed(0),
        bottomLeftY: +selectBox.top.toFixed(0) + (+selectBox.height.toFixed(0)),
        bottomRightX: +selectBox.left.toFixed(0) + (+selectBox.width.toFixed(0)),
        bottomRightY: +selectBox.top.toFixed(0) + (+selectBox.height.toFixed(0))
    }

    Store.get('listeningElements').toJS().forEach(element => {
        if ((
            element.topLeftX > selectBox.topLeftX
            && element.topLeftY > selectBox.topLeftY
            && element.topLeftX < selectBox.bottomRightX
            && element.topLeftY < selectBox.bottomRightY
        ) || (
            element.topRightX > selectBox.bottomLeftX
            && element.topRightY < selectBox.bottomLeftY
            && element.topRightX < selectBox.topRightX
            && element.topRightY > selectBox.topRightY
        ) || (
            element.bottomLeftX > selectBox.topLeftX
            && element.bottomLeftY > selectBox.topLeftY
            && element.bottomLeftX < selectBox.bottomRightX
            && element.bottomLeftY < selectBox.bottomRightY
        ) || (
            element.bottomRightX > selectBox.topLeftX
            && element.bottomRightY > selectBox.topLeftY
            && element.bottomRightX < selectBox.bottomRightX
            && element.bottomRightY < selectBox.bottomRightY
        ) || (
            element.topLeftX < selectBox.topLeftX
            && element.topLeftY > selectBox.topLeftY
            && element.topRightX > selectBox.bottomRightX
            && element.topRightY < selectBox.bottomRightY
        ) || (
            element.topRightX > selectBox.topLeftX
            && element.topRightY < selectBox.topLeftY
            && element.bottomRightX < selectBox.bottomRightX
            && element.bottomRightY > selectBox.bottomRightY
        ) || (
            element.bottomLeftX < selectBox.topLeftX
            && element.bottomLeftY > selectBox.topLeftY
            && element.bottomRightX > selectBox.bottomRightX
            && element.bottomRightY < selectBox.bottomRightY
        ) || (
            element.topLeftX > selectBox.topLeftX
            && element.topLeftY < selectBox.topLeftY
            && element.bottomLeftX < selectBox.bottomRightX
            && element.bottomLeftY > selectBox.bottomRightY
        )) {
            if (keyboardPressing === "Alt") {
                deselectedElements = deselectedElements.add(element.id);
            } else if ( !selectedElements.has(element.id) ) {
                newSelectedElements = newSelectedElements.add(element.id);
            }
        } else if (selectedElements.has(element.id) && keyboardPressing === "") {
            deselectedElements = deselectedElements.add(element.id);
        }
    });

    if (newSelectedElements.size || deselectedElements.size) {
        selectedElements = new Set([...selectedElements, ...newSelectedElements]);
        selectedElements = [...selectedElements].filter(
            element => !deselectedElements.has(element)
        )
        selectedElements = new Set(selectedElements);
        Store = Store.set('selectedElements', selectedElements);
        selectedElementsHandler(Array.from(selectedElements));
    }
}

const handleMouseUp = event => {

    document.body.removeEventListener('mousemove', handleMouseMove);
    if (Store.get('selectedElements').size === 0)
        deselectedAllElementsHandler();

    Store = Store.set('firstDownPosition', {
        x: 0,
        y: 0
    });

    selectBoxDOM.removeAttribute('style');
}

const addSelectbleElements = (elements, flag) => {
    Store = Store.set('listeningElements', Immutable.List(
        elements.map(element => {
            const eBCR = element.getBoundingClientRect()
            return {
                id: element.dataset[flag],
                topLeftX: +eBCR.left.toFixed(0),
                topLeftY: +eBCR.top.toFixed(0),
                topRightX: +eBCR.left.toFixed(0) + +eBCR.width.toFixed(0),
                topRightY: +eBCR.top.toFixed(0),
                bottomLeftX: +eBCR.left.toFixed(0),
                bottomLeftY: +eBCR.top.toFixed(0) + +eBCR.height.toFixed(0),
                bottomRightX: +eBCR.left.toFixed(0) + +eBCR.width.toFixed(0),
                bottomRightY: +eBCR.top.toFixed(0) + +eBCR.height.toFixed(0)
            }
        })
    ));
}

const handleKeyDown = event => {
    Store = Store.set('keyboardPressing', event.keyIdentifier);
}

const handleKeyUp = event => {
    Store = Store.set('keyboardPressing', "");
}

const startListen = () => {

    window.addEventListener('keydown', handleKeyDown);
    window.addEventListener('keyup', handleKeyUp);

    document.body.appendChild(selectBoxDOM);
    document.body.addEventListener('mousedown', handleMouseDown);
    document.body.addEventListener('mouseup', handleMouseUp);
}

const endListen = () => {

    window.removeEventListener('keydown', handleKeyDown);
    window.removeEventListener('keyup', handleKeyUp);

    document.body.removeEventListener('mousedown', handleMouseDown);
    document.body.removeEventListener('mouseup', handleMouseUp);
    if (document.getElementsByClassName('select-box')[0])
        document.body.removeChild(selectBoxDOM);

    selectedElementsHandler = () => {};
    deselectedAllElementsHandler = () => {};
}

module.exports = {
    addSelectbleElements,
    startListen,
    endListen,
    setSelectedElementsHandler: func => {
        selectedElementsHandler = func;
    },
    setDeselecteAllElementsHandler: func => {
        deselectedAllElementsHandler = func
    }
}<|endoftext|>


<commit_before>'use strict';

import NearestNeighborIndexProcessor from './NearestNeighborIndexProcessor';

class FixationsDataProcessor {

    constructor() {
        this.fixationsData = [];
        this.nniProcessor = new NearestNeighborIndexProcessor();
    }

    getScreenResolution(fileContentsAsArray)
    {
        let firstLine = fileContentsAsArray[0];
        let resolution = firstLine.split(' ');

        return {
            width: parseInt(resolution[0]),
            height: parseInt(resolution[1])
        }
    }

    process(fileContentsAsArray)
    {
        fileContentsAsArray.splice(0, 1);

        for(let c in fileContentsAsArray) {
            let lineItems = fileContentsAsArray[c].split(' ');

            this.addItemToMinute([lineItems[1], lineItems[2]], this.getMinuteFromTimestamp(parseInt(lineItems[0])));
        }

        for(let i in this.fixationsData) {
            this.fixationsData[i]['nni'] = this.nniProcessor.calculate(this.fixationsData[i].points);
        }

        return this.fixationsData;
    }

    addItemToMinute(point, minute)
    {
        if(this.fixationsData[minute] === undefined) {
            this.fixationsData[minute] = {
                points: [point]
            }
        } else {
            this.fixationsData[minute].points.push(point);
        }
    }

    getMinuteFromTimestamp(timestamp) {
        let minute = Math.floor(timestamp * 1.67 * 0.00001);
        return minute;
    }

}

export default FixationsDataProcessor
<commit_msg>Fix formula as requested in review<commit_after>'use strict';

import NearestNeighborIndexProcessor from './NearestNeighborIndexProcessor';

class FixationsDataProcessor {

    constructor() {
        this.fixationsData = [];
        this.nniProcessor = new NearestNeighborIndexProcessor();
    }

    getScreenResolution(fileContentsAsArray)
    {
        let firstLine = fileContentsAsArray[0];
        let resolution = firstLine.split(' ');

        return {
            width: parseInt(resolution[0]),
            height: parseInt(resolution[1])
        }
    }

    process(fileContentsAsArray)
    {
        fileContentsAsArray.splice(0, 1);

        for(let c in fileContentsAsArray) {
            let lineItems = fileContentsAsArray[c].split(' ');

            this.addItemToMinute([lineItems[1], lineItems[2]], this.getMinuteFromTimestamp(parseInt(lineItems[0])));
        }

        for(let i in this.fixationsData) {
            this.fixationsData[i]['nni'] = this.nniProcessor.calculate(this.fixationsData[i].points);
        }

        return this.fixationsData;
    }

    addItemToMinute(point, minute)
    {
        if(this.fixationsData[minute] === undefined) {
            this.fixationsData[minute] = {
                points: [point]
            }
        } else {
            this.fixationsData[minute].points.push(point);
        }
    }

    getMinuteFromTimestamp(timestamp) {
        let minute = Math.floor(timestamp / 1000 / 60);
        return minute;
    }

}

export default FixationsDataProcessor
<|endoftext|>


<commit_before>jest.mock('src/lib/ga');
jest.mock('src/lib/gql');
jest.mock('src/webhook/lineClient');

import ga from 'src/lib/ga';
import gql from 'src/lib/gql';

import groupMessage, { getValidArticleReply } from '../groupMessage';

import {
  apiResult,
  article as articleFixtures,
} from '../__fixtures__/groupMessage';
import lineClient from 'src/webhook/lineClient';

const event = {
  groupId: 'C904bb9fc2f4904b2facf8204b3f08c79',
  source: { type: 'group' },
};

beforeEach(() => {
  event.input = undefined;
  ga.clearAllMocks();
  gql.__reset();
  lineClient.post.mockClear();
});

describe('groupMessage', () => {
  it('rejects undefined input', async () => {
    await expect(groupMessage(event)).rejects.toMatchInlineSnapshot(
      `[Error: input undefined]`
    );
  });

  it('should not process input length less then 11', async () => {
    event.input = '0123456789';
    gql.__push(apiResult.validArticleAndOneReply);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(false);
    expect(ga.sendMock).toHaveBeenCalledTimes(0);
  });

  it('processes introduction message Hi Cofacts', async () => {
    gql.__push(apiResult.notFound);
    event.input = 'Hi Cofacts';
    let result = await groupMessage(event);
    expect(result.replies).not.toBeUndefined();
    expect(result).toMatchSnapshot();
    event.input = 'hi confacts';
    result = await groupMessage(event);
    expect(result.replies).not.toBeUndefined();
    expect(result).toMatchSnapshot();
    expect(gql.__finished()).toBe(false);

    // don't really care about this result :p
    event.input = 'cofacts';
    expect(await groupMessage(event)).toMatchSnapshot();
  });

  it('processes leave command bye bye cofacts', async () => {
    gql.__push(apiResult.notFound);
    event.input = 'bye bye cofacts';
    let result = await groupMessage(event);
    expect(gql.__finished()).toBe(false);
    expect(result.replies).toBeUndefined();
    expect(lineClient.post).toHaveBeenCalledTimes(1);
  });

  it('should not reply and send ga if article not found', async () => {
    event.input = 'article_not_found';
    gql.__push(apiResult.notFound);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(true);
    expect(ga.sendMock).toHaveBeenCalledTimes(0);
  });

  it('should handle valid article with one category', async () => {
    event.input =
      'WHO 最新研究顯示 Covid-19 其實源自黑暗料理界，即日起正名為「黑料病毒」';
    gql.__push(apiResult.validArticleWithOneCategory);
    const result = await groupMessage(event);
    expect(result.replies).not.toBeUndefined();
    expect(result).toMatchSnapshot();
    expect(gql.__finished()).toBe(true);
    expect(ga.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          undefined,
          "__INIT__",
          "WHO 最新研究顯示 Covid-19 其實源自黑暗料理界，即日起正名為「黑料病毒」",
          "group",
        ],
      ]
    `);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "3nbzf064ks60d",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Article",
            "el": "3nbzf064ks60d",
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Reply",
            "el": "fake-reply-id",
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });

  it('should handle valid article with two categories', async () => {
    event.input = '你知道黑啤愛吃什麼嗎？ 黑啤愛吃蠶寶寶！';
    gql.__push(apiResult.validArticleWithTwoCategories);
    const result = await groupMessage(event);
    expect(result.replies).not.toBeUndefined();
    expect(result).toMatchSnapshot();
    expect(gql.__finished()).toBe(true);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "3nbzf064ks60d",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "8nbzf064ks87g",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "2zn1215x6e70v",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Article",
            "el": "3nbzf064ks60d",
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Reply",
            "el": "fake-reply-id1",
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });

  it('should handle invalid article category feedback', async () => {
    event.input = '你知道嗎？其實黑啤愛吃蠶寶寶哦！';
    gql.__push(apiResult.invalidCategoryFeedback);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(true);
    expect(ga.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          undefined,
          "__INIT__",
          "你知道嗎？其實黑啤愛吃蠶寶寶哦！",
          "group",
        ],
      ]
    `);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "3nbzf064ks60d",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Article",
            "el": "3nbzf064ks60d",
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });

  it('should handle invalid article category', async () => {
    event.input = '以後吃蘋果一定要削皮。';
    gql.__push(apiResult.invalidArticleCategory);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(true);
    expect(ga.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          undefined,
          "__INIT__",
          "以後吃蘋果一定要削皮。",
          "group",
        ],
      ]
    `);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "2zn1215x6e70v",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Article",
            "el": "2zn1215x6e70v",
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });

  it('should handle input is not identical to article ', async () => {
    event.input = '我知道黑啤愛吃蛾哦！';
    gql.__push(apiResult.invalidArticleReply);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(true);
    expect(ga.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          undefined,
          "__INIT__",
          "我知道黑啤愛吃蛾哦！",
          "group",
        ],
      ]
    `);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "3nbzf064ks60d",
            "ni": true,
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });

  it('should handle invalid article reply', async () => {
    event.input = '我不會說我知道黑啤愛吃蠶寶寶哦！';
    gql.__push(apiResult.invalidArticleReply);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(true);
    expect(ga.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          undefined,
          "__INIT__",
          "我不會說我知道黑啤愛吃蠶寶寶哦！",
          "group",
        ],
      ]
    `);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "3nbzf064ks60d",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Article",
            "el": "3nbzf064ks60d",
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });
});

describe('processes articleReplies which article is valid', () => {
  it('invalid reply type', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidReplyType)
    ).toBeUndefined();
  });

  it('invalid reply feedback count', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidReplyFeedbackCount)
    ).toBeUndefined();
  });

  it('invalidTwoReplies1', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidTwoReplies1)
    ).toBeUndefined();
  });

  it('invalidTwoReplies2', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidTwoReplies2)
    ).toBeUndefined();
  });

  it('invalidThreeReplies1', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidThreeReplies1)
    ).toBeUndefined();
  });

  it('invalidThreeReplies2', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidThreeReplies2)
    ).toBeUndefined();
  });

  it('invalidMultipleReplies1', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidMultipleReplies1)
    ).toBeUndefined();
  });
  it('invalidMultipleReplies2', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidMultipleReplies2)
    ).toBeUndefined();
  });
  it('invalidMultipleReplies3', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidMultipleReplies3)
    ).toBeUndefined();
  });

  it('twoReplies1 RUMOR positiveFeedbackCount > Non-RUMOR', () => {
    expect(getValidArticleReply(articleFixtures.twoReplies1)).toMatchSnapshot();
  });

  it('twoReplies2 equal positiveFeedbackCount, but both type are RUMOR', () => {
    expect(getValidArticleReply(articleFixtures.twoReplies2)).toMatchSnapshot();
  });

  it('threeReplies1 should return reply has more positiveFeedbackCount', () => {
    expect(
      getValidArticleReply(articleFixtures.threeReplies1)
    ).toMatchSnapshot();
  });

  it('threeReplies2 should return reply has second highest positiveFeedbackCount', () => {
    expect(
      getValidArticleReply(articleFixtures.threeReplies2)
    ).toMatchSnapshot();
  });

  it('multipleReplies1 should return Rumor reply1 or Rumor reply3', () => {
    expect(
      getValidArticleReply(articleFixtures.multipleReplies1)
    ).toMatchSnapshot();
  });

  it('multipleReplies2 should return reply has second highest positiveFeedbackCount', () => {
    expect(
      getValidArticleReply(articleFixtures.multipleReplies2)
    ).toMatchSnapshot();
  });
});
<commit_msg>[groupMessage] fix unit test by making message longer (exceed 10 character)<commit_after>jest.mock('src/lib/ga');
jest.mock('src/lib/gql');
jest.mock('src/webhook/lineClient');

import ga from 'src/lib/ga';
import gql from 'src/lib/gql';

import groupMessage, { getValidArticleReply } from '../groupMessage';

import {
  apiResult,
  article as articleFixtures,
} from '../__fixtures__/groupMessage';
import lineClient from 'src/webhook/lineClient';

const event = {
  groupId: 'C904bb9fc2f4904b2facf8204b3f08c79',
  source: { type: 'group' },
};

beforeEach(() => {
  event.input = undefined;
  ga.clearAllMocks();
  gql.__reset();
  lineClient.post.mockClear();
});

describe('groupMessage', () => {
  it('rejects undefined input', async () => {
    await expect(groupMessage(event)).rejects.toMatchInlineSnapshot(
      `[Error: input undefined]`
    );
  });

  it('should not process input length less then 11', async () => {
    event.input = '0123456789';
    gql.__push(apiResult.validArticleAndOneReply);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(false);
    expect(ga.sendMock).toHaveBeenCalledTimes(0);
  });

  it('processes introduction message Hi Cofacts', async () => {
    gql.__push(apiResult.notFound);
    event.input = 'Hi Cofacts';
    let result = await groupMessage(event);
    expect(result.replies).not.toBeUndefined();
    expect(result).toMatchSnapshot();
    event.input = 'hi confacts';
    result = await groupMessage(event);
    expect(result.replies).not.toBeUndefined();
    expect(result).toMatchSnapshot();
    expect(gql.__finished()).toBe(false);

    // don't really care about this result :p
    event.input = 'cofacts';
    expect(await groupMessage(event)).toMatchSnapshot();
  });

  it('processes leave command bye bye cofacts', async () => {
    gql.__push(apiResult.notFound);
    event.input = 'bye bye cofacts';
    let result = await groupMessage(event);
    expect(gql.__finished()).toBe(false);
    expect(result.replies).toBeUndefined();
    expect(lineClient.post).toHaveBeenCalledTimes(1);
  });

  it('should not reply and send ga if article not found', async () => {
    event.input = 'article_not_found';
    gql.__push(apiResult.notFound);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(true);
    expect(ga.sendMock).toHaveBeenCalledTimes(0);
  });

  it('should handle valid article with one category', async () => {
    event.input =
      'WHO 最新研究顯示 Covid-19 其實源自黑暗料理界，即日起正名為「黑料病毒」';
    gql.__push(apiResult.validArticleWithOneCategory);
    const result = await groupMessage(event);
    expect(result.replies).not.toBeUndefined();
    expect(result).toMatchSnapshot();
    expect(gql.__finished()).toBe(true);
    expect(ga.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          undefined,
          "__INIT__",
          "WHO 最新研究顯示 Covid-19 其實源自黑暗料理界，即日起正名為「黑料病毒」",
          "group",
        ],
      ]
    `);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "3nbzf064ks60d",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Article",
            "el": "3nbzf064ks60d",
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Reply",
            "el": "fake-reply-id",
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });

  it('should handle valid article with two categories', async () => {
    event.input = '你知道黑啤愛吃什麼嗎？ 黑啤愛吃蠶寶寶！';
    gql.__push(apiResult.validArticleWithTwoCategories);
    const result = await groupMessage(event);
    expect(result.replies).not.toBeUndefined();
    expect(result).toMatchSnapshot();
    expect(gql.__finished()).toBe(true);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "3nbzf064ks60d",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "8nbzf064ks87g",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "2zn1215x6e70v",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Article",
            "el": "3nbzf064ks60d",
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Reply",
            "el": "fake-reply-id1",
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });

  it('should handle invalid article category feedback', async () => {
    event.input = '你知道嗎？其實黑啤愛吃蠶寶寶哦！';
    gql.__push(apiResult.invalidCategoryFeedback);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(true);
    expect(ga.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          undefined,
          "__INIT__",
          "你知道嗎？其實黑啤愛吃蠶寶寶哦！",
          "group",
        ],
      ]
    `);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "3nbzf064ks60d",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Article",
            "el": "3nbzf064ks60d",
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });

  it('should handle invalid article category', async () => {
    event.input = '以後吃蘋果一定要削皮。';
    gql.__push(apiResult.invalidArticleCategory);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(true);
    expect(ga.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          undefined,
          "__INIT__",
          "以後吃蘋果一定要削皮。",
          "group",
        ],
      ]
    `);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "2zn1215x6e70v",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Article",
            "el": "2zn1215x6e70v",
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });

  it('should handle input is not identical to article ', async () => {
    event.input = '我知道黑啤愛吃兔寶寶哦！';
    gql.__push(apiResult.invalidArticleReply);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(true);
    expect(ga.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          undefined,
          "__INIT__",
          "我知道黑啤愛吃兔寶寶哦！",
          "group",
        ],
      ]
    `);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "3nbzf064ks60d",
            "ni": true,
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });

  it('should handle invalid article reply', async () => {
    event.input = '我不會說我知道黑啤愛吃蠶寶寶哦！';
    gql.__push(apiResult.invalidArticleReply);
    expect((await groupMessage(event)).replies).toBeUndefined();
    expect(gql.__finished()).toBe(true);
    expect(ga.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          undefined,
          "__INIT__",
          "我不會說我知道黑啤愛吃蠶寶寶哦！",
          "group",
        ],
      ]
    `);
    expect(ga.eventMock.mock.calls).toMatchInlineSnapshot(`
      Array [
        Array [
          Object {
            "ea": "ArticleSearch",
            "ec": "UserInput",
            "el": "ArticleFound",
          },
        ],
        Array [
          Object {
            "ea": "Search",
            "ec": "Article",
            "el": "3nbzf064ks60d",
            "ni": true,
          },
        ],
        Array [
          Object {
            "ea": "Selected",
            "ec": "Article",
            "el": "3nbzf064ks60d",
          },
        ],
      ]
    `);
    expect(ga.sendMock).toHaveBeenCalledTimes(1);
  });
});

describe('processes articleReplies which article is valid', () => {
  it('invalid reply type', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidReplyType)
    ).toBeUndefined();
  });

  it('invalid reply feedback count', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidReplyFeedbackCount)
    ).toBeUndefined();
  });

  it('invalidTwoReplies1', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidTwoReplies1)
    ).toBeUndefined();
  });

  it('invalidTwoReplies2', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidTwoReplies2)
    ).toBeUndefined();
  });

  it('invalidThreeReplies1', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidThreeReplies1)
    ).toBeUndefined();
  });

  it('invalidThreeReplies2', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidThreeReplies2)
    ).toBeUndefined();
  });

  it('invalidMultipleReplies1', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidMultipleReplies1)
    ).toBeUndefined();
  });
  it('invalidMultipleReplies2', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidMultipleReplies2)
    ).toBeUndefined();
  });
  it('invalidMultipleReplies3', () => {
    expect(
      getValidArticleReply(articleFixtures.invalidMultipleReplies3)
    ).toBeUndefined();
  });

  it('twoReplies1 RUMOR positiveFeedbackCount > Non-RUMOR', () => {
    expect(getValidArticleReply(articleFixtures.twoReplies1)).toMatchSnapshot();
  });

  it('twoReplies2 equal positiveFeedbackCount, but both type are RUMOR', () => {
    expect(getValidArticleReply(articleFixtures.twoReplies2)).toMatchSnapshot();
  });

  it('threeReplies1 should return reply has more positiveFeedbackCount', () => {
    expect(
      getValidArticleReply(articleFixtures.threeReplies1)
    ).toMatchSnapshot();
  });

  it('threeReplies2 should return reply has second highest positiveFeedbackCount', () => {
    expect(
      getValidArticleReply(articleFixtures.threeReplies2)
    ).toMatchSnapshot();
  });

  it('multipleReplies1 should return Rumor reply1 or Rumor reply3', () => {
    expect(
      getValidArticleReply(articleFixtures.multipleReplies1)
    ).toMatchSnapshot();
  });

  it('multipleReplies2 should return reply has second highest positiveFeedbackCount', () => {
    expect(
      getValidArticleReply(articleFixtures.multipleReplies2)
    ).toMatchSnapshot();
  });
});
<|endoftext|>


<commit_before>import { useRouterHistory } from 'react-router';
import createHashHistory from 'history/lib/createHashHistory';

const history = useRouterHistory(createHashHistory)({
  queryKey: false,
});

module.exports = history;
<commit_msg>React-Router 3.0.0 update<commit_after>import { useRouterHistory } from 'react-router';
import createHashHistory from 'history/lib/createHashHistory';

const history = useRouterHistory(createHashHistory)();

module.exports = history;
<|endoftext|>