<commit_before>var CASTDiagramCanvas = document.getElementById("CASTDiagram"),
    CASTDiagramCtx = CASTDiagramCanvas.getContext('2d');

var scrWidth = CASTDiagramCanvas.width,
    scrHeight = CASTDiagramCanvas.height;

//draws CAST diagram circular outline
CASTDiagramCtx.beginPath();
CASTDiagramCtx.strokeStyle = 'rgba(0,0,0,1)';
CASTDiagramCtx.lineWidth = 1.5;
CASTDiagramCtx.arc(scrWidth/2,scrHeight/2,scrWidth/2-25,0,Math.PI*2,false);
CASTDiagramCtx.closePath();
CASTDiagramCtx.stroke();

//draws lines on CAST diagram
CASTDiagramCtx.beginPath();
CASTDiagramCtx.strokeStyle = 'rgba(255,0,0,1)';
CASTDiagramCtx.lineWidth = 2;
CASTDiagramCtx.moveTo(scrWidth/2,scrHeight/2);
CASTDiagramCtx.lineTo(scrWidth/2 + scrWidth/2 - 15,scrHeight/2);
CASTDiagramCtx.moveTo(scrWidth/2,scrHeight/2);
CASTDiagramCtx.lineTo(scrWidth/2 - scrWidth/2 + 15,scrHeight/2);
CASTDiagramCtx.moveTo(scrWidth/2,scrHeight/2);
CASTDiagramCtx.lineTo(scrWidth/2,scrHeight/2 + scrHeight/2 - 15);
CASTDiagramCtx.moveTo(scrWidth/2,scrHeight/2);
CASTDiagramCtx.lineTo(scrWidth/2,scrHeight/2 - scrHeight/2 + 15);
CASTDiagramCtx.closePath();
CASTDiagramCtx.stroke();

//adds labels to CAST diagram
CASTDiagramCtx.fillStyle = 'rgb(0,0,255)';
CASTDiagramCtx.font = "12px Arial";
CASTDiagramCtx.fillText("0°/360°",scrWidth - 67,scrHeight/2 - 5);
CASTDiagramCtx.fillText("90°",scrWidth/2 -20,40);
CASTDiagramCtx.fillText("180°",30,scrHeight/2 + 15);
CASTDiagramCtx.fillText("270°",scrWidth/2 + 5,scrHeight - 30);

CASTDiagramCtx.font = "18px Arial";
CASTDiagramCtx.fillText("x",scrWidth*2/3,scrHeight*1/3);
CASTDiagramCtx.fillText("180 - x",scrWidth*1/3 - 20,scrHeight*1/3);
CASTDiagramCtx.fillText("180 + x",scrWidth*1/3 - 20,scrHeight*2/3);
CASTDiagramCtx.fillText("360 - x",scrWidth*2/3 - 20,scrHeight*2/3);

function controllsForThingy(e){
    var lool = true;
    if(e.clientX >= 200 && e.clientY <= 200 + 130){
        var x = e.clientX - 400/2,
            y =(400/2 - (e.clientY -130));

        theta = Math.atan(y/x) * (180/Math.PI);
    } else if(e.clientX < 200 && e.clientY <= 200 + 130){
        var x = 400/2 - e.clientX,
            y =(400/2 - (e.clientY -130));

        theta = Math.atan(y/x) * (180/Math.PI);
    } else if(e.clientX < 200 && e.clientY <= 200 + 130){
        var x = 400/2 - e.clientX,
            y =(400/2 - (e.clientY -130));

        theta = Math.atan(y/x) * (180/Math.PI);
    }
}
<commit_msg>commit<commit_after>var CASTDiagramCanvas = document.getElementById("CASTDiagram"),
    CASTDiagramCtx = CASTDiagramCanvas.getContext('2d');

var scrWidth = CASTDiagramCanvas.width,
    scrHeight = CASTDiagramCanvas.height;

//draws CAST diagram circular outline
CASTDiagramCtx.beginPath();
CASTDiagramCtx.strokeStyle = 'rgba(0,0,0,1)';
CASTDiagramCtx.lineWidth = 1.5;
CASTDiagramCtx.arc(scrWidth/2,scrHeight/2,scrWidth/2-25,0,Math.PI*2,false);
CASTDiagramCtx.closePath();
CASTDiagramCtx.stroke();

//draws lines on CAST diagram
CASTDiagramCtx.beginPath();
CASTDiagramCtx.strokeStyle = 'rgba(255,0,0,1)';
CASTDiagramCtx.lineWidth = 2;
CASTDiagramCtx.moveTo(scrWidth/2,scrHeight/2);
CASTDiagramCtx.lineTo(scrWidth/2 + scrWidth/2 - 15,scrHeight/2);
CASTDiagramCtx.moveTo(scrWidth/2,scrHeight/2);
CASTDiagramCtx.lineTo(scrWidth/2 - scrWidth/2 + 15,scrHeight/2);
CASTDiagramCtx.moveTo(scrWidth/2,scrHeight/2);
CASTDiagramCtx.lineTo(scrWidth/2,scrHeight/2 + scrHeight/2 - 15);
CASTDiagramCtx.moveTo(scrWidth/2,scrHeight/2);
CASTDiagramCtx.lineTo(scrWidth/2,scrHeight/2 - scrHeight/2 + 15);
CASTDiagramCtx.closePath();
CASTDiagramCtx.stroke();

//adds labels to CAST diagram
CASTDiagramCtx.fillStyle = 'rgb(0,0,255)';
CASTDiagramCtx.font = "12px Arial";
CASTDiagramCtx.fillText("0°/360°",scrWidth - 67,scrHeight/2 - 5);
CASTDiagramCtx.fillText("90°",scrWidth/2 -20,40);
CASTDiagramCtx.fillText("180°",30,scrHeight/2 + 15);
CASTDiagramCtx.fillText("270°",scrWidth/2 + 5,scrHeight - 30);

CASTDiagramCtx.font = "18px Arial";
CASTDiagramCtx.fillText("x",scrWidth*2/3,scrHeight*1/3);
CASTDiagramCtx.fillText("180 - x",scrWidth*1/3 - 20,scrHeight*1/3);
CASTDiagramCtx.fillText("180 + x",scrWidth*1/3 - 20,scrHeight*2/3);
CASTDiagramCtx.fillText("360 - x",scrWidth*2/3 - 20,scrHeight*2/3);

function controllsForThingy(e){
    var lool = true;
    if(e.clientX >= 200 && e.clientY <= 200 + 130){
        var x = e.clientX - 400/2,
            y =(400/2 - (e.clientY -130));

        theta = Math.atan(y/x) * (180/Math.PI);
    } else if(e.clientX < 200 && e.clientY <= 200 + 130){
        var x = 400/2 - e.clientX,
            y =(400/2 - (e.clientY -130));

        theta = 180 - (Math.atan(y/x) * (180/Math.PI));
    } else if(e.clientX <= 200 && e.clientY > 200 + 130){
        var x = 400/2 - e.clientX,
            y =(400/2 - (e.clientY -130));

        theta = 180 - (Math.atan(y/x) * (180/Math.PI));
    } else if(e.clientX > 200 && e.clientY > 200 + 130){
        var x = 400/2 - e.clientX,
            y =(400/2 - (e.clientY -130));

        theta = 360 - (Math.atan(y/x) * (180/Math.PI));
    }
}
<|endoftext|>


<commit_before>/** @preserve
 * jquery-custom-checkbox
 *
 * Created at: 2012-12-17 15:53:56 +0100
 * Updated at: 2013-06-27 12:45:28 +0200
 *
 * Author: Yves Van Broekhoven
 * Version: 1.0.2
 *
 */

/*global jQuery:false*/

(function($) {
  "use strict";

  var plugin_name = 'jqueryCustomCheckbox';

  function CustomCheckbox( element ) {
    var $this = $(element);

    this.element      = element;
    this.name         = $this.attr('name');
    this.escaped_name = this.name.replace(/\[/g, '\\[').replace(/\]/g, '\\]');
    this.$label       = $this.siblings('label[for='+ $this.attr('id') +']');
    this.$fake        = $('');
    this.$groupling_labels = this.findGrouplingLabels();
    this.init();
  }

  CustomCheckbox.prototype.init = function() {
    var _this = this,
        $this = $(_this.element);

    $this.hide();

    _this.$label
      .addClass('custom-checkbox')
      .append( _this.$fake );

    _this.setState();

    $this.on('change', function() {
      _this.setState();
    });
  };

  CustomCheckbox.prototype.setState = function() {
    var $this = $(this.element);

    if ( $this.prop('checked') === true ) {
      this.$fake.addClass('checked');

    } else {
      this.$fake.removeClass('checked');

    }

    // If radio button, uncheck group members
    if ( $this.attr('type') == 'radio' ) {
      this.$groupling_labels.find('.fake-checkbox').removeClass('checked');
    }
  };

  CustomCheckbox.prototype.findGrouplingLabels = function() {
    var $this    = $(this.element),
        $options = $('input[name=' + this.escaped_name + ']').not($this),
        $groupling_labels = $();

    $options.each(function() {
      var $option = $(this),
          $label  = $option.siblings('label[for='+ $option.attr('id') +']');

      $groupling_labels = $groupling_labels.add($label);
    });

    return $groupling_labels;
  };

  $.fn.customcheckbox = function() {
    return this.each( function() {
      $(this).data( plugin_name, new CustomCheckbox(this) );
    });
  };

}(jQuery));
<commit_msg>Added github url to script<commit_after>/** @preserve
 * jquery-custom-checkbox
 *
 * Created at: 2012-12-17 15:53:56 +0100
 * Updated at: 2013-06-27 12:45:28 +0200
 *
 * Author: Yves Van Broekhoven
 * Version: 1.0.2
 *
 * https://github.com/mrhenry/jquery-custom-checkbox
 *
 */

/*global jQuery:false*/

(function($) {
  "use strict";

  var plugin_name = 'jqueryCustomCheckbox';

  function CustomCheckbox( element ) {
    var $this = $(element);

    this.element      = element;
    this.name         = $this.attr('name');
    this.escaped_name = this.name.replace(/\[/g, '\\[').replace(/\]/g, '\\]');
    this.$label       = $this.siblings('label[for='+ $this.attr('id') +']');
    this.$fake        = $('');
    this.$groupling_labels = this.findGrouplingLabels();
    this.init();
  }

  CustomCheckbox.prototype.init = function() {
    var _this = this,
        $this = $(_this.element);

    $this.hide();

    _this.$label
      .addClass('custom-checkbox')
      .append( _this.$fake );

    _this.setState();

    $this.on('change', function() {
      _this.setState();
    });
  };

  CustomCheckbox.prototype.setState = function() {
    var $this = $(this.element);

    if ( $this.prop('checked') === true ) {
      this.$fake.addClass('checked');

    } else {
      this.$fake.removeClass('checked');

    }

    // If radio button, uncheck group members
    if ( $this.attr('type') == 'radio' ) {
      this.$groupling_labels.find('.fake-checkbox').removeClass('checked');
    }
  };

  CustomCheckbox.prototype.findGrouplingLabels = function() {
    var $this    = $(this.element),
        $options = $('input[name=' + this.escaped_name + ']').not($this),
        $groupling_labels = $();

    $options.each(function() {
      var $option = $(this),
          $label  = $option.siblings('label[for='+ $option.attr('id') +']');

      $groupling_labels = $groupling_labels.add($label);
    });

    return $groupling_labels;
  };

  $.fn.customcheckbox = function() {
    return this.each( function() {
      $(this).data( plugin_name, new CustomCheckbox(this) );
    });
  };

}(jQuery));
<|endoftext|>


<commit_before>function updatePreset(val)
{
	var preset = val instanceof Number ? val : +$('#preset').val();
	if (preset > 0) $('.presetoption').prop('checked', false);

	if (preset == 0) return;

	$('#randomize_stages').prop('checked', true);
	$('#randomize_sublevels').prop('checked', preset > 4);
	$('#randomize_exits').prop('checked', preset > 2);
	$('#randomize_warps').prop('checked', preset > 3);

	$('#randomize_sameworld').prop('checked', preset < 2);
	$('#randomize_sametype').prop('checked', preset < 2);

	$('#randomize_levelexits').prop('checked', preset > 5);
	$('#randomize_noyoshi').prop('checked', preset > 2);
	$('#randomize_colors').prop('checked', true);

	$('#levelnames_samestage').prop('checked', true);
	if (preset > 2) $('#levelnames_overworld').prop('checked', true);
	if (preset > 3) $('#levelnames_randomstage').prop('checked', true);

	$('#bowser_default').prop('checked', true);
	if (preset > 1) $('#bowser_swapdoors').prop('checked', true);
	if (preset > 4) $('#bowser_minigauntlet').prop('checked', true);
	if (preset > 7) $('#bowser_gauntlet').prop('checked', true);
	$('#randomize_bowserdoors').prop('checked', preset > 3);

	$('#powerup_default').prop('checked', true);
	if (preset > 3) $('#powerup_randomize').prop('checked', true);

	$('#slippery').prop('checked', preset > 3);
	$('#addwater').prop('checked', preset > 3);
	$('#delwater').prop('checked', preset > 7);

	$('#saving_all').prop('checked', true);
	if (preset > 3) $('#saving_original').prop('checked', true);
	if (preset > 6) $('#saving_default').prop('checked', true);

	$('#enemyprop_default').prop('checked', true);
	if (preset > 2) $('#enemyprop_normal').prop('checked', true);
	if (preset > 7) $('#enemyprop_chaos').prop('checked', true);
	$('#randomize_enemies').prop('checked', preset > 5);
	$('#customyoshipowers').prop('checked', preset > 3);

	$('#randomize_koopakids').prop('checked', preset > 3);
	$('#randomize_bossdiff').prop('checked', preset > 5);
	$('#randomize_autoscrollers').prop('checked', preset > 5);

	$('#customnames').prop('checked', preset > 2);
	$('#customtext').prop('checked', preset > 3);
	$('#pogyo_mode').prop('checked', preset > 7);
}

$('#preset').change(updatePreset);

// selecting any option by hand should set the preset box to "custom"
$('.presetoption').click(function()
{
	$('#preset').val(0);
});

var PRESET_NAMES =
[
	"Custom",
	"Gnarly",
	"Tubular",
	"Way Cool",
	"Awesome",
	"Groovy",
	"Mondo",
	"Outrageous",
	"Funky",
];

function getPresetName(n)
{
	if (n === undefined) n = $('#preset').val();
	return PRESET_NAMES[n] || PRESET_NAMES[0];
}

var RANDOMIZER_SETTINGS = [];
$('.presetoption').each(function(){ RANDOMIZER_SETTINGS.push(this); });

function getRandomizerSettings()
{
	var bitset = $.map(RANDOMIZER_SETTINGS, function(x){ return $(x).is(':checked'); });
	return bitsToHex(bitset);
}

function setRandomizerSettings(set)
{
	var bitset = hexToBits(set);
	for (var i = 0; i < RANDOMIZER_SETTINGS.length; ++i)
	{
		var x = i > bitset.length ? 0 : bitset[i];
		$(RANDOMIZER_SETTINGS[i]).prop('checked', x);
	}
}

function shuffleOptions()
{
	$('input:checkbox:enabled.presetoption').each(function()
	{ $(this).prop('checked', Math.random() > 0.5); });

	var radiogroups = {};
	$('input:radio:enabled.presetoption').each(function()
	{ radiogroups[$(this).attr('name')] = true; });

	for (var k in radiogroups)
	{
		var group = $('input:radio:enabled[name="' + k + '"]');
		var n = Math.floor(Math.random() * group.length);
		group.eq(n).prop('checked', true);
	}
}
<commit_msg>Improve some preset values<commit_after>function updatePreset(val)
{
	var preset = val instanceof Number ? val : +$('#preset').val();
	if (preset > 0) $('.presetoption').prop('checked', false);

	if (preset == 0) return;

	$('#randomize_stages').prop('checked', true);
	$('#randomize_sublevels').prop('checked', preset > 3);
	$('#randomize_exits').prop('checked', preset > 2);
	$('#randomize_warps').prop('checked', preset > 4);

	$('#randomize_sameworld').prop('checked', preset < 2);
	$('#randomize_sametype').prop('checked', preset < 2);

	$('#randomize_levelexits').prop('checked', preset > 5);
	$('#randomize_noyoshi').prop('checked', preset > 2);
	$('#randomize_colors').prop('checked', true);

	$('#levelnames_samestage').prop('checked', true);
	if (preset > 2) $('#levelnames_overworld').prop('checked', true);
	if (preset > 3) $('#levelnames_randomstage').prop('checked', true);

	$('#bowser_default').prop('checked', true);
	if (preset > 1) $('#bowser_swapdoors').prop('checked', true);
	if (preset > 3) $('#bowser_minigauntlet').prop('checked', true);
	if (preset > 7) $('#bowser_gauntlet').prop('checked', true);
	$('#randomize_bowserdoors').prop('checked', preset > 3);

	$('#powerup_default').prop('checked', true);
	if (preset > 3) $('#powerup_randomize').prop('checked', true);

	$('#slippery').prop('checked', preset > 3);
	$('#addwater').prop('checked', preset > 3);
	$('#delwater').prop('checked', preset > 7);

	$('#saving_all').prop('checked', true);
	if (preset > 3) $('#saving_original').prop('checked', true);
	if (preset > 6) $('#saving_default').prop('checked', true);

	$('#enemyprop_default').prop('checked', true);
	if (preset > 2) $('#enemyprop_normal').prop('checked', true);
	if (preset > 7) $('#enemyprop_chaos').prop('checked', true);
	$('#randomize_enemies').prop('checked', preset > 5);
	$('#customyoshipowers').prop('checked', preset > 3);

	$('#randomize_koopakids').prop('checked', preset > 3);
	$('#randomize_bossdiff').prop('checked', preset > 5);
	$('#randomize_autoscrollers').prop('checked', preset > 5);

	$('#customnames').prop('checked', preset > 2);
	$('#customtext').prop('checked', preset > 3);
	$('#pogyo_mode').prop('checked', preset > 7);
}

$('#preset').change(updatePreset);

// selecting any option by hand should set the preset box to "custom"
$('.presetoption').click(function()
{
	$('#preset').val(0);
});

var PRESET_NAMES =
[
	"Custom",
	"Gnarly",
	"Tubular",
	"Way Cool",
	"Awesome",
	"Groovy",
	"Mondo",
	"Outrageous",
	"Funky",
];

function getPresetName(n)
{
	if (n === undefined) n = $('#preset').val();
	return PRESET_NAMES[n] || PRESET_NAMES[0];
}

var RANDOMIZER_SETTINGS = [];
$('.presetoption').each(function(){ RANDOMIZER_SETTINGS.push(this); });

function getRandomizerSettings()
{
	var bitset = $.map(RANDOMIZER_SETTINGS, function(x){ return $(x).is(':checked'); });
	return bitsToHex(bitset);
}

function setRandomizerSettings(set)
{
	var bitset = hexToBits(set);
	for (var i = 0; i < RANDOMIZER_SETTINGS.length; ++i)
	{
		var x = i > bitset.length ? 0 : bitset[i];
		$(RANDOMIZER_SETTINGS[i]).prop('checked', x);
	}
}

function shuffleOptions()
{
	$('input:checkbox:enabled.presetoption').each(function()
	{ $(this).prop('checked', Math.random() > 0.5); });

	var radiogroups = {};
	$('input:radio:enabled.presetoption').each(function()
	{ radiogroups[$(this).attr('name')] = true; });

	for (var k in radiogroups)
	{
		var group = $('input:radio:enabled[name="' + k + '"]');
		var n = Math.floor(Math.random() * group.length);
		group.eq(n).prop('checked', true);
	}
}
<|endoftext|>


<commit_before>'use strict';
// Load required libraries
const AWSXRay = require('aws-xray-sdk-core');
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
const documentclient = new AWS.DynamoDB.DocumentClient();

module.exports.handler = (event, context, callback) => {

	// Set from and to variables, use from and to in event if present.
	let from;
	if (Number(event.from) > 1) from = Number(event.from);
	else from = 1;

	let to;
	if (Number(event.to) > 1) to = Number(event.to);
	else to = new Date() / 1000;

	// Set scan limit to 20 if not present or greater than 20
	let limit = process.env.LIMIT;
	if (Number(event.limit) < process.env.LIMIT) limit = event.limit;

	console.log('Scanning from greater than ' + from);
	// Set scanning parameters
	let params = {
		TableName: 'impact-map',
		// Only get Coordinates and Action data from the table
		ProjectionExpression: 'Coordinates, #b',
		// Only return items that fall within start and stop parameters
		FilterExpression: 'Added BETWEEN :start AND :stop',
		ExpressionAttributeNames: {
			// Set an 'alias' for the word, 'Action', because 'Action' is a reserved word in DynamoDB
			'#b': 'Action'
		},
		ExpressionAttributeValues: {
			// Set an 'alias' for start and stop variables
			':start': from,
			':stop': to
		},
		Limit: limit
	};

	if (event.hasOwnProperty('exclusivestartkey') && event.exclusivestartkey !== '') {
		console.log('exclusivestartkey detected ' + event.exclusivestartkey);
		params.ExclusiveStartKey = {};
		params.ExclusiveStartKey.Id = event.exclusivestartkey;
	}

	console.log('Scanning table.');
	documentclient.scan(params, onScan);

	function onScan(err, data) {
		// Log error if scanning returns err
		if (err) {
			console.error('Unable to scan the table. Error JSON:', JSON.stringify(err, null, 2));
			callback(err);
		} else {
			// Continue scanning if we have more items, because scan can retrieve a maximum of 1MB of data
			if (data.hasOwnProperty('LastEvaluatedKey')) {
				console.log('LastEvaluatedKey detected');
			}

			// Returns the item data back to the client
			console.log('Returning ' + data.Items.Count + ' items to the client');
			callback(null, data);
		}
	}
};
<commit_msg>Remove scan start logging<commit_after>'use strict';
// Load required libraries
const AWSXRay = require('aws-xray-sdk-core');
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
const documentclient = new AWS.DynamoDB.DocumentClient();

module.exports.handler = (event, context, callback) => {

	// Set from and to variables, use from and to in event if present.
	let from;
	if (Number(event.from) > 1) from = Number(event.from);
	else from = 1;

	let to;
	if (Number(event.to) > 1) to = Number(event.to);
	else to = new Date() / 1000;

	// Set scan limit to 20 if not present or greater than 20
	let limit = process.env.LIMIT;
	if (Number(event.limit) < process.env.LIMIT) limit = event.limit;

	// Set scanning parameters
	let params = {
		TableName: 'impact-map',
		// Only get Coordinates and Action data from the table
		ProjectionExpression: 'Coordinates, #b',
		// Only return items that fall within start and stop parameters
		FilterExpression: 'Added BETWEEN :start AND :stop',
		ExpressionAttributeNames: {
			// Set an 'alias' for the word, 'Action', because 'Action' is a reserved word in DynamoDB
			'#b': 'Action'
		},
		ExpressionAttributeValues: {
			// Set an 'alias' for start and stop variables
			':start': from,
			':stop': to
		},
		Limit: limit
	};

	if (event.hasOwnProperty('exclusivestartkey') && event.exclusivestartkey !== '') {
		console.log('exclusivestartkey detected ' + event.exclusivestartkey);
		params.ExclusiveStartKey = {};
		params.ExclusiveStartKey.Id = event.exclusivestartkey;
	}

	console.log('Scanning table.');
	documentclient.scan(params, onScan);

	function onScan(err, data) {
		// Log error if scanning returns err
		if (err) {
			console.error('Unable to scan the table. Error JSON:', JSON.stringify(err, null, 2));
			callback(err);
		} else {
			// Continue scanning if we have more items, because scan can retrieve a maximum of 1MB of data
			if (data.hasOwnProperty('LastEvaluatedKey')) {
				console.log('LastEvaluatedKey detected');
			}

			// Returns the item data back to the client
			console.log('Returning ' + data.Items.Count + ' items to the client');
			callback(null, data);
		}
	}
};
<|endoftext|>


<commit_before>module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  }
}
<commit_msg>add html-webpack-plugin into config<commit_after>const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
}
<|endoftext|>


<commit_before>/*
	Linear iteration apporach fast

*/
var availableFunction = [apporach1_SimpleSearch,apporach2_BreadthFirstSearch];

function apporach1_SimpleSearch(){
	var currentUseingClassName = state === 0 ? "sh-hover" : "sh-select";
	//console.log("============================================================");
	var currentSelected = $("."+currentUseingClassName);

	var path = "";
	var count = currentSelected.length;
	var recommend,suggestion;
	var findSolution = false;

	if(currentSelected.length>0){
		path = currentSelected.getFullPathName(true);

		findSolution = true;
		var currentNode = $(currentSelected[0]);
		recommend = "";
		while(!isCorrectSelection(currentSelected,recommend) && currentNode.prop("tagName") !== "BODY" ){
			findSolution = false;
			//console.log(currentNode);
			var classList = currentNode.attr('class')===undefined ? [] :currentNode.attr('class').split(/\s+/);

			// loop though all possible class attritube to see if we are able to succfully select

			$.each(classList, function(index, item) {

				if(item == 'sh-select' || item == 'sh-hover' || item == '' || item == 'sh-prdict'){
					return;
				}

				var trival_solution = currentNode.prop("tagName").toLowerCase()+"."+item +" "+  recommend;
				if(isCorrectSelection(currentSelected,trival_solution)){
					findSolution = true;
					recommend = trival_solution;
					console.log("Solution Found!");
					return false;
				}
			});
			if(findSolution){
				break;
			}
			//console.log("go to upper level tree for answer");
			recommend = currentNode.prop("tagName").toLowerCase() + " "+recommend;
			currentNode = currentNode.parent();
		}
	}

	if(!findSolution){
		recommend = "";
		if(current_best_solution!= ""){
			suggestion = current_best_solution;
		}else{
			suggestion = "";
		}

	}else{
		suggestion = "";
	}

	return {path:path.toLowerCase(),count:{select:count,predict:0},recommend:recommend,suggestion:suggestion,findSolution:findSolution};
}

/*
	Apporach 1 Breadth First Search
	Looping All possible combination of selector, including element class and element id
	For bottom to top of data tree

*/
function apporach2_BreadthFirstSearch(){
	var currentUsingClassName = state === 0 ? "sh-hover" : "sh-select";

	var currentSelected = $("."+currentUsingClassName);

	var recommend;

	var findSolution = false;

	if(currentSelected.length>0){

		resultData   = breadthFirstSearchAux(currentSelected,gCurrentNode,[]);

		findSolution = resultData.findSolution;
		recommend    = resultData.recommend

	}

	var path = gCurrentNode.getFullPathName(true);
	path     = path || "";
	var count = currentSelected.length;
	var predict_count = $(".sh-predict").length;
	return {path:path.toLowerCase(),count:{select:count,predict:predict_count},recommend:recommend,suggestion:current_best_solution,findSolution:findSolution};
}

/*
	Recursive BFS
*/
function breadthFirstSearchAux(currentState,currentNode,selectorArray){

	// prepare loop selector for this level

	try{
		var thisNodeTag  = currentNode.prop("tagName").toLowerCase();
		if(thisNodeTag == "body"){
			return {findSolution:false,recommend:""};
		}
	}catch(err){
		console.log("Error!!!!!!!");
		console.log(currentNode);
		return {findSolution:false,recommend:""};
	}
	var modifierList = [thisNodeTag];

	var classList = currentNode.attr('class')===undefined ? [] :currentNode.attr('class').split(/\s+/);

	$.each(classList,function(index,item){
		if(item == 'sh-select' || item == 'sh-hover' || item == '' || item == 'sh-prdict'){
			return;
		}
		modifierList.push(thisNodeTag+"."+item);
	});

	// var thisNodeId = currentNode.attr("id") || "";
	// if(thisNodeId != ""){
	// 	modifierList.push("#"+thisNodeId);
	// }

	// run loop
	for(var i = 0;i<modifierList.length;i++){

		var trival_selector = modifierList[i] + " " + selectorArray.join(" ");
		if(isCorrectSelection(currentState,trival_selector)){
			return {findSolution:true,recommend:trival_selector};
		}
	};

	for(var i = 0;i<modifierList.length;i++){

		selectorArray.unshift(modifierList[i]);
		result = breadthFirstSearchAux(currentState,$(currentNode.parent()[0]),selectorArray);
		if(result.findSolution){
			return {findSolution:true,recommend:result.recommend};
		}
		selectorArray.shift();
	};
	return {findSolution:false,recommend:""};

}

<commit_msg>Clean sh-implementation.js and add commit<commit_after>/*
	Linear iteration apporach fast

*/
var availableFunction = [apporach1_SimpleSearch,apporach2_BreadthFirstSearch];

/*
	Apporach 1 Linear Search (Can solve most of the problem and ... fast)
	Keep tring node with its class, if not, jump to its parent.

	e.g
	===============
	div class="a b"
	h1  class="c d" <-selected
	===============
	will try

	h1
	h1.c
	h1.d

	div h1
	div.a h1
	div.b h1

	For bottom to top of data tree

*/

function apporach1_SimpleSearch(){
	var currentUseingClassName = state === 0 ? "sh-hover" : "sh-select";
	//console.log("============================================================");
	var currentSelected = $("."+currentUseingClassName);

	var path = "";
	var count = currentSelected.length;
	var recommend,suggestion;
	var findSolution = false;

	if(currentSelected.length>0){
		path = currentSelected.getFullPathName(true);

		findSolution = true;
		var currentNode = $(currentSelected[0]);
		recommend = "";
		while(!isCorrectSelection(currentSelected,recommend) && currentNode.prop("tagName") !== "BODY" ){
			findSolution = false;
			//console.log(currentNode);
			var classList = currentNode.attr('class')===undefined ? [] :currentNode.attr('class').split(/\s+/);

			// loop though all possible class attritube to see if we are able to succfully select

			$.each(classList, function(index, item) {

				if(item == 'sh-select' || item == 'sh-hover' || item == '' || item == 'sh-prdict'){
					return;
				}

				var trival_solution = currentNode.prop("tagName").toLowerCase()+"."+item +" "+  recommend;
				if(isCorrectSelection(currentSelected,trival_solution)){
					findSolution = true;
					recommend = trival_solution;
					console.log("Solution Found!");
					return false;
				}
			});
			if(findSolution){
				break;
			}
			//console.log("go to upper level tree for answer");
			recommend = currentNode.prop("tagName").toLowerCase() + " "+recommend;
			currentNode = currentNode.parent();
		}
	}

	if(!findSolution){
		recommend = "";
		if(current_best_solution!= ""){
			suggestion = current_best_solution;
		}else{
			suggestion = "";
		}

	}else{
		suggestion = "";
	}

	return {path:path.toLowerCase(),count:{select:count,predict:0},recommend:recommend,suggestion:suggestion,findSolution:findSolution};
}

/*
	Apporach 2 Breadth First Search
	Looping All possible combination of selector, including element class and element id
	For bottom to top of data tree

	e.g
	===============
	div class="a b"
	h1  class="c d" <-selected
	===============
	will try:

	h1
	h1.c
	h1.d

	div h1
	div.a h1
	div.b h1

	div h1.c
	div.a h1.c
	div.b h1.c

	div h1.d
	div.a h1.d
	div.b h1.d

*/
function apporach2_BreadthFirstSearch(){
	var currentUsingClassName = state === 0 ? "sh-hover" : "sh-select";

	var currentSelected = $("."+currentUsingClassName);

	var recommend;

	var findSolution = false;

	if(currentSelected.length>0){

		resultData   = breadthFirstSearchAux(currentSelected,gCurrentNode,[]);

		findSolution = resultData.findSolution;
		recommend    = resultData.recommend

	}

	var path = gCurrentNode.getFullPathName(true);
	path     = path || "";
	var count = currentSelected.length;
	var predict_count = $(".sh-predict").length;
	return {path:path.toLowerCase(),count:{select:count,predict:predict_count},recommend:recommend,suggestion:current_best_solution,findSolution:findSolution};
}

/*
	Recursive BFS
*/
function breadthFirstSearchAux(currentState,currentNode,selectorArray){

	// prepare loop selector for this level

	try{
		var thisNodeTag  = currentNode.prop("tagName").toLowerCase();
		if(thisNodeTag == "body"){
			return {findSolution:false,recommend:""};
		}
	}catch(err){
		console.log("Error!!!!!!!");
		console.log(currentNode);
		return {findSolution:false,recommend:""};
	}
	var modifierList = [thisNodeTag];

	var classList = currentNode.attr('class')===undefined ? [] :currentNode.attr('class').split(/\s+/);

	$.each(classList,function(index,item){
		if(item == 'sh-select' || item == 'sh-hover' || item == '' || item == 'sh-prdict'){
			return;
		}
		modifierList.push(thisNodeTag+"."+item);
	});

	// var thisNodeId = currentNode.attr("id") || "";
	// if(thisNodeId != ""){
	// 	modifierList.push("#"+thisNodeId);
	// }

	// run loop
	for(var i = 0;i<modifierList.length;i++){

		var trival_selector = modifierList[i] + " " + selectorArray.join(" ");
		if(isCorrectSelection(currentState,trival_selector)){
			return {findSolution:true,recommend:trival_selector};
		}
	};

	for(var i = 0;i<modifierList.length;i++){

		selectorArray.unshift(modifierList[i]);
		result = breadthFirstSearchAux(currentState,$(currentNode.parent()[0]),selectorArray);
		if(result.findSolution){
			return {findSolution:true,recommend:result.recommend};
		}
		selectorArray.shift();
	};
	return {findSolution:false,recommend:""};

}

<|endoftext|>


<commit_before>#!/usr/bin/env node

var fs = require('fs');
var fetch = require('node-fetch');
var values = require('object-values');
var unique = require('just-unique');
var chalk = require('chalk');
var extraAliases = require('./locales/extra_aliases.js');

/**
 * CONSTANTS
 */

var ALL_LANGS = ['ar', 'an', 'hy', 'ast', 'eu', 'bn_IN', 'nb', 'bg', 'zh_CN',
                 'zh_TW', 'da', 'de', 'eo', 'et', 'fo', 'fi', 'fr',
                 'gl', 'ht', 'he', 'hi', 'hch', 'id', 'ga', 'is', 'it', 'ja',
                 'ja_HIRA', 'km', 'kn', 'kk', 'ca', 'ko', 'hr', 'ku',
                 'cy', 'ky', 'la', 'lv', 'lt', 'mk', 'ms', 'ml', 'mr', 'maz',
                 'mn', 'my', 'nah', 'ne', 'el', 'nl', 'no', 'nn', 'or', 'os',
                 'oto', 'ote', 'pap', 'fa', 'fil', 'pl', 'pt', 'pt_BR', 'ro',
                 'ru', 'rw', 'sv', 'sr', 'sk', 'sl', 'es', 'sw', 'tzm', 'ta',
                 'th', 'cs', 'tr', 'ug', 'uk', 'hu', 'vi'];

// ISO Codes for all the language forums.
var FORUM_LANGS = ['de', 'es', 'fr', 'zh_CN', 'pl', 'ja', 'nl' , 'pt', 'it',
                   'he', 'ko', 'nb', 'tr', 'el', 'ru', 'ca', 'id'];

var ENGLISH_COMMANDS = require('./commands.js');
var COMMAND_SPECS = unique(ENGLISH_COMMANDS.map(cmd => cmd[0]));
var PALETTE_SPECS = [
  'Motion', 'Looks', 'Sound', 'Pen', 'Data', 'variable',
  'list', 'Events', 'Control', 'Sensing', 'Operators',
  'More Blocks', 'Tips'
];
var NEED_ALIAS = [
  'turn @turnRight %n degrees',
  'turn @turnLeft %n degrees',
  'when @greenFlag clicked'
];
var UNTRANSLATED = [
  '%n + %n',
  '%n - %n',
  '%n * %n',
  '%n / %n',
  '%s < %s',
  '%s = %s',
  '%s > %s',
  '…',
  '...'
];
var ACCEPTABLE_MISSING = [
  'turn %m.motor on for %n seconds',
  'set light color to %n',
  'play note %n for %n seconds',
  'when tilted',
  'tilt %m.xxx',
  'else',
  'end',
  '. . .',
  '%n @addInput',
  'user id',
  'if %b',
  'if %b',
  'forever if %b',
  'stop script',
  'stop all',
  'switch to costume %m.costume',
  'next background',
  'switch to background %m.backdrop',
  'background #',
  'loud?',
  'username'
];
var MATH_FUNCS = [
  'abs', 'floor', 'ceiling', 'sqrt', 'sin', 'cos', 'tan',
  'asin', 'acos', 'atan', 'ln', 'log', 'e ^', '10 ^'
];
var OSIS = [ 'other scripts in sprite', 'other scripts in stage' ];
var DROPDOWN_SPECS = [
  'A connected', 'all', 'all around',
  'B connected', 'brightness', 'button pressed', 'C connected', 'color',
  'costume name', 'D connected', 'date', 'day of week', 'don\'t rotate',
  'down arrow', 'edge', 'fisheye', 'ghost', 'hour',
  'left arrow', 'left-right', 'light', 'minute', 'month',
  'mosaic', 'motion', 'mouse-pointer',
  'myself', 'off', 'on', 'on-flipped', 'other scripts in sprite',
  'pixelate', 'previous backdrop', 'resistance-A',
  'resistance-B', 'resistance-C', 'resistance-D', 'reverse', 'right arrow',
  'second', 'slider', 'sound', 'space', 'Stage', 'that way', 'this script',
  'this sprite', 'this way', 'up arrow', 'video motion', 'whirl', 'year'
];

/**
 * CONSTANTS END
 */

/**
 * Arguments
 *
 * ./fetch_translations [language code | all]
 *
 * No arguments will fetch forum languages.
 */
var args = process.argv;
var BUILD_LANGS = FORUM_LANGS;
if (args.length > 3 || args[2] === '--help') {
  console.log('\nFetches scratch translations from translate.scratch.mit.edu.');
  console.log('\n  Usage: ./fetch_translations.js [language code | all]');
  console.log('\nIf no language code is given, translations for forum languages will be fetched.');
  process.exit(1);
}  else if (args.length === 3 && args[2] === 'all') {
  BUILD_LANGS = ALL_LANGS;
} else if (args.length === 3 && ALL_LANGS.indexOf(args[2]) === -1) {
  console.log('Language code not valid, supported languages:');
  console.log(ALL_LANGS.join(', '));
} else if (args.length === 3) {
  BUILD_LANGS = [ args[2] ];
}

/**
 * Flow:
 * - fetch
 * - add special cases
 * - transform translations
 * - print statistics
 * - write to JSON
 */
BUILD_LANGS.forEach(lang => {
  fetchTranslations(lang)
    .then(addEnd)
    .then(transformTranslation)
    .then(printPercentageTranslated)
    .then(writeJSON)
    .catch(e => console.error(e));
});

/**
 * Fetches translations for given language.
 *
 * @parameter lang {string} Which language to fetch.
 * @parameter retry {bool} If this fetch is a retry.
 * @returns {Promise} Resolves with object { lang, blocks, editor }.
 */
function fetchTranslations (lang, retry) {
  var baseUrl = `http://translate.scratch.mit.edu/download/${lang}/`;

  function handleFetch (res) {
    if (!res.ok) {
      throw new Error(`Failed fetching ${res.url} with code ${res.status} ${res.statusText}.`);
    }
    return res.text().then(parsePo);
  }

  var returnObject = {
    lang: lang
    // editor: parsed translations
    // blocks: parsed translations
  };
  return fetch(baseUrl + 'editor/editor.po')
    .then(res => handleFetch(res))
    .then(res => returnObject.editor = res)
    .then(() => fetch(baseUrl + 'blocks/blocks.po'))
    .then(res => handleFetch(res))
    .then(res => returnObject.blocks = res)
    .then(() => returnObject)
    // retry once on failures
    .catch((err) => {
      if (!retry) {
        return fetchTranslations(lang, true);
      }
      throw new Error(`Fetching ${lang} failed: ${err}`);
    });
}

/**
 * Parses a .po file to an object, with msgid => key and msgstr => value.
 *
 * @parameter content {string}
 * @returns {object}
 */
function parsePo (content) {
  var msgId = null;
  var translations = {};
  var lines = content.split('\n');

  lines.forEach(line => {
    if (line.indexOf('msgid ') === 0) {
      msgId = poLineContent(line, 6);
    } else if (line.indexOf('msgstr ') === 0) {
      var msgStr = poLineContent(line, 7);
      if (msgId && msgStr) {
        translations[msgId] = msgStr;
        msgId = null;
      }
    }
  });

  return translations;
}

/**
 * Get values in .po lines. Example:
 *
 * poLineContent('msgid    "the content"  ', 5)
 *   => 'the content'
 *
 * @parameter line {string} The .po line.
 * @parameter strip {int} How many beginning characters to cut.
 * @returns {string}
 */
function poLineContent (line, strip) {
  var ret = line.substr(strip);
  ret = ret.trim();
  ret = ret.replace(/^"(.*)"$/, '$1');
  return ret;
}

/**
 * Add 'end' special case translation to blocks.
 *
 * @parameter fetchedTranslation {object} Return object from `fetchTranslation`.
 * @returns {object} `fetchedTranslation` with 'end' added.
 */
function addEnd (fetchedTranslation) {
  var aliases = extraAliases[fetchedTranslation.lang];
  var end = getEndBlock(aliases);
  if (end) {
    fetchedTranslation.blocks.end = end;
  }
  return fetchedTranslation;
}

/**
 * Get end block from aliases (extra_aliases.js).
 *
 * @parameter aliases {object}
 * @returns {string} End block in given language or empty string if not found.
 */
function getEndBlock (aliases) {
  for (var langSpec in aliases) {
    // spec is equivalent to msgid in .po
    var spec = aliases[langSpec];
    if (spec === 'end') {
      return langSpec;
    }
  }
  return '';
}

/**
 * Transforms the translation for scratchblocks consumption.
 *
 * Should contain:
 * - aliases
 * - define
 * - ignorelt (ignore less than)
 * - math
 * - osis
 * - commands
 * - dropdowns
 * - palette
 *
 * @parameter fetchedTranslation {object} `returnObject` from `fetchTranslations`.
 * returns {object}
 */
function transformTranslation (fetchedTranslation) {
  // short hands
  var lang = fetchedTranslation.lang;
  var editor = fetchedTranslation.editor;
  var blocks = fetchedTranslation.blocks;

  var translation = {};
  translation.lang = lang;

  translation.aliases = extraAliases[lang];
  warningMissingAlias(lang, translation.aliases);

  translation.define = [ blocks['define'] || '' ];
  translation.ignorelt = [ getWhenDistance(blocks) ];

  translation.commands = translate(lang, COMMAND_SPECS, blocks);
  translation.dropdowns = translate(lang, DROPDOWN_SPECS, blocks, editor);
  translation.palette = translate(lang, PALETTE_SPECS, blocks, editor);

  translation.math = values(translate(lang, MATH_FUNCS, editor));
  translation.osis = values(translate(lang, OSIS, editor));

  delete translation.blocks;
  delete translation.editor;

  return translation;
}

/**
 * Prints a warning if language is missing needed aliases (extra_aliases.js).
 *
 * @parameter lang {string}
 * @parameter aliases {object}
 */
function warningMissingAlias (lang, aliases) {
  if (!aliases) {
    console.log(chalk.red(`${lang} is missing all aliases, add them to extra_aliases.js`));
    return;
  }
  var englishSpecs = values(aliases);
  NEED_ALIAS.forEach(alias => {
    if (englishSpecs.indexOf(alias) === -1) {
      console.log(chalk.red(`${lang} is missing "${alias}" translation in extra_aliases.js`));
    }
  });
}

/**
 * Get translated "when distance < %n" special case.
 * Needed for ignoring the "<" less than sign in "when distance < %n".
 *
 * @parameter blocks {object} Translation for blocks.
 * @returns {string|null} When distance in language, or null if not translated.
 */
function getWhenDistance (blocks) {
  var whenDistance = blocks['when distance < %n'];
  if (!whenDistance) {
    return null;
  }
  return whenDistance.split(' < %n')[0];
}

/**
 * Translates keywords in `specs` if found in `x` or `y`. Example:
 * Prints a warning if translation for spec is not found.
 *
 * translate('no', ['a', 'b', 'c'], {
 *   // this is x
 *   a: 'preferred'
 * }, {
 *   // this is y
 *   a: 'note preferred',
 *   b: 'translation'
 * })
 *   => { a: 'preferred', b: 'translation' }
 *
 * @parameter lang {string} For the warning message.
 * @parameter specs {array} msgid in .po
 * @parameter x {object} {msgid: msgstr, ...}
 * @parameter [y] {object} Optional.
 * @returns {object} {msgid: msgstr, ...} with msgids from specs array.
 */
function translate (lang, specs, x, y) {
  y = y || {};
  var translations = {}
  specs.forEach(spec => {
    var langSpec = x[spec] || y[spec];
    if (langSpec) {
      translations[spec] = langSpec;
    } else if (UNTRANSLATED.indexOf(spec) === -1 &&
               ACCEPTABLE_MISSING.indexOf(spec) === -1) {
    // translation not found and not acceptable missing -> warn
      console.log(chalk.red(`${lang}: missing translation for "${spec}"`));
    }
  });
  return translations;
}

/**
 * Prints translation percentage for COMMAND_SPECS, DROPDOWN_SPECS and
 * PALETTE_SPECS, minus those in UNTRANSLATED and ACCEPTABLE_MISSING.
 *
 * @parameter translation {object} From `transformTranslation`.
 * @returns {object} `translation`
 */
function printPercentageTranslated (translation) {
  var total = removeNotNeeded(COMMAND_SPECS).length;
  total += DROPDOWN_SPECS.length;
  total += PALETTE_SPECS.length;

  var translated = removeNotNeeded(Object.keys(translation.commands)).length;
  translated += Object.keys(translation.dropdowns).length;
  translated += Object.keys(translation.palette).length;

  var percentage = (translated / total * 100).toFixed(0);
  var color = translated === total ? chalk.green : chalk.red;

  console.log(color(`${translation.lang}: translated ${translated} of ${total}, ${percentage} %`));

  return translation;
}

/**
 * Returns a list of commands which are not in `UNTRANSLATED` or
 * `ACCEPTABLE_MISSING`.
 */
function removeNotNeeded (commands) {
  return commands.filter(cmd =>
    UNTRANSLATED.indexOf(cmd) === -1 &&
    ACCEPTABLE_MISSING.indexOf(cmd) === -1);
}

/**
 * Writes the translations from `transformTranslation` to a JSON file.
 *
 * @parameter translation {object}
 */
function writeJSON (translation) {
  var lang = translation.lang;
  var filename = __dirname + '/locales/' + lang + '.json';

  var out = {};
  out[lang] = translation;  // TODO: flatten out object, fix loadLanguage
  delete out[lang].lang;

  fs.writeFileSync(filename, JSON.stringify(out, null, 2));
}
<commit_msg>fetch translations:  not acceptable missing<commit_after>#!/usr/bin/env node

var fs = require('fs');
var fetch = require('node-fetch');
var values = require('object-values');
var unique = require('just-unique');
var chalk = require('chalk');
var extraAliases = require('./locales/extra_aliases.js');

/**
 * CONSTANTS
 */

var ALL_LANGS = ['ar', 'an', 'hy', 'ast', 'eu', 'bn_IN', 'nb', 'bg', 'zh_CN',
                 'zh_TW', 'da', 'de', 'eo', 'et', 'fo', 'fi', 'fr',
                 'gl', 'ht', 'he', 'hi', 'hch', 'id', 'ga', 'is', 'it', 'ja',
                 'ja_HIRA', 'km', 'kn', 'kk', 'ca', 'ko', 'hr', 'ku',
                 'cy', 'ky', 'la', 'lv', 'lt', 'mk', 'ms', 'ml', 'mr', 'maz',
                 'mn', 'my', 'nah', 'ne', 'el', 'nl', 'no', 'nn', 'or', 'os',
                 'oto', 'ote', 'pap', 'fa', 'fil', 'pl', 'pt', 'pt_BR', 'ro',
                 'ru', 'rw', 'sv', 'sr', 'sk', 'sl', 'es', 'sw', 'tzm', 'ta',
                 'th', 'cs', 'tr', 'ug', 'uk', 'hu', 'vi'];

// ISO Codes for all the language forums.
var FORUM_LANGS = ['de', 'es', 'fr', 'zh_CN', 'pl', 'ja', 'nl' , 'pt', 'it',
                   'he', 'ko', 'nb', 'tr', 'el', 'ru', 'ca', 'id'];

var ENGLISH_COMMANDS = require('./commands.js');
var COMMAND_SPECS = unique(ENGLISH_COMMANDS.map(cmd => cmd[0]));
var PALETTE_SPECS = [
  'Motion', 'Looks', 'Sound', 'Pen', 'Data', 'variable',
  'list', 'Events', 'Control', 'Sensing', 'Operators',
  'More Blocks', 'Tips'
];
var NEED_ALIAS = [
  'turn @turnRight %n degrees',
  'turn @turnLeft %n degrees',
  'when @greenFlag clicked'
];
var UNTRANSLATED = [
  '%n + %n',
  '%n - %n',
  '%n * %n',
  '%n / %n',
  '%s < %s',
  '%s = %s',
  '%s > %s',
  '…',
  '...'
];
var ACCEPTABLE_MISSING = [
  'turn %m.motor on for %n seconds',
  'set light color to %n',
  'play note %n for %n seconds',
  'when tilted',
  'tilt %m.xxx',
  'else',
  'end',
  '. . .',
  '%n @addInput',
  'user id',
  'if %b',
  'if %b',
  'forever if %b',
  'stop script',
  'stop all',
  'switch to costume %m.costume',
  'next background',
  'switch to background %m.backdrop',
  'background #',
  'loud?'
];
var MATH_FUNCS = [
  'abs', 'floor', 'ceiling', 'sqrt', 'sin', 'cos', 'tan',
  'asin', 'acos', 'atan', 'ln', 'log', 'e ^', '10 ^'
];
var OSIS = [ 'other scripts in sprite', 'other scripts in stage' ];
var DROPDOWN_SPECS = [
  'A connected', 'all', 'all around',
  'B connected', 'brightness', 'button pressed', 'C connected', 'color',
  'costume name', 'D connected', 'date', 'day of week', 'don\'t rotate',
  'down arrow', 'edge', 'fisheye', 'ghost', 'hour',
  'left arrow', 'left-right', 'light', 'minute', 'month',
  'mosaic', 'motion', 'mouse-pointer',
  'myself', 'off', 'on', 'on-flipped', 'other scripts in sprite',
  'pixelate', 'previous backdrop', 'resistance-A',
  'resistance-B', 'resistance-C', 'resistance-D', 'reverse', 'right arrow',
  'second', 'slider', 'sound', 'space', 'Stage', 'that way', 'this script',
  'this sprite', 'this way', 'up arrow', 'video motion', 'whirl', 'year'
];

/**
 * CONSTANTS END
 */

/**
 * Arguments
 *
 * ./fetch_translations [language code | all]
 *
 * No arguments will fetch forum languages.
 */
var args = process.argv;
var BUILD_LANGS = FORUM_LANGS;
if (args.length > 3 || args[2] === '--help') {
  console.log('\nFetches scratch translations from translate.scratch.mit.edu.');
  console.log('\n  Usage: ./fetch_translations.js [language code | all]');
  console.log('\nIf no language code is given, translations for forum languages will be fetched.');
  process.exit(1);
}  else if (args.length === 3 && args[2] === 'all') {
  BUILD_LANGS = ALL_LANGS;
} else if (args.length === 3 && ALL_LANGS.indexOf(args[2]) === -1) {
  console.log('Language code not valid, supported languages:');
  console.log(ALL_LANGS.join(', '));
} else if (args.length === 3) {
  BUILD_LANGS = [ args[2] ];
}

/**
 * Flow:
 * - fetch
 * - add special cases
 * - transform translations
 * - print statistics
 * - write to JSON
 */
BUILD_LANGS.forEach(lang => {
  fetchTranslations(lang)
    .then(addEnd)
    .then(transformTranslation)
    .then(printPercentageTranslated)
    .then(writeJSON)
    .catch(e => console.error(e));
});

/**
 * Fetches translations for given language.
 *
 * @parameter lang {string} Which language to fetch.
 * @parameter retry {bool} If this fetch is a retry.
 * @returns {Promise} Resolves with object { lang, blocks, editor }.
 */
function fetchTranslations (lang, retry) {
  var baseUrl = `http://translate.scratch.mit.edu/download/${lang}/`;

  function handleFetch (res) {
    if (!res.ok) {
      throw new Error(`Failed fetching ${res.url} with code ${res.status} ${res.statusText}.`);
    }
    return res.text().then(parsePo);
  }

  var returnObject = {
    lang: lang
    // editor: parsed translations
    // blocks: parsed translations
  };
  return fetch(baseUrl + 'editor/editor.po')
    .then(res => handleFetch(res))
    .then(res => returnObject.editor = res)
    .then(() => fetch(baseUrl + 'blocks/blocks.po'))
    .then(res => handleFetch(res))
    .then(res => returnObject.blocks = res)
    .then(() => returnObject)
    // retry once on failures
    .catch((err) => {
      if (!retry) {
        return fetchTranslations(lang, true);
      }
      throw new Error(`Fetching ${lang} failed: ${err}`);
    });
}

/**
 * Parses a .po file to an object, with msgid => key and msgstr => value.
 *
 * @parameter content {string}
 * @returns {object}
 */
function parsePo (content) {
  var msgId = null;
  var translations = {};
  var lines = content.split('\n');

  lines.forEach(line => {
    if (line.indexOf('msgid ') === 0) {
      msgId = poLineContent(line, 6);
    } else if (line.indexOf('msgstr ') === 0) {
      var msgStr = poLineContent(line, 7);
      if (msgId && msgStr) {
        translations[msgId] = msgStr;
        msgId = null;
      }
    }
  });

  return translations;
}

/**
 * Get values in .po lines. Example:
 *
 * poLineContent('msgid    "the content"  ', 5)
 *   => 'the content'
 *
 * @parameter line {string} The .po line.
 * @parameter strip {int} How many beginning characters to cut.
 * @returns {string}
 */
function poLineContent (line, strip) {
  var ret = line.substr(strip);
  ret = ret.trim();
  ret = ret.replace(/^"(.*)"$/, '$1');
  return ret;
}

/**
 * Add 'end' special case translation to blocks.
 *
 * @parameter fetchedTranslation {object} Return object from `fetchTranslation`.
 * @returns {object} `fetchedTranslation` with 'end' added.
 */
function addEnd (fetchedTranslation) {
  var aliases = extraAliases[fetchedTranslation.lang];
  var end = getEndBlock(aliases);
  if (end) {
    fetchedTranslation.blocks.end = end;
  }
  return fetchedTranslation;
}

/**
 * Get end block from aliases (extra_aliases.js).
 *
 * @parameter aliases {object}
 * @returns {string} End block in given language or empty string if not found.
 */
function getEndBlock (aliases) {
  for (var langSpec in aliases) {
    // spec is equivalent to msgid in .po
    var spec = aliases[langSpec];
    if (spec === 'end') {
      return langSpec;
    }
  }
  return '';
}

/**
 * Transforms the translation for scratchblocks consumption.
 *
 * Should contain:
 * - aliases
 * - define
 * - ignorelt (ignore less than)
 * - math
 * - osis
 * - commands
 * - dropdowns
 * - palette
 *
 * @parameter fetchedTranslation {object} `returnObject` from `fetchTranslations`.
 * returns {object}
 */
function transformTranslation (fetchedTranslation) {
  // short hands
  var lang = fetchedTranslation.lang;
  var editor = fetchedTranslation.editor;
  var blocks = fetchedTranslation.blocks;

  var translation = {};
  translation.lang = lang;

  translation.aliases = extraAliases[lang];
  warningMissingAlias(lang, translation.aliases);

  translation.define = [ blocks['define'] || '' ];
  translation.ignorelt = [ getWhenDistance(blocks) ];

  translation.commands = translate(lang, COMMAND_SPECS, blocks);
  translation.dropdowns = translate(lang, DROPDOWN_SPECS, blocks, editor);
  translation.palette = translate(lang, PALETTE_SPECS, blocks, editor);

  translation.math = values(translate(lang, MATH_FUNCS, editor));
  translation.osis = values(translate(lang, OSIS, editor));

  delete translation.blocks;
  delete translation.editor;

  return translation;
}

/**
 * Prints a warning if language is missing needed aliases (extra_aliases.js).
 *
 * @parameter lang {string}
 * @parameter aliases {object}
 */
function warningMissingAlias (lang, aliases) {
  if (!aliases) {
    console.log(chalk.red(`${lang} is missing all aliases, add them to extra_aliases.js`));
    return;
  }
  var englishSpecs = values(aliases);
  NEED_ALIAS.forEach(alias => {
    if (englishSpecs.indexOf(alias) === -1) {
      console.log(chalk.red(`${lang} is missing "${alias}" translation in extra_aliases.js`));
    }
  });
}

/**
 * Get translated "when distance < %n" special case.
 * Needed for ignoring the "<" less than sign in "when distance < %n".
 *
 * @parameter blocks {object} Translation for blocks.
 * @returns {string|null} When distance in language, or null if not translated.
 */
function getWhenDistance (blocks) {
  var whenDistance = blocks['when distance < %n'];
  if (!whenDistance) {
    return null;
  }
  return whenDistance.split(' < %n')[0];
}

/**
 * Translates keywords in `specs` if found in `x` or `y`. Example:
 * Prints a warning if translation for spec is not found.
 *
 * translate('no', ['a', 'b', 'c'], {
 *   // this is x
 *   a: 'preferred'
 * }, {
 *   // this is y
 *   a: 'note preferred',
 *   b: 'translation'
 * })
 *   => { a: 'preferred', b: 'translation' }
 *
 * @parameter lang {string} For the warning message.
 * @parameter specs {array} msgid in .po
 * @parameter x {object} {msgid: msgstr, ...}
 * @parameter [y] {object} Optional.
 * @returns {object} {msgid: msgstr, ...} with msgids from specs array.
 */
function translate (lang, specs, x, y) {
  y = y || {};
  var translations = {}
  specs.forEach(spec => {
    var langSpec = x[spec] || y[spec];
    if (langSpec) {
      translations[spec] = langSpec;
    } else if (UNTRANSLATED.indexOf(spec) === -1 &&
               ACCEPTABLE_MISSING.indexOf(spec) === -1) {
    // translation not found and not acceptable missing -> warn
      console.log(chalk.red(`${lang}: missing translation for "${spec}"`));
    }
  });
  return translations;
}

/**
 * Prints translation percentage for COMMAND_SPECS, DROPDOWN_SPECS and
 * PALETTE_SPECS, minus those in UNTRANSLATED and ACCEPTABLE_MISSING.
 *
 * @parameter translation {object} From `transformTranslation`.
 * @returns {object} `translation`
 */
function printPercentageTranslated (translation) {
  var total = removeNotNeeded(COMMAND_SPECS).length;
  total += DROPDOWN_SPECS.length;
  total += PALETTE_SPECS.length;

  var translated = removeNotNeeded(Object.keys(translation.commands)).length;
  translated += Object.keys(translation.dropdowns).length;
  translated += Object.keys(translation.palette).length;

  var percentage = (translated / total * 100).toFixed(0);
  var color = translated === total ? chalk.green : chalk.red;

  console.log(color(`${translation.lang}: translated ${translated} of ${total}, ${percentage} %`));

  return translation;
}

/**
 * Returns a list of commands which are not in `UNTRANSLATED` or
 * `ACCEPTABLE_MISSING`.
 */
function removeNotNeeded (commands) {
  return commands.filter(cmd =>
    UNTRANSLATED.indexOf(cmd) === -1 &&
    ACCEPTABLE_MISSING.indexOf(cmd) === -1);
}

/**
 * Writes the translations from `transformTranslation` to a JSON file.
 *
 * @parameter translation {object}
 */
function writeJSON (translation) {
  var lang = translation.lang;
  var filename = __dirname + '/locales/' + lang + '.json';

  var out = {};
  out[lang] = translation;  // TODO: flatten out object, fix loadLanguage
  delete out[lang].lang;

  fs.writeFileSync(filename, JSON.stringify(out, null, 2));
}
<|endoftext|>


<commit_before>/**
 * @author zhixin wen <wenzhixin2010@gmail.com>
 * @version 1.1.0
 *
 * http://wenzhixin.net.cn/p/multiple-select/
 */

(function($) {

    'use strict';

    function MultipleSelect($el, options) {
        var that = this,
            name = $el.attr('name') || options.name || '',
            elWidth = $el.width();

        this.$el = $el.hide();
        this.options = options;

        this.$parent = $('

');
        this.$choice = $('<button type="button" class="ms-choice">' +
            options.placeholder + '

</button>');
        this.$drop = $('

');
        this.$el.after(this.$parent);
        this.$parent.append(this.$choice);
        this.$parent.append(this.$drop);

        if (this.$el.prop('disabled')) {
            this.$choice.addClass('disabled');
        }
        this.$choice.css('width', elWidth + 'px');
        this.$drop.css({
            width: (options.width || elWidth) + 'px'
        });

        if (!this.options.keepOpen) {
            $('body').click(function(e) {
                if ($(e.target)[0] === that.$choice[0] ||
                    $(e.target).parents('.ms-choice')[0] === that.$choice[0]) {
                    return;
                }
                if (($(e.target)[0] === that.$drop[0] ||
                    $(e.target).parents('.ms-drop')[0] !== that.$drop[0]) &&
                    that.options.isOpen) {
                    that.close();
                }
            });
        }

        this.selectAllName = 'name="selectAll' + name + '"';
        this.selectGroupName = 'name="selectGroup' + name + '"';
        this.selectItemName = 'name="selectItem' + name + '"';
    }

    MultipleSelect.prototype = {
        constructor : MultipleSelect,

        init: function() {
            var that = this,
                html = [];
            if (this.options.filter) {
                html.push(
                    '
',
                        '<input type="text" autocomplete="off" autocorrect="off" autocapitilize="off" spellcheck="false">',
                    '
'
                );
            }
            html.push('
');
            if (this.options.selectAll && !this.options.single) {
                html.push(
                    '
',
                        '',
                            '<input type="checkbox" ' + this.selectAllName + ' /> ',
                            '[' + this.options.selectAllText + ']',
                        '',
                    '
'
                );
            }
            $.each(this.$el.children(), function(i, elm) {
                html.push(that.optionToHtml(i, elm));
            });
            html.push('
No matches found
');
            html.push('
');
            this.$drop.html(html.join(''));
            this.$drop.find('ul').css('max-height', this.options.maxHeight + 'px');
            this.$drop.find('.multiple').css('width', this.options.multipleWidth + 'px');

            this.$searchInput = this.$drop.find('.ms-search input');
            this.$selectAll = this.$drop.find('input[' + this.selectAllName + ']');
            this.$selectGroups = this.$drop.find('input[' + this.selectGroupName + ']');
            this.$selectItems = this.$drop.find('input[' + this.selectItemName + ']:enabled');
            this.$disableItems = this.$drop.find('input[' + this.selectItemName + ']:disabled');
            this.$noResults = this.$drop.find('.ms-no-results');
            this.events();
            this.update();

            if (this.options.isOpen) {
                this.open();
            }
        },

        optionToHtml: function(i, elm, group, groupDisabled) {
            var that = this,
                $elm = $(elm),
                html = [],
                multiple = this.options.multiple,
                disabled,
                type = this.options.single ? 'radio' : 'checkbox';

            if ($elm.is('option')) {
                var value = $elm.val(),
                    text = $elm.text(),
                    selected = $elm.prop('selected'),
                    style = this.options.styler(value) ? ' style="' + this.options.styler(value) + '"' : '';

                disabled = groupDisabled || $elm.prop('disabled');
                html.push(
                    '
',
                        '',
                            '<input type="' + type + '" ' + this.selectItemName + ' value="' + value + '"' +
                                (selected ? ' checked="checked"' : '') +
                                (disabled ? ' disabled="disabled"' : '') +
                                (group ? ' data-group="' + group + '"' : '') +
                                '/> ',
                            text,
                        '',
                    '
'
                );
            } else if (!group && $elm.is('optgroup')) {
                var _group = 'group_' + i,
                    label = $elm.attr('label');

                disabled = $elm.prop('disabled');
                html.push(
                    '
',
                        '',
                            '<input type="checkbox" ' + this.selectGroupName +
                                (disabled ? ' disabled="disabled"' : '') + ' /> ',
                            label,
                        '',
                    '
');
                $.each($elm.children(), function(i, elm) {
                    html.push(that.optionToHtml(i, elm, _group, disabled));
                });
            }
            return html.join('');
        },

        events: function() {
            var that = this;
            this.$choice.off('click').on('click', function(e) {
                e.preventDefault();
                that[that.options.isOpen ? 'close' : 'open']();
            })
                .off('focus').on('focus', this.options.onFocus)
                .off('blur').on('blur', this.options.onBlur);

            this.$parent.off('keydown').on('keydown', function(e) {
                switch (e.which) {
                    case 27: // esc key
                        that.close();
                        that.$choice.focus();
                        break;
                }
            });
            this.$searchInput.off('keyup').on('keyup', function() {
                that.filter();
            });
            this.$selectAll.off('click').on('click', function() {
                var checked = $(this).prop('checked'),
                    $items = that.$selectItems.filter(':visible');
                if ($items.length === that.$selectItems.length) {
                    that[checked ? 'checkAll' : 'uncheckAll']();
                } else { // when the filter option is true
                    that.$selectGroups.prop('checked', checked);
                    $items.prop('checked', checked);
                    that.options[checked ? 'onCheckAll' : 'onUncheckAll']();
                    that.update();
                }
            });
            this.$selectGroups.off('click').on('click', function() {
                var group = $(this).parent().attr('data-group'),
                    $items = that.$selectItems.filter(':visible'),
                    $children = $items.filter('[data-group="' + group + '"]'),
                    checked = $children.length !== $children.filter(':checked').length;
                $children.prop('checked', checked);
                that.updateSelectAll();
                that.update();
                that.options.onOptgroupClick({
                    label: $(this).parent().text(),
                    checked: checked,
                    children: $children.get()
                });
            });
            this.$selectItems.off('click').on('click', function() {
                that.updateSelectAll();
                that.update();
                that.updateOptGroupSelect();
                that.options.onClick({
                    label: $(this).parent().text(),
                    value: $(this).val(),
                    checked: $(this).prop('checked')
                });
            });
        },

        open: function() {
            if (this.$choice.hasClass('disabled')) {
                return;
            }
            this.options.isOpen = true;
            this.$choice.find('>div').addClass('open');
            this.$drop.show();
            if (this.options.container) {
                var offset = this.$drop.offset();
                this.$drop.appendTo($(this.options.container));
                this.$drop.offset({ top: offset.top, left: offset.left });
            }
            if (this.options.filter) {
                this.$searchInput.val('');
                this.filter();
            }
            this.options.onOpen();
        },

        close: function() {
            this.options.isOpen = false;
            this.$choice.find('>div').removeClass('open');
            this.$drop.hide();
            if (this.options.container) {
                this.$parent.append(this.$drop);
                this.$drop.css({
                    'top': 'auto',
                    'left': 'auto'
                })
            }
            this.options.onClose();
        },

        update: function() {
            var selects = this.getSelects('text'),
                $span = this.$choice.find('>span');
            if (selects.length === this.$selectItems.length + this.$disableItems.length && this.options.allSelected) {
                $span.removeClass('placeholder').html(this.options.allSelected);
            } else if (selects.length > this.options.minumimCountSelected && this.options.countSelected) {
                $span.removeClass('placeholder').html(this.options.countSelected
                    .replace('#', selects.length)
                    .replace('%', this.$selectItems.length + this.$disableItems.length));
            } else if (selects.length) {
                $span.removeClass('placeholder').html(selects.join(', '));
            } else {
                $span.addClass('placeholder').html(this.options.placeholder);
            }
            // set selects to select
            this.$el.val(this.getSelects());
        },

        updateSelectAll: function() {
            var $items = this.$selectItems.filter(':visible');
            this.$selectAll.prop('checked', $items.length &&
                $items.length === $items.filter(':checked').length);
            if (this.$selectAll.prop('checked')) {
                this.options.onCheckAll();
            }
        },

        updateOptGroupSelect: function() {
            var $items = this.$selectItems.filter(':visible');
            $.each(this.$selectGroups, function(i, val) {
                var group = $(val).parent().attr('data-group'),
                    $children = $items.filter('[data-group="' + group + '"]');
                $(val).prop('checked', $children.length &&
                    $children.length === $children.filter(':checked').length);
            });
        },

        //value or text, default: 'value'
        getSelects: function(type) {
            var that = this,
                texts = [],
                values = [];
            this.$drop.find('input[' + this.selectItemName + ']:checked').each(function() {
                texts.push($(this).parent().text());
                values.push($(this).val());
            });

            if (type === 'text' && this.$selectGroups.length) {
                texts = [];
                this.$selectGroups.each(function() {
                    var html = [],
                        text = $.trim($(this).parent().text()),
                        group = $(this).parent().data('group'),
                        $children = that.$drop.find('[' + that.selectItemName + '][data-group="' + group + '"]'),
                        $selected = $children.filter(':checked');

                    if ($selected.length === 0) {
                        return;
                    }

                    html.push('[');
                    html.push(text);
                    if ($children.length > $selected.length) {
                        var list = [];
                        $selected.each(function() {
                            list.push($(this).parent().text());
                        });
                        html.push(': ' + list.join(', '));
                    }
                    html.push(']');
                    texts.push(html.join(''));
                });
            }
            return type === 'text' ? texts : values;
        },

        setSelects: function(values) {
            var that = this;
            this.$selectItems.prop('checked', false);
            $.each(values, function(i, value) {
                that.$selectItems.filter('[value="' + value + '"]').prop('checked', true);
            });
            this.$selectAll.prop('checked', this.$selectItems.length ===
                this.$selectItems.filter(':checked').length);
            this.update();
        },

        enable: function() {
            this.$choice.removeClass('disabled');
        },

        disable: function() {
            this.$choice.addClass('disabled');
        },

        checkAll: function() {
            this.$selectItems.prop('checked', true);
            this.$selectGroups.prop('checked', true);
            this.$selectAll.prop('checked', true);
            this.update();
            this.options.onCheckAll();
        },

        uncheckAll: function() {
            this.$selectItems.prop('checked', false);
            this.$selectGroups.prop('checked', false);
            this.$selectAll.prop('checked', false);
            this.update();
            this.options.onUncheckAll();
        },

        focus: function() {
            this.$choice.focus();
            this.options.onFocus();
        },

        blur: function() {
            this.$choice.blur();
            this.options.onBlur();
        },

        refresh: function() {
            this.init();
        },

        filter: function() {
            var that = this,
                text = $.trim(this.$searchInput.val()).toLowerCase();
            if (text.length === 0) {
                this.$selectItems.parent().show();
                this.$disableItems.parent().show();
                this.$selectGroups.parent().show();
            } else {
                this.$selectItems.each(function() {
                    var $parent = $(this).parent();
                    $parent[$parent.text().toLowerCase().indexOf(text) < 0 ? 'hide' : 'show']();
                });
                this.$disableItems.parent().hide();
                this.$selectGroups.each(function() {
                    var $parent = $(this).parent();
                    var group = $parent.attr('data-group'),
                        $items = that.$selectItems.filter(':visible');
                    $parent[$items.filter('[data-group="' + group + '"]').length === 0 ? 'hide' : 'show']();
                });

                //Check if no matches found
                if (this.$selectItems.filter(':visible').length) {
                    this.$selectAll.parent().show();
                    this.$noResults.hide();
                } else {
                    this.$selectAll.parent().hide();
                    this.$noResults.show();
                }
            }
            this.updateOptGroupSelect();
            this.updateSelectAll();
        }
    };

    $.fn.multipleSelect = function() {
        var option = arguments[0],
            args = arguments,

            value,
            allowedMethods = [
                'getSelects', 'setSelects',
                'enable', 'disable',
                'checkAll', 'uncheckAll',
                'focus', 'blur',
                'refresh'
            ];

        this.each(function() {
            var $this = $(this),
                data = $this.data('multipleSelect'),
                options = $.extend({}, $.fn.multipleSelect.defaults,
                    $this.data(), typeof option === 'object' && option);

            if (!data) {
                data = new MultipleSelect($this, options);
                $this.data('multipleSelect', data);
            }

            if (typeof option === 'string') {
                if ($.inArray(option, allowedMethods) < 0) {
                    throw "Unknown method: " + option;
                }
                value = data[option](args[1]);
            } else {
                data.init();
            }
        });

        return value ? value : this;
    };

    $.fn.multipleSelect.defaults = {
        name: '',
        isOpen: false,
        placeholder: '',
        selectAll: true,
        selectAllText: 'Select all',
        allSelected: 'All selected',
        minumimCountSelected: 3,
        countSelected: '# of % selected',
        multiple: false,
        multipleWidth: 80,
        single: false,
        filter: false,
        width: undefined,
        maxHeight: 250,
        container: null,
        position: 'bottom',
        keepOpen: false,

        styler: function() {return false;},

        onOpen: function() {return false;},
        onClose: function() {return false;},
        onCheckAll: function() {return false;},
        onUncheckAll: function() {return false;},
        onFocus: function() {return false;},
        onBlur: function() {return false;},
        onOptgroupClick: function() {return false;},
        onClick: function() {return false;}
    };
})(jQuery);
<commit_msg>Fix #48: auto hide when the single option is set to true.<commit_after>/**
 * @author zhixin wen <wenzhixin2010@gmail.com>
 * @version 1.1.0
 *
 * http://wenzhixin.net.cn/p/multiple-select/
 */

(function($) {

    'use strict';

    function MultipleSelect($el, options) {
        var that = this,
            name = $el.attr('name') || options.name || '',
            elWidth = $el.width();

        this.$el = $el.hide();
        this.options = options;

        this.$parent = $('

');
        this.$choice = $('<button type="button" class="ms-choice">' +
            options.placeholder + '

</button>');
        this.$drop = $('

');
        this.$el.after(this.$parent);
        this.$parent.append(this.$choice);
        this.$parent.append(this.$drop);

        if (this.$el.prop('disabled')) {
            this.$choice.addClass('disabled');
        }
        this.$choice.css('width', elWidth + 'px');
        this.$drop.css({
            width: (options.width || elWidth) + 'px'
        });

        if (!this.options.keepOpen) {
            $('body').click(function(e) {
                if ($(e.target)[0] === that.$choice[0] ||
                    $(e.target).parents('.ms-choice')[0] === that.$choice[0]) {
                    return;
                }
                if (($(e.target)[0] === that.$drop[0] ||
                    $(e.target).parents('.ms-drop')[0] !== that.$drop[0]) &&
                    that.options.isOpen) {
                    that.close();
                }
            });
        }

        this.selectAllName = 'name="selectAll' + name + '"';
        this.selectGroupName = 'name="selectGroup' + name + '"';
        this.selectItemName = 'name="selectItem' + name + '"';
    }

    MultipleSelect.prototype = {
        constructor : MultipleSelect,

        init: function() {
            var that = this,
                html = [];
            if (this.options.filter) {
                html.push(
                    '
',
                        '<input type="text" autocomplete="off" autocorrect="off" autocapitilize="off" spellcheck="false">',
                    '
'
                );
            }
            html.push('
');
            if (this.options.selectAll && !this.options.single) {
                html.push(
                    '
',
                        '',
                            '<input type="checkbox" ' + this.selectAllName + ' /> ',
                            '[' + this.options.selectAllText + ']',
                        '',
                    '
'
                );
            }
            $.each(this.$el.children(), function(i, elm) {
                html.push(that.optionToHtml(i, elm));
            });
            html.push('
No matches found
');
            html.push('
');
            this.$drop.html(html.join(''));
            this.$drop.find('ul').css('max-height', this.options.maxHeight + 'px');
            this.$drop.find('.multiple').css('width', this.options.multipleWidth + 'px');

            this.$searchInput = this.$drop.find('.ms-search input');
            this.$selectAll = this.$drop.find('input[' + this.selectAllName + ']');
            this.$selectGroups = this.$drop.find('input[' + this.selectGroupName + ']');
            this.$selectItems = this.$drop.find('input[' + this.selectItemName + ']:enabled');
            this.$disableItems = this.$drop.find('input[' + this.selectItemName + ']:disabled');
            this.$noResults = this.$drop.find('.ms-no-results');
            this.events();
            this.update();

            if (this.options.isOpen) {
                this.open();
            }
        },

        optionToHtml: function(i, elm, group, groupDisabled) {
            var that = this,
                $elm = $(elm),
                html = [],
                multiple = this.options.multiple,
                disabled,
                type = this.options.single ? 'radio' : 'checkbox';

            if ($elm.is('option')) {
                var value = $elm.val(),
                    text = $elm.text(),
                    selected = $elm.prop('selected'),
                    style = this.options.styler(value) ? ' style="' + this.options.styler(value) + '"' : '';

                disabled = groupDisabled || $elm.prop('disabled');
                html.push(
                    '
',
                        '',
                            '<input type="' + type + '" ' + this.selectItemName + ' value="' + value + '"' +
                                (selected ? ' checked="checked"' : '') +
                                (disabled ? ' disabled="disabled"' : '') +
                                (group ? ' data-group="' + group + '"' : '') +
                                '/> ',
                            text,
                        '',
                    '
'
                );
            } else if (!group && $elm.is('optgroup')) {
                var _group = 'group_' + i,
                    label = $elm.attr('label');

                disabled = $elm.prop('disabled');
                html.push(
                    '
',
                        '',
                            '<input type="checkbox" ' + this.selectGroupName +
                                (disabled ? ' disabled="disabled"' : '') + ' /> ',
                            label,
                        '',
                    '
');
                $.each($elm.children(), function(i, elm) {
                    html.push(that.optionToHtml(i, elm, _group, disabled));
                });
            }
            return html.join('');
        },

        events: function() {
            var that = this;
            this.$choice.off('click').on('click', function(e) {
                e.preventDefault();
                that[that.options.isOpen ? 'close' : 'open']();
            })
                .off('focus').on('focus', this.options.onFocus)
                .off('blur').on('blur', this.options.onBlur);

            this.$parent.off('keydown').on('keydown', function(e) {
                switch (e.which) {
                    case 27: // esc key
                        that.close();
                        that.$choice.focus();
                        break;
                }
            });
            this.$searchInput.off('keyup').on('keyup', function() {
                that.filter();
            });
            this.$selectAll.off('click').on('click', function() {
                var checked = $(this).prop('checked'),
                    $items = that.$selectItems.filter(':visible');
                if ($items.length === that.$selectItems.length) {
                    that[checked ? 'checkAll' : 'uncheckAll']();
                } else { // when the filter option is true
                    that.$selectGroups.prop('checked', checked);
                    $items.prop('checked', checked);
                    that.options[checked ? 'onCheckAll' : 'onUncheckAll']();
                    that.update();
                }
            });
            this.$selectGroups.off('click').on('click', function() {
                var group = $(this).parent().attr('data-group'),
                    $items = that.$selectItems.filter(':visible'),
                    $children = $items.filter('[data-group="' + group + '"]'),
                    checked = $children.length !== $children.filter(':checked').length;
                $children.prop('checked', checked);
                that.updateSelectAll();
                that.update();
                that.options.onOptgroupClick({
                    label: $(this).parent().text(),
                    checked: checked,
                    children: $children.get()
                });
            });
            this.$selectItems.off('click').on('click', function() {
                that.updateSelectAll();
                that.update();
                that.updateOptGroupSelect();
                that.options.onClick({
                    label: $(this).parent().text(),
                    value: $(this).val(),
                    checked: $(this).prop('checked')
                });

                if (that.options.single && that.options.isOpen && !that.options.keepOpen) {
                    that.close();
                }
            });
        },

        open: function() {
            if (this.$choice.hasClass('disabled')) {
                return;
            }
            this.options.isOpen = true;
            this.$choice.find('>div').addClass('open');
            this.$drop.show();
            if (this.options.container) {
                var offset = this.$drop.offset();
                this.$drop.appendTo($(this.options.container));
                this.$drop.offset({ top: offset.top, left: offset.left });
            }
            if (this.options.filter) {
                this.$searchInput.val('');
                this.filter();
            }
            this.options.onOpen();
        },

        close: function() {
            this.options.isOpen = false;
            this.$choice.find('>div').removeClass('open');
            this.$drop.hide();
            if (this.options.container) {
                this.$parent.append(this.$drop);
                this.$drop.css({
                    'top': 'auto',
                    'left': 'auto'
                })
            }
            this.options.onClose();
        },

        update: function() {
            var selects = this.getSelects('text'),
                $span = this.$choice.find('>span');
            if (selects.length === this.$selectItems.length + this.$disableItems.length && this.options.allSelected) {
                $span.removeClass('placeholder').html(this.options.allSelected);
            } else if (selects.length > this.options.minumimCountSelected && this.options.countSelected) {
                $span.removeClass('placeholder').html(this.options.countSelected
                    .replace('#', selects.length)
                    .replace('%', this.$selectItems.length + this.$disableItems.length));
            } else if (selects.length) {
                $span.removeClass('placeholder').html(selects.join(', '));
            } else {
                $span.addClass('placeholder').html(this.options.placeholder);
            }
            // set selects to select
            this.$el.val(this.getSelects());
        },

        updateSelectAll: function() {
            var $items = this.$selectItems.filter(':visible');
            this.$selectAll.prop('checked', $items.length &&
                $items.length === $items.filter(':checked').length);
            if (this.$selectAll.prop('checked')) {
                this.options.onCheckAll();
            }
        },

        updateOptGroupSelect: function() {
            var $items = this.$selectItems.filter(':visible');
            $.each(this.$selectGroups, function(i, val) {
                var group = $(val).parent().attr('data-group'),
                    $children = $items.filter('[data-group="' + group + '"]');
                $(val).prop('checked', $children.length &&
                    $children.length === $children.filter(':checked').length);
            });
        },

        //value or text, default: 'value'
        getSelects: function(type) {
            var that = this,
                texts = [],
                values = [];
            this.$drop.find('input[' + this.selectItemName + ']:checked').each(function() {
                texts.push($(this).parent().text());
                values.push($(this).val());
            });

            if (type === 'text' && this.$selectGroups.length) {
                texts = [];
                this.$selectGroups.each(function() {
                    var html = [],
                        text = $.trim($(this).parent().text()),
                        group = $(this).parent().data('group'),
                        $children = that.$drop.find('[' + that.selectItemName + '][data-group="' + group + '"]'),
                        $selected = $children.filter(':checked');

                    if ($selected.length === 0) {
                        return;
                    }

                    html.push('[');
                    html.push(text);
                    if ($children.length > $selected.length) {
                        var list = [];
                        $selected.each(function() {
                            list.push($(this).parent().text());
                        });
                        html.push(': ' + list.join(', '));
                    }
                    html.push(']');
                    texts.push(html.join(''));
                });
            }
            return type === 'text' ? texts : values;
        },

        setSelects: function(values) {
            var that = this;
            this.$selectItems.prop('checked', false);
            $.each(values, function(i, value) {
                that.$selectItems.filter('[value="' + value + '"]').prop('checked', true);
            });
            this.$selectAll.prop('checked', this.$selectItems.length ===
                this.$selectItems.filter(':checked').length);
            this.update();
        },

        enable: function() {
            this.$choice.removeClass('disabled');
        },

        disable: function() {
            this.$choice.addClass('disabled');
        },

        checkAll: function() {
            this.$selectItems.prop('checked', true);
            this.$selectGroups.prop('checked', true);
            this.$selectAll.prop('checked', true);
            this.update();
            this.options.onCheckAll();
        },

        uncheckAll: function() {
            this.$selectItems.prop('checked', false);
            this.$selectGroups.prop('checked', false);
            this.$selectAll.prop('checked', false);
            this.update();
            this.options.onUncheckAll();
        },

        focus: function() {
            this.$choice.focus();
            this.options.onFocus();
        },

        blur: function() {
            this.$choice.blur();
            this.options.onBlur();
        },

        refresh: function() {
            this.init();
        },

        filter: function() {
            var that = this,
                text = $.trim(this.$searchInput.val()).toLowerCase();
            if (text.length === 0) {
                this.$selectItems.parent().show();
                this.$disableItems.parent().show();
                this.$selectGroups.parent().show();
            } else {
                this.$selectItems.each(function() {
                    var $parent = $(this).parent();
                    $parent[$parent.text().toLowerCase().indexOf(text) < 0 ? 'hide' : 'show']();
                });
                this.$disableItems.parent().hide();
                this.$selectGroups.each(function() {
                    var $parent = $(this).parent();
                    var group = $parent.attr('data-group'),
                        $items = that.$selectItems.filter(':visible');
                    $parent[$items.filter('[data-group="' + group + '"]').length === 0 ? 'hide' : 'show']();
                });

                //Check if no matches found
                if (this.$selectItems.filter(':visible').length) {
                    this.$selectAll.parent().show();
                    this.$noResults.hide();
                } else {
                    this.$selectAll.parent().hide();
                    this.$noResults.show();
                }
            }
            this.updateOptGroupSelect();
            this.updateSelectAll();
        }
    };

    $.fn.multipleSelect = function() {
        var option = arguments[0],
            args = arguments,

            value,
            allowedMethods = [
                'getSelects', 'setSelects',
                'enable', 'disable',
                'checkAll', 'uncheckAll',
                'focus', 'blur',
                'refresh'
            ];

        this.each(function() {
            var $this = $(this),
                data = $this.data('multipleSelect'),
                options = $.extend({}, $.fn.multipleSelect.defaults,
                    $this.data(), typeof option === 'object' && option);

            if (!data) {
                data = new MultipleSelect($this, options);
                $this.data('multipleSelect', data);
            }

            if (typeof option === 'string') {
                if ($.inArray(option, allowedMethods) < 0) {
                    throw "Unknown method: " + option;
                }
                value = data[option](args[1]);
            } else {
                data.init();
            }
        });

        return value ? value : this;
    };

    $.fn.multipleSelect.defaults = {
        name: '',
        isOpen: false,
        placeholder: '',
        selectAll: true,
        selectAllText: 'Select all',
        allSelected: 'All selected',
        minumimCountSelected: 3,
        countSelected: '# of % selected',
        multiple: false,
        multipleWidth: 80,
        single: false,
        filter: false,
        width: undefined,
        maxHeight: 250,
        container: null,
        position: 'bottom',
        keepOpen: false,

        styler: function() {return false;},

        onOpen: function() {return false;},
        onClose: function() {return false;},
        onCheckAll: function() {return false;},
        onUncheckAll: function() {return false;},
        onFocus: function() {return false;},
        onBlur: function() {return false;},
        onOptgroupClick: function() {return false;},
        onClick: function() {return false;}
    };
})(jQuery);
<|endoftext|>