<commit_before>(function($) {
    var plugin = $(this);

	$.fn.validate = function(options) {

		// Default config
		var defaults = {
			auto: false,
			error: '#EB4847',
			success: '#63C76A'
		};

		// Update all configs
		var settings = $.extend(defaults, options);

		var has_errors = false;

		// Check all fields
		$(this).each(function() {

			if(settings.auto === true){
				var error = plugin.check($(this), settings);
				if(error === true){
					has_errors = true;
				}
			}

			$(this).keyup(function(e) {
				e.preventDefault();
				// Ignore enter
				if (e.which === 13) {
					return;
				}
				var error = plugin.check($(this), settings);
				if(error === true){
					has_errors = true;
				}
			});
		});

		// Invert result !error = validation successful
		return !has_errors;
	};

	/**
	 * Run the rules in the field.
	 *
 	 * @param object value
 	 * @param object value
	 */
	plugin.check = function(element, settings) {
		// Explode rules
		var rules = $(element).data('rules').split('|');

		var error = false;
		for (var i = 0; i < rules.length; i++) {
			var rule = rules[i];
			var regex = /(.*?)\[(.*?)\]/;

			var func = rule;
			var options = '';

			// Check if arguments are enclosed in brackets
			var groups = regex.exec(rule);
			if(groups !== null){
				func = groups[1];
				options = groups[2];
			}

			// If the function exists call it.
			if (typeof(plugin[func]) !== "undefined") {
				error = plugin[func]($(element).val(), options);
			}
		}

		// Apply the correct css to the field.
		if(error){
			$(element).css('border-color', settings.error);
		}
		else{
			$(element).css('border-color', settings.success);
		}
		return error;
	};

	/**
	 * Checks if the field is filled.
	 *
 	 * @param string value
	 */
	plugin.required = function(value) {
		if(value.length === 0){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the field is x characters long.
	 *
 	 * @param string value
 	 * @param integer value
	 */
	plugin.exactlength = function(value, options) {
		if(value.length === options){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the field is at least x characters long.
	 *
 	 * @param string value
 	 * @param integer value
	 */
	plugin.minlength = function(value, options) {
		if(value.length < options){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the field is less than x characters long.
 	 * @param string value
 	 * @param integer value
	 */
	plugin.maxlength = function(value, options) {
		if(value.length > options){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the field only contains letters.
	 *
 	 * @param string value
	 */
	plugin.alpha = function(value) {
		var regex = /^([a-z])+$/i;
		return !regex.test(value);
	};

	/**
	 * Checks if the field only contains alpha-numeric characters.
	 *
 	 * @param string value
	 */
	plugin.alphanumeric = function(value) {
		var regex = /^([a-z0-9])+$/i;
		return !regex.test(value);
	};

	/**
	 * Checks if the field is numeric.
	 *
 	 * @param string value
	 */
	plugin.numeric = function(value) {
		var regex = /^[\-+]?[0-9]*\.?[0-9]+$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the field is integer.
	 *
 	 * @param string value
	 */
	plugin.integer = function(value) {
		var regex = /^[\-+]?[0-9]+$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the field is natrual.
	 *
 	 * @param string value
	 */
	plugin.natrual = function(value) {
		var regex = /^[0-9]+$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the field is decimal.
	 *
 	 * @param string value
	 */
	plugin.decimal = function(value) {
		var regex = /^[\-+]?[0-9]+\.[0-9]+$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the numeric value is between two values.
	 *
 	 * @param string value
 	 * @param integer value
	 */
	plugin.between = function(value, options) {
		var minmax = options.split('-');
		if(value < minmax[0]){
			return true;
		}
		if(value > minmax[1]){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the value matches with the field.
	 *
 	 * @param string value
 	 * @param string options
	 */
	plugin.matches = function(value, options) {
		var other = $(options).val();
		if(value !== other){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the value is a valid email.
	 * http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
	 *
 	 * @param string value
	 */
	plugin.email = function(value) {
		var regex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the value is a valid url.
	 * http://stackoverflow.com/questions/1303872/trying-to-validate-url-using-javascript
	 *
 	 * @param string value
	 */
	plugin.url = function(value) {
		var regex = /^(http|https):\/\/(([a-zA-Z0-9$\-_.+!*'(),;:&=]|%[0-9a-fA-F]{2})+@)?(((25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9]|[1-9][0-9]|[0-9])(\.(25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9]|[1-9][0-9]|[0-9])){3})|localhost|([a-zA-Z0-9\-\u00C0-\u017F]+\.)+([a-zA-Z]{2,}))(:[0-9]+)?(\/(([a-zA-Z0-9$\-_.+!*'(),;:@&=]|%[0-9a-fA-F]{2})*(\/([a-zA-Z0-9$\-_.+!*'(),;:@&=]|%[0-9a-fA-F]{2})*)*)?(\?([a-zA-Z0-9$\-_.+!*'(),;:@&=\/?]|%[0-9a-fA-F]{2})*)?(\#([a-zA-Z0-9$\-_.+!*'(),;:@&=\/?]|%[0-9a-fA-F]{2})*)?)?$/;;
		return !regex.test(value);
	};

	/**
	 * Checks if the value is a valid ip.
	 * http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address
	 *
 	 * @param string value
	 */
	plugin.ip = function(value) {
		var regex = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the value is a valid time.
	 *
 	 * @param string value
	 */
	plugin.time = function(value) {
		var regex = /^([0]\d|[1][0-2]):([0-5]\d)\s?(?:AM|PM)$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the value is a valid date.
	 *
 	 * @param string value
	 */
	plugin.date = function(value) {
		var regex = /^\d{2}[./-]\d{2}[./-]\d{4}$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the value is a valid credit card number.
	 *
 	 * @param string value
 	 * @param string options
	 */
	plugin.cc = function(value, options) {
		var cc = [
			/^4\d{3}-?\d{4}-?\d{4}-?\d{4}$/, // Visa
			/^5[1-5]\d{2}-?\d{4}-?\d{4}-?\d{4}$/, // Mastercard
			/^6011-?\d{4}-?\d{4}-?\d{4}$/, // Discover
			/^3[4,7]\d{13}$/, // American Express
			/^3[0,6,8]\d{12}$/ // Diners
		];

		// If options is not set check with all regex
		if(options.length === 0){
			var valid = false;
			for(var i = 0; i < cc.length; i++){
				if(cc[i].test(value)){
					valid = true;
				}
			}
			if(valid === false){
				return true;
			}
		}
		else{
			var regex;
			if(options === "visa"){
				regex = cc[0];
			}
			else if(options === "mastercard"){
				regex = cc[1];
			}
			else if(options === "discover"){
				regex = cc[2];
			}
			else if(options === "amex"){
				regex = cc[3];
			}
			else if(options === "diners"){
				regex = cc[4];
			}

			return !regex.test(value);
		}

		// Remove all dashes for the checksum
		value = value.split("-").join("");

		// Add even digits in even length strings or odd digits in odd length strings.
		var checksum = 0;
		for (var i=(2-(value.length % 2)); i<=value.length; i+=2) {
			checksum += parseInt(value.charAt(i-1),10);
		}
		// Analyze odd digits in even length strings or even digits in odd length strings.
		for (var i=(value.length % 2) + 1; i<value.length; i+=2) {
			var digit = parseInt(value.charAt(i-1),10) * 2;
			if (digit < 10) {
				checksum += digit;
			}
			else {
				checksum += (digit-9);
			}
		}
		return ((checksum % 10) !== 0);
	};

}(jQuery));<commit_msg>Added callback functions and cleaned it a bit<commit_after>(function($) {
    var plugin = $(this);

	$.fn.validate = function(options) {

		// Default config
		var defaults = {
			error: '#EB4847',
			success: '#63C76A',
			submit: '',
			onSuccess: function(){},
			onError: function(){}
		};

		// Update all configs
		var settings = $.extend(true, {}, defaults, options);

		var is_valid = true;

		// Check all fields
		this.each(function() {
			var error = plugin.check($(this), settings);
			if(error === true){
				is_valid = false;
			}

			$(this).keyup(function(e) {
				e.preventDefault();
				// Ignore enter
				if (e.which === 13) {
					return;
				}
				var error = plugin.check($(this), settings);
				if(error === true){
					is_valid = false;
				}
			});
		});

		if(is_valid === true){
			settings.onSuccess.call(this);
		}
		else{
			settings.onError.call(this);
		}

		if(is_valid === true && settings.submit.length > 0){
			$(settings.submit).submit();
		}

		return this;
	};

	/**
	 * Run the rules in the field.
	 *
 	 * @param object value
 	 * @param object value
	 */
	plugin.check = function(element, settings) {
		// Explode rules
		var rules = $(element).data('rules').split('|');

		var error = false;
		for (var i = 0; i < rules.length; i++) {
			var rule = rules[i];
			var regex = /(.*?)\[(.*?)\]/;

			var func = rule;
			var options = '';

			// Check if arguments are enclosed in brackets
			var groups = regex.exec(rule);
			if(groups !== null){
				func = groups[1];
				options = groups[2];
			}

			// If the function exists call it.
			if (typeof(plugin[func]) !== "undefined") {
				error = plugin[func]($(element).val(), options);
			}
		}

		// Apply the correct css to the field.
		if(error){
			$(element).css('border-color', settings.error);
		}
		else{
			$(element).css('border-color', settings.success);
		}
		return error;
	};

	/**
	 * Checks if the field is filled.
	 *
 	 * @param string value
	 */
	plugin.required = function(value) {
		if(value.length === 0){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the field is x characters long.
	 *
 	 * @param string value
 	 * @param integer value
	 */
	plugin.exactlength = function(value, options) {
		if(value.length === options){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the field is at least x characters long.
	 *
 	 * @param string value
 	 * @param integer value
	 */
	plugin.minlength = function(value, options) {
		if(value.length < options){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the field is less than x characters long.
 	 * @param string value
 	 * @param integer value
	 */
	plugin.maxlength = function(value, options) {
		if(value.length > options){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the field only contains letters.
	 *
 	 * @param string value
	 */
	plugin.alpha = function(value) {
		var regex = /^([a-z])+$/i;
		return !regex.test(value);
	};

	/**
	 * Checks if the field only contains alpha-numeric characters.
	 *
 	 * @param string value
	 */
	plugin.alphanumeric = function(value) {
		var regex = /^([a-z0-9])+$/i;
		return !regex.test(value);
	};

	/**
	 * Checks if the field is numeric.
	 *
 	 * @param string value
	 */
	plugin.numeric = function(value) {
		var regex = /^[\-+]?[0-9]*\.?[0-9]+$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the field is integer.
	 *
 	 * @param string value
	 */
	plugin.integer = function(value) {
		var regex = /^[\-+]?[0-9]+$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the field is natrual.
	 *
 	 * @param string value
	 */
	plugin.natrual = function(value) {
		var regex = /^[0-9]+$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the field is decimal.
	 *
 	 * @param string value
	 */
	plugin.decimal = function(value) {
		var regex = /^[\-+]?[0-9]+\.[0-9]+$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the numeric value is between two values.
	 *
 	 * @param string value
 	 * @param integer value
	 */
	plugin.between = function(value, options) {
		var minmax = options.split('-');
		if(value < minmax[0]){
			return true;
		}
		if(value > minmax[1]){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the value matches with the field.
	 *
 	 * @param string value
 	 * @param string options
	 */
	plugin.matches = function(value, options) {
		var other = $(options).val();
		if(value !== other){
			return true;
		}
		return false;
	};

	/**
	 * Checks if the value is a valid email.
	 * http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
	 *
 	 * @param string value
	 */
	plugin.email = function(value) {
		var regex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the value is a valid url.
	 * http://stackoverflow.com/questions/1303872/trying-to-validate-url-using-javascript
	 *
 	 * @param string value
	 */
	plugin.url = function(value) {
		var regex = /^(http|https):\/\/(([a-zA-Z0-9$\-_.+!*'(),;:&=]|%[0-9a-fA-F]{2})+@)?(((25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9]|[1-9][0-9]|[0-9])(\.(25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9]|[1-9][0-9]|[0-9])){3})|localhost|([a-zA-Z0-9\-\u00C0-\u017F]+\.)+([a-zA-Z]{2,}))(:[0-9]+)?(\/(([a-zA-Z0-9$\-_.+!*'(),;:@&=]|%[0-9a-fA-F]{2})*(\/([a-zA-Z0-9$\-_.+!*'(),;:@&=]|%[0-9a-fA-F]{2})*)*)?(\?([a-zA-Z0-9$\-_.+!*'(),;:@&=\/?]|%[0-9a-fA-F]{2})*)?(\#([a-zA-Z0-9$\-_.+!*'(),;:@&=\/?]|%[0-9a-fA-F]{2})*)?)?$/;;
		return !regex.test(value);
	};

	/**
	 * Checks if the value is a valid ip.
	 * http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address
	 *
 	 * @param string value
	 */
	plugin.ip = function(value) {
		var regex = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the value is a valid time.
	 *
 	 * @param string value
	 */
	plugin.time = function(value) {
		var regex = /^([0]\d|[1][0-2]):([0-5]\d)\s?(?:AM|PM)$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the value is a valid date.
	 *
 	 * @param string value
	 */
	plugin.date = function(value) {
		var regex = /^\d{2}[./-]\d{2}[./-]\d{4}$/;
		return !regex.test(value);
	};

	/**
	 * Checks if the value is a valid credit card number.
	 *
 	 * @param string value
 	 * @param string options
	 */
	plugin.cc = function(value, options) {
		var cc = [
			/^4\d{3}-?\d{4}-?\d{4}-?\d{4}$/, // Visa
			/^5[1-5]\d{2}-?\d{4}-?\d{4}-?\d{4}$/, // Mastercard
			/^6011-?\d{4}-?\d{4}-?\d{4}$/, // Discover
			/^3[4,7]\d{13}$/, // American Express
			/^3[0,6,8]\d{12}$/ // Diners
		];

		// If options is not set check with all regex
		if(options.length === 0){
			var valid = false;
			for(var i = 0; i < cc.length; i++){
				if(cc[i].test(value)){
					valid = true;
				}
			}
			if(valid === false){
				return true;
			}
		}
		else{
			var regex;
			if(options === "visa"){
				regex = cc[0];
			}
			else if(options === "mastercard"){
				regex = cc[1];
			}
			else if(options === "discover"){
				regex = cc[2];
			}
			else if(options === "amex"){
				regex = cc[3];
			}
			else if(options === "diners"){
				regex = cc[4];
			}

			return !regex.test(value);
		}

		// Remove all dashes for the checksum
		value = value.split("-").join("");

		// Add even digits in even length strings or odd digits in odd length strings.
		var checksum = 0;
		for (var i=(2-(value.length % 2)); i<=value.length; i+=2) {
			checksum += parseInt(value.charAt(i-1),10);
		}
		// Analyze odd digits in even length strings or even digits in odd length strings.
		for (var i=(value.length % 2) + 1; i<value.length; i+=2) {
			var digit = parseInt(value.charAt(i-1),10) * 2;
			if (digit < 10) {
				checksum += digit;
			}
			else {
				checksum += (digit-9);
			}
		}
		return ((checksum % 10) !== 0);
	};

}(jQuery));<|endoftext|>


<commit_before>/**
 * calculateNodeHeight(uiTextNode, useCache = false)
 */

const HIDDEN_TEXTAREA_STYLE = `
  height:0;
  visibility:hidden;
  overflow:hidden;
  position:absolute;
  z-index:-1000;
  top:0;
  right:0
`;

const SIZING_STYLE = [
  'line-height',
  'padding-top',
  'padding-bottom',
  'font-family',
  'font-weight',
  'font-size',
  'width',
  'padding-left',
  'padding-right',
  'border-width',
  'box-sizing'
];

let computedStyleCache = {};
let hiddenTextarea;

export default function calculateNodeHeight(uiTextNode, useCache = false, minRows = null, maxRows = null) {
  if (!hiddenTextarea) {
    hiddenTextarea = document.createElement('textarea');
    document.body.appendChild(hiddenTextarea);
  }

  // Copy all CSS properties that have an impact on the height of the content in
  // the textbox
  let {sizingStyle, sumVerticalPaddings} = calculateNodeStyling(uiTextNode, useCache);

  // Need to have the overflow attribute to hide the scrollbar otherwise
  // text-lines will not calculated properly as the shadow will technically be
  // narrower for content
  hiddenTextarea.setAttribute('style', sizingStyle + ';' + HIDDEN_TEXTAREA_STYLE);

  hiddenTextarea.value = uiTextNode.value;
  let height = hiddenTextarea.scrollHeight - sumVerticalPaddings;

  if (minRows !== null || maxRows !== null) {
    // measure height of a textarea with a single row
    hiddenTextarea.value = 'x';
    let singleRowHeight = hiddenTextarea.scrollHeight - sumVerticalPaddings;
    if (minRows !== null) {
      let minHeight = singleRowHeight * minRows;
      return {
        height: Math.max(minHeight, height),
        minHeight,
        maxHeight: Infinity
      };
    }
    if (maxRows !== null) {
      let maxHeight = singleRowHeight * maxRows;
      return {
        height: Math.min(maxHeight, height),
        minHeight: -Infinity,
        maxHeight
      };
    }
  }
  return {
    height,
    minHeight: -Infinity,
    maxHeight: Infinity
  };
}

function calculateNodeStyling(node, useCache = false) {
  let nodeRef = (
    node.getAttribute('id') ||
    node.getAttribute('data-reactid') ||
    node.getAttribute('name')
  );

  if (useCache && computedStyleCache[nodeRef]) {
    return computedStyleCache[nodeRef];
  }

  let compStyle = window.getComputedStyle(node);

  let sumPaddings = 0;

  // If the textarea is set to border-box, it's not necessary to
  // subtract the padding.
  if (
    compStyle.getPropertyValue('box-sizing') !== 'border-box' &&
    compStyle.getPropertyValue('-moz-box-sizing') !== 'border-box' &&
    compStyle.getPropertyValue('-webkit-box-sizing') !== 'border-box'
  ) {
    sumPaddings = (
      parseFloat(compStyle.getPropertyValue('padding-bottom')) +
      parseFloat(compStyle.getPropertyValue('padding-top'))
    );
  }

  let nodeInfo = {
    sizingStyle: SIZING_STYLE
      .map(name => `${name}:${compStyle.getPropertyValue(name)}`)
      .join(';'),
    sumVerticalPaddings: sumPaddings
  };

  if (useCache && nodeRef) {
    computedStyleCache[nodeRef] = nodeInfo;
  }
  return nodeInfo;
}
<commit_msg>Fix lint error<commit_after>/**
 * calculateNodeHeight(uiTextNode, useCache = false)
 */

const HIDDEN_TEXTAREA_STYLE = `
  height:0;
  visibility:hidden;
  overflow:hidden;
  position:absolute;
  z-index:-1000;
  top:0;
  right:0
`;

const SIZING_STYLE = [
  'line-height',
  'padding-top',
  'padding-bottom',
  'font-family',
  'font-weight',
  'font-size',
  'width',
  'padding-left',
  'padding-right',
  'border-width',
  'box-sizing'
];

let computedStyleCache = {};
let hiddenTextarea;

export default function calculateNodeHeight(uiTextNode,
    useCache = false,
    minRows = null, maxRows = null) {
  if (!hiddenTextarea) {
    hiddenTextarea = document.createElement('textarea');
    document.body.appendChild(hiddenTextarea);
  }

  // Copy all CSS properties that have an impact on the height of the content in
  // the textbox
  let {sizingStyle, sumVerticalPaddings} = calculateNodeStyling(uiTextNode, useCache);

  // Need to have the overflow attribute to hide the scrollbar otherwise
  // text-lines will not calculated properly as the shadow will technically be
  // narrower for content
  hiddenTextarea.setAttribute('style', sizingStyle + ';' + HIDDEN_TEXTAREA_STYLE);

  hiddenTextarea.value = uiTextNode.value;
  let height = hiddenTextarea.scrollHeight - sumVerticalPaddings;

  if (minRows !== null || maxRows !== null) {
    // measure height of a textarea with a single row
    hiddenTextarea.value = 'x';
    let singleRowHeight = hiddenTextarea.scrollHeight - sumVerticalPaddings;
    if (minRows !== null) {
      let minHeight = singleRowHeight * minRows;
      return {
        height: Math.max(minHeight, height),
        minHeight,
        maxHeight: Infinity
      };
    }
    if (maxRows !== null) {
      let maxHeight = singleRowHeight * maxRows;
      return {
        height: Math.min(maxHeight, height),
        minHeight: -Infinity,
        maxHeight
      };
    }
  }
  return {
    height,
    minHeight: -Infinity,
    maxHeight: Infinity
  };
}

function calculateNodeStyling(node, useCache = false) {
  let nodeRef = (
    node.getAttribute('id') ||
    node.getAttribute('data-reactid') ||
    node.getAttribute('name')
  );

  if (useCache && computedStyleCache[nodeRef]) {
    return computedStyleCache[nodeRef];
  }

  let compStyle = window.getComputedStyle(node);

  let sumPaddings = 0;

  // If the textarea is set to border-box, it's not necessary to
  // subtract the padding.
  if (
    compStyle.getPropertyValue('box-sizing') !== 'border-box' &&
    compStyle.getPropertyValue('-moz-box-sizing') !== 'border-box' &&
    compStyle.getPropertyValue('-webkit-box-sizing') !== 'border-box'
  ) {
    sumPaddings = (
      parseFloat(compStyle.getPropertyValue('padding-bottom')) +
      parseFloat(compStyle.getPropertyValue('padding-top'))
    );
  }

  let nodeInfo = {
    sizingStyle: SIZING_STYLE
      .map(name => `${name}:${compStyle.getPropertyValue(name)}`)
      .join(';'),
    sumVerticalPaddings: sumPaddings
  };

  if (useCache && nodeRef) {
    computedStyleCache[nodeRef] = nodeInfo;
  }
  return nodeInfo;
}
<|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor';

import { Subscriptions } from '../../app/models';
import { hasPermission } from '../../app/authorization';
import { settings } from '../../app/settings';

function findUsers({ rid, status, skip, limit }) {
	return Subscriptions.model.rawCollection().aggregate([
		{ $match: { rid } },
		{
			$lookup:
				{
					from: 'users',
					localField: 'u._id',
					foreignField: '_id',
					as: 'u',
				},
		},
		{
			$project: {
				'u._id': 1,
				'u.name': 1,
				'u.username': 1,
				'u.status': 1,
			},
		},
		...status ? [{ $match: { 'u.status': status } }] : [],
		{
			$sort: {
				[settings.get('UI_Use_Real_Name') ? 'u.name' : 'u.username']: 1,
			},
		},
		...skip > 0 ? [{ $skip: skip }] : [],
		...limit > 0 ? [{ $limit: limit }] : [],
		{
			$project: {
				_id: { $arrayElemAt: ['$u._id', 0] },
				name: { $arrayElemAt: ['$u.name', 0] },
				username: { $arrayElemAt: ['$u.username', 0] },
			},
		},
	]).toArray();
}

Meteor.methods({
	async getUsersOfRoom(rid, showAll, { limit, skip } = {}) {
		const userId = Meteor.userId();
		if (!userId) {
			throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getUsersOfRoom' });
		}

		const room = Meteor.call('canAccessRoom', rid, userId);
		if (!room) {
			throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'getUsersOfRoom' });
		}

		if (room.broadcast && !hasPermission(userId, 'view-broadcast-member-list', rid)) {
			throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getUsersOfRoom' });
		}

		const total = Subscriptions.findByRoomIdWhenUsernameExists(rid).count();
		const users = await findUsers({ rid, status: { $ne: 'offline' }, limit, skip });

		if (showAll && users.length < limit) {
			const offlineUsers = await findUsers({
				rid,
				status: { $eq: 'offline' },
				limit: limit - users.length,
				skip,
			});

			return {
				total,
				records: users.concat(offlineUsers),
			};
		}

		return {
			total,
			records: users,
		};
	},
});
<commit_msg>[FIX] Method `getUsersOfRoom` not returning offline users if limit is not defined (#14753)<commit_after>import { Meteor } from 'meteor/meteor';

import { Subscriptions } from '../../app/models';
import { hasPermission } from '../../app/authorization';
import { settings } from '../../app/settings';

function findUsers({ rid, status, skip, limit }) {
	return Subscriptions.model.rawCollection().aggregate([
		{ $match: { rid } },
		{
			$lookup:
				{
					from: 'users',
					localField: 'u._id',
					foreignField: '_id',
					as: 'u',
				},
		},
		{
			$project: {
				'u._id': 1,
				'u.name': 1,
				'u.username': 1,
				'u.status': 1,
			},
		},
		...status ? [{ $match: { 'u.status': status } }] : [],
		{
			$sort: {
				[settings.get('UI_Use_Real_Name') ? 'u.name' : 'u.username']: 1,
			},
		},
		...skip > 0 ? [{ $skip: skip }] : [],
		...limit > 0 ? [{ $limit: limit }] : [],
		{
			$project: {
				_id: { $arrayElemAt: ['$u._id', 0] },
				name: { $arrayElemAt: ['$u.name', 0] },
				username: { $arrayElemAt: ['$u.username', 0] },
			},
		},
	]).toArray();
}

Meteor.methods({
	async getUsersOfRoom(rid, showAll, { limit, skip } = {}) {
		const userId = Meteor.userId();
		if (!userId) {
			throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getUsersOfRoom' });
		}

		const room = Meteor.call('canAccessRoom', rid, userId);
		if (!room) {
			throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'getUsersOfRoom' });
		}

		if (room.broadcast && !hasPermission(userId, 'view-broadcast-member-list', rid)) {
			throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getUsersOfRoom' });
		}

		const total = Subscriptions.findByRoomIdWhenUsernameExists(rid).count();
		const users = await findUsers({ rid, status: { $ne: 'offline' }, limit, skip });
		if (showAll && (!limit || users.length < limit)) {
			const offlineUsers = await findUsers({
				rid,
				status: { $eq: 'offline' },
				limit: limit ? limit - users.length : 0,
				skip: skip || 0,
			});

			return {
				total,
				records: users.concat(offlineUsers),
			};
		}

		return {
			total,
			records: users,
		};
	},
});
<|endoftext|>


<commit_before>import template from './item-review.html';
import './item-review.scss';

function ItemReviewController(dimSettingsService, dimDestinyTrackerService, $scope) {
  'ngInject';

  const vm = this;
  vm.canReview = dimSettingsService.allowIdPostToDtr;
  vm.canCreateReview = (vm.canReview && vm.item.instanceId);
  vm.submitted = false;
  vm.hasUserReview = vm.item.userRating;
  vm.expandReview = vm.hasUserReview;

  vm.procon = false; // TODO: turn this back on..
  vm.aggregate = {
    pros: ['fast', 'lol'],
    cons: ['ok']
  };
//  vm.item.writtenReviews.forEach((review) => {
//    aggregate.pros.push(review.pros);
//    aggregate.cons.push(review.cons);
//  });

  vm.toggleEdit = function() {
    vm.expandReview = !vm.expandReview;
  };

  vm.submitReview = function() {
    dimDestinyTrackerService.submitReview(vm.item);
    vm.expandReview = false;
    vm.submitted = true;
  };

  vm.setRating = function(rating) {
    if (rating) {
      vm.item.userRating = rating;
    }
    vm.expandReview = true;
  };

  vm.reviewBlur = function() {
    const item = vm.item;
    const userReview = vm.toUserReview(item);

    dimDestinyTrackerService.updateCachedUserRankings(item,
                                                      userReview);
  };

  vm.toUserReview = function(item) {
    const newRating = item.userRating;
    const review = item.userReview;
    const pros = item.userReviewPros;
    const cons = item.userReviewCons;

    const userReview = {
      rating: newRating,
      review: review,
      pros: pros,
      cons: cons
    };

    return userReview;
  };

  vm.featureFlags = {
    qualityEnabled: $featureFlags.qualityEnabled,
    reviewsEnabled: $featureFlags.reviewsEnabled
  };

  vm.settings = dimSettingsService;

  $scope.$watchCollection('vm.settings', () => {
    dimSettingsService.save();
  });

  vm.valueChanged = function() {
    vm.canReview = dimSettingsService.allowIdPostToDtr;

    if (vm.canReview) {
      dimDestinyTrackerService.getItemReviews(vm.item);
    }
  };
}

export const ItemReviewComponent = {
  bindings: {
    item: '<'
  },
  controller: ItemReviewController,
  template: template
};
<commit_msg>instanceId doesn't seem to be available here now?<commit_after>import template from './item-review.html';
import './item-review.scss';

function ItemReviewController(dimSettingsService, dimDestinyTrackerService, $scope) {
  'ngInject';

  const vm = this;
  vm.canReview = dimSettingsService.allowIdPostToDtr;
  vm.canCreateReview = (vm.canReview && vm.item.owner);
  vm.submitted = false;
  vm.hasUserReview = vm.item.userRating;
  vm.expandReview = vm.hasUserReview;

  vm.procon = false; // TODO: turn this back on..
  vm.aggregate = {
    pros: ['fast', 'lol'],
    cons: ['ok']
  };
//  vm.item.writtenReviews.forEach((review) => {
//    aggregate.pros.push(review.pros);
//    aggregate.cons.push(review.cons);
//  });

  vm.toggleEdit = function() {
    vm.expandReview = !vm.expandReview;
  };

  vm.submitReview = function() {
    dimDestinyTrackerService.submitReview(vm.item);
    vm.expandReview = false;
    vm.submitted = true;
  };

  vm.setRating = function(rating) {
    if (rating) {
      vm.item.userRating = rating;
    }
    vm.expandReview = true;
  };

  vm.reviewBlur = function() {
    const item = vm.item;
    const userReview = vm.toUserReview(item);

    dimDestinyTrackerService.updateCachedUserRankings(item,
                                                      userReview);
  };

  vm.toUserReview = function(item) {
    const newRating = item.userRating;
    const review = item.userReview;
    const pros = item.userReviewPros;
    const cons = item.userReviewCons;

    const userReview = {
      rating: newRating,
      review: review,
      pros: pros,
      cons: cons
    };

    return userReview;
  };

  vm.featureFlags = {
    qualityEnabled: $featureFlags.qualityEnabled,
    reviewsEnabled: $featureFlags.reviewsEnabled
  };

  vm.settings = dimSettingsService;

  $scope.$watchCollection('vm.settings', () => {
    dimSettingsService.save();
  });

  vm.valueChanged = function() {
    vm.canReview = dimSettingsService.allowIdPostToDtr;

    if (vm.canReview) {
      dimDestinyTrackerService.getItemReviews(vm.item);
    }
  };
}

export const ItemReviewComponent = {
  bindings: {
    item: '<'
  },
  controller: ItemReviewController,
  template: template
};
<|endoftext|>


<commit_before><commit_msg>Corect coloration problems<commit_after><|endoftext|>


<commit_before>// Created by snov on 18.02.2017.
//
// Component that can talk with Firebase
//
//=========================================================================

/* eslint-disable no-unused-vars */
/* eslint-disable no-undef */

import React, { Component } from 'react';
import { Firebase as firebase, auth, database } from '../../firebase';
import Logger from '../Logger';
import { addMessage } from '../../redux/modules/logger';
import { connect } from 'react-redux';

const provider = new firebase.auth.GoogleAuthProvider();

function authenticate() {
  auth.signInWithRedirect(provider);

  auth.getRedirectResult().then(function(result) {
    if (result.credential) {
      const token = result.credential.accessToken;
      console.log(token);
    }
  }).catch(function(error) {
    const { errorCode, errorMessage, email, credential } = error;
    alert(errorCode, errorMessage, email, credential);
  });
}

const mapStateToProps = (state, ownProps) => {
  return {
    user: state.user,
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return { dispatch }
}

const keyProp = 'database-key';
const valueProp = 'database-value';

const messagesRef = database.ref('messages');

function writeData(ref, data) {
  const newRef = ref.push();
  newRef.set(data);
}

const sendMessage = (user, message) => {
  writeData(messagesRef, { user, message});
}

@connect(mapStateToProps, mapDispatchToProps)
export default class FirebaseApp extends Component {

  state = {
    [keyProp]: '',
    [valueProp]: '',
    user: null
  }

  static propTypes = {
    dispatch: React.PropTypes.func
  }

  componentDidMount() {

    auth.onAuthStateChanged((user) => {
      if (user) {
        console.log(`U are ${user.displayName}!`);
      }
      this.handleUserChange(user);
    });

    messagesRef.on('child_added', (snapshot) => {
      const val = snapshot.val();
      this.props.dispatch(addMessage(`${val.user}: ${val.message}`));
    });
  }

  componentWillUnmount() {

  }

  handleInputChange = (e) =>  {
    e.preventDefault();
    this.setState({
      [e.target.name]: e.target.value
    });
  }

  handleUserChange = (user) => {
    this.setState({
      user
    });

    this.props.dispatch(addMessage(`User changed.`));
  }

  handleLogout = () => {
    auth.signOut().then(() => {
    }, (error) => {
      console.log(error);
    })
  }

  render() {
    let actionElement = null;
    if (this.state.user) {
      actionElement =
        (

Hello, {this.state.user.displayName}!

          <button onClick={this.handleLogout}>Logout</button>
          <form onSubmit={e => {
            e.preventDefault()
            this.setState({
              [valueProp]: ''
            });
            sendMessage(this.state.user.displayName, this.state[valueProp]);
          }}>
            <input onChange={this.handleInputChange} value={this.state[valueProp]} name={valueProp}/>
            <button>Send</button>
          </form>

);
    } else {
      actionElement = (

Login to get access to database.

          <button onClick={authenticate}>Enter with Google Account</button>

      )
    }

    return (

        {actionElement}
        <Logger/>

    );
  }
}<commit_msg>test fix<commit_after>// Created by snov on 18.02.2017.
//
// Component that can talk with Firebase
//
//=========================================================================

/* eslint-disable no-unused-vars */
/* eslint-disable no-undef */

import React, { Component } from 'react';
import { Firebase as firebase, auth, database } from '../../firebase';
import Logger from '../Logger';
import { addMessage } from '../../redux/modules/logger';
import { connect } from 'react-redux';

const provider = new firebase.auth.GoogleAuthProvider();

function authenticate() {
  auth.signInWithRedirect(provider);

  auth.getRedirectResult().then(function(result) {
    if (result.credential) {
      const token = result.credential.accessToken;
      console.log(token);
    }
  }).catch(function(error) {
    const { errorCode, errorMessage, email, credential } = error;
    alert(errorCode, errorMessage, email, credential);
  });
}

const mapStateToProps = (state, ownProps) => {
  return {
    user: state.user,
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return { dispatch }
}

const keyProp = 'database-key';
const valueProp = 'database-value';

const messagesRef = database.ref('messages');

function writeData(ref, data) {
  const newRef = ref.push();
  newRef.set(data);
}

const sendMessage = (user, message) => {
  writeData(messagesRef, { user, message});
}

@connect(mapStateToProps, mapDispatchToProps)
export default class FirebaseApp extends Component {

  state = {
    [keyProp]: '',
    [valueProp]: '',
    user: null
  }

  static propTypes = {
    dispatch: React.PropTypes.func
  }

  componentDidMount() {

    auth.onAuthStateChanged((user) => {
      if (user) {
        console.log(`U are ${user.displayName}!`);
      }
      this.handleUserChange(user);
    });

    messagesRef.on('child_added', (snapshot) => {
      const val = snapshot.val();
      this.props.dispatch(addMessage(`${val.user}: ${val.message}`));
    });
  }

  componentWillUnmount() {

  }

  handleInputChange = (e) =>  {
    e.preventDefault();
    this.setState({
      [e.target.name]: e.target.value
    });
  }

  handleUserChange = (user) => {
    this.setState({
      user
    });

    this.props.dispatch(addMessage(`User changed.`));
  }

  handleLogout = () => {
    auth.signOut().then(() => {
    }, (error) => {
      console.log(error);
    })
  }

  render() {
    let actionElement = null;
    if (this.state.user) {
      actionElement =
        (

Hello, {this.state.user.displayName}!

          <button onClick={this.handleLogout}>Logout</button>
          <form onSubmit={e => {
            e.preventDefault()
            this.setState({
              [valueProp]: ''
            });
            sendMessage(this.state.user.displayName, this.state[valueProp]);
          }}>
            <input onChange={this.handleInputChange} value={this.state[valueProp]} name={valueProp}/>
            <button>Send</button>
          </form>

);
    } else {
      actionElement = (

Login to get access to the database!

          <button onClick={authenticate}>Enter with Google Account</button>

      )
    }

    return (

        {actionElement}
        <Logger/>

    );
  }
}<|endoftext|>


<commit_before>// Copyright 2019, University of Colorado Boulder

/**
 * The button on a combo box box.  Displays the current selection on the button.
 * Typically instantiated by ComboBox, not by client code.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  const HStrut = require( 'SCENERY/nodes/HStrut' );
  const Node = require( 'SCENERY/nodes/Node' );
  const Path = require( 'SCENERY/nodes/Path' );
  const RectangularButtonView = require( 'SUN/buttons/RectangularButtonView' );
  const RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  const Shape = require( 'KITE/Shape' );
  const sun = require( 'SUN/sun' );
  const Tandem = require( 'TANDEM/Tandem' );
  const VSeparator = require( 'SUN/VSeparator' );

  // constants
  const ALIGN_VALUES = [ 'left', 'center', 'right' ];
  const ARROW_DIRECTION_VALUES = [ 'up', 'down' ];

  class ComboBoxButton extends RectangularPushButton {

    /**
     * @param {Property} property
     * @param {ComboBoxItem[]} items
     * @param {Object} [options]
     */
    constructor( property, items, options ) {

      options = _.extend( {

        align: 'left', // see ALIGN_VALUES
        arrowDirection: 'down', // see ARROW_DIRECTION_VALUES
        arrowFill: 'black',

        // RectangularPushButton options
        cursor: 'pointer',
        baseColor: 'white',
        buttonAppearanceStrategy: RectangularButtonView.FlatAppearanceStrategy,
        xMargin: 12,
        yMargin: 8,
        stroke: 'black',
        lineWidth: 1,

        // phet-io
        tandem: Tandem.optional,

        // a11y
        labelTagName: 'span',
        containerTagName: 'div',
        labelContent: null // {string|null}

      }, options );

      assert && assert( _.includes( ALIGN_VALUES, options.align ),
        'invalid align: ' + options.align );
      assert && assert( _.includes( ARROW_DIRECTION_VALUES, options.arrowDirection ),
        'invalid arrowDirection: ' + options.arrowDirection );

      // To improve readability
      const itemXMargin = options.xMargin;

      // Compute max item size
      const maxItemWidth = _.maxBy( items, item => item.node.width ).node.width;
      const maxItemHeight = _.maxBy( items, item => item.node.height ).node.height;

      // We want the arrow area to be square, see https://github.com/phetsims/sun/issues/453
      const arrowAreaSize = ( maxItemHeight + 2 * options.yMargin );

      // The arrow is sized to fit in the arrow area, empirically determined to be visually pleasing.
      const arrowHeight = 0.35 * arrowAreaSize; // height of equilateral triangle
      const arrowWidth = 2 * arrowHeight * Math.sqrt( 3 ) / 3; // side of equilateral triangle

      // Invisible horizontal struts that span the item area and arrow area. Makes layout more straightforward.
      const itemAreaStrut = new HStrut( maxItemWidth + 2 * itemXMargin );
      const arrowAreaStrut = new HStrut( arrowAreaSize, {
        left: itemAreaStrut.right
      } );

      // arrow that points up or down, to indicate which way the list pops up
      let arrowShape = null;
      if ( options.arrowDirection === 'up' ) {
        arrowShape = new Shape()
          .moveTo( 0, arrowHeight )
          .lineTo( arrowWidth / 2, 0 )
          .lineTo( arrowWidth, arrowHeight )
          .close();
      }
      else {
        arrowShape = new Shape()
          .moveTo( 0, 0 )
          .lineTo( arrowWidth, 0 )
          .lineTo( arrowWidth / 2, arrowHeight )
          .close();
      }
      const arrow = new Path( arrowShape, {
        fill: options.arrowFill,
        center: arrowAreaStrut.center
      } );

      // Wrapper for the selected item's Node.
      // Do not transform ComboBoxItem.node because it is shared with ComboBoxListItemNode.
      const itemNodeWrapper = new Node();

      // Vertical separator between the item and arrow that is the full height of the button.
      const vSeparator = new VSeparator( maxItemHeight + 2 * options.yMargin, {
        stroke: 'black',
        lineWidth: options.lineWidth,
        centerX: itemAreaStrut.right,
        centerY: itemAreaStrut.centerY
      } );

      // Margins are different in the item and button areas. And we want the vertical separator to extend
      // beyond the margin.  We've handled those margins above, so the actual margins propagated to the button
      // need to be zero.
      options.xMargin = 0;
      options.yMargin = 0;

      assert && assert( !options.content, 'ComboBoxButton sets content' );
      options.content = new Node( {
        children: [ itemAreaStrut, arrowAreaStrut, itemNodeWrapper, vSeparator, arrow ]
      } );

      super( options );

      const updateItemLayout = () => {
        if ( options.align === 'left' ) {
          itemNodeWrapper.left = itemAreaStrut.left + itemXMargin;
        }
        else if ( options.align === 'right' ) {
          itemNodeWrapper.right = itemAreaStrut.right - itemXMargin;
        }
        else {
          itemNodeWrapper.centerX = itemAreaStrut.centerX;
        }
        itemNodeWrapper.centerY = itemAreaStrut.centerY;
      };

      // When Property's value changes, show the corresponding item's Node on the button.
      let item = null;
      const propertyObserver = ( value ) => {

        // Remove bounds listener from previous item.node
        if ( item && item.node.hasListener( 'bounds', updateItemLayout ) ) {
          item.node.off( 'bounds', updateItemLayout );
        }

        // remove the node for the previous item
        itemNodeWrapper.removeAllChildren();

        // find the ComboBoxItem whose value matches the property's value
        item = _.find( items, item => item.value === value );
        assert && assert( item, 'no item found for value: ' + value );

        // add the associated node
        itemNodeWrapper.addChild( item.node );

        // Update layout if bounds change, see See https://github.com/phetsims/scenery-phet/issues/482
        item.node.on( 'bounds', updateItemLayout );

        updateItemLayout();

        // a11y
        this.innerContent = item.a11yLabel;
      };
      property.link( propertyObserver );

      // Add aria-labelledby attribute to the button.
      // The button is aria-labelledby its own label sibling, and then (second) its primary sibling in the PDOM.
      // Order matters!
      assert && assert( !options.ariaLabelledbyAssociations, 'ComboBoxButton sets ariaLabelledbyAssociations' );
      this.ariaLabelledbyAssociations = [
        {
          otherNode: this,
          otherElementName: AccessiblePeer.LABEL_SIBLING,
          thisElementName: AccessiblePeer.PRIMARY_SIBLING
        },
        {
          otherNode: this,
          otherElementName: AccessiblePeer.PRIMARY_SIBLING,
          thisElementName: AccessiblePeer.PRIMARY_SIBLING
        }
      ];

      // signify to AT that this button opens a menu
      this.setAccessibleAttribute( 'aria-haspopup', 'listbox' );

      // @private
      this.disposeComboBoxButton = () => {
        property.unlink( propertyObserver );
      };

      // @private needed by methods
      this.arrow = arrow;
      this.vSeparator = vSeparator;
    }

    /**
     * Sets the button to look like a value display instead of a combo box button.
     * See https://github.com/phetsims/sun/issues/451
     * @param {boolean} displayOnly
     * @public
     */
    setDisplayOnly( displayOnly ) {
      this.arrow.visible = !displayOnly;
      this.vSeparator.visible = !displayOnly;
    }

    /**
     * @public
     * @override
     */
    dispose() {
      this.disposeComboBoxButton();
      super.dispose();
    }
  }

  return sun.register( 'ComboBoxButton', ComboBoxButton );
} );<commit_msg>doc, https://github.com/phetsims/scenery-phet/issues/482<commit_after>// Copyright 2019, University of Colorado Boulder

/**
 * The button on a combo box box.  Displays the current selection on the button.
 * Typically instantiated by ComboBox, not by client code.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  const HStrut = require( 'SCENERY/nodes/HStrut' );
  const Node = require( 'SCENERY/nodes/Node' );
  const Path = require( 'SCENERY/nodes/Path' );
  const RectangularButtonView = require( 'SUN/buttons/RectangularButtonView' );
  const RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  const Shape = require( 'KITE/Shape' );
  const sun = require( 'SUN/sun' );
  const Tandem = require( 'TANDEM/Tandem' );
  const VSeparator = require( 'SUN/VSeparator' );

  // constants
  const ALIGN_VALUES = [ 'left', 'center', 'right' ];
  const ARROW_DIRECTION_VALUES = [ 'up', 'down' ];

  class ComboBoxButton extends RectangularPushButton {

    /**
     * @param {Property} property
     * @param {ComboBoxItem[]} items
     * @param {Object} [options]
     */
    constructor( property, items, options ) {

      options = _.extend( {

        align: 'left', // see ALIGN_VALUES
        arrowDirection: 'down', // see ARROW_DIRECTION_VALUES
        arrowFill: 'black',

        // RectangularPushButton options
        cursor: 'pointer',
        baseColor: 'white',
        buttonAppearanceStrategy: RectangularButtonView.FlatAppearanceStrategy,
        xMargin: 12,
        yMargin: 8,
        stroke: 'black',
        lineWidth: 1,

        // phet-io
        tandem: Tandem.optional,

        // a11y
        labelTagName: 'span',
        containerTagName: 'div',
        labelContent: null // {string|null}

      }, options );

      assert && assert( _.includes( ALIGN_VALUES, options.align ),
        'invalid align: ' + options.align );
      assert && assert( _.includes( ARROW_DIRECTION_VALUES, options.arrowDirection ),
        'invalid arrowDirection: ' + options.arrowDirection );

      // To improve readability
      const itemXMargin = options.xMargin;

      // Compute max item size
      const maxItemWidth = _.maxBy( items, item => item.node.width ).node.width;
      const maxItemHeight = _.maxBy( items, item => item.node.height ).node.height;

      // We want the arrow area to be square, see https://github.com/phetsims/sun/issues/453
      const arrowAreaSize = ( maxItemHeight + 2 * options.yMargin );

      // The arrow is sized to fit in the arrow area, empirically determined to be visually pleasing.
      const arrowHeight = 0.35 * arrowAreaSize; // height of equilateral triangle
      const arrowWidth = 2 * arrowHeight * Math.sqrt( 3 ) / 3; // side of equilateral triangle

      // Invisible horizontal struts that span the item area and arrow area. Makes layout more straightforward.
      const itemAreaStrut = new HStrut( maxItemWidth + 2 * itemXMargin );
      const arrowAreaStrut = new HStrut( arrowAreaSize, {
        left: itemAreaStrut.right
      } );

      // arrow that points up or down, to indicate which way the list pops up
      let arrowShape = null;
      if ( options.arrowDirection === 'up' ) {
        arrowShape = new Shape()
          .moveTo( 0, arrowHeight )
          .lineTo( arrowWidth / 2, 0 )
          .lineTo( arrowWidth, arrowHeight )
          .close();
      }
      else {
        arrowShape = new Shape()
          .moveTo( 0, 0 )
          .lineTo( arrowWidth, 0 )
          .lineTo( arrowWidth / 2, arrowHeight )
          .close();
      }
      const arrow = new Path( arrowShape, {
        fill: options.arrowFill,
        center: arrowAreaStrut.center
      } );

      // Wrapper for the selected item's Node.
      // Do not transform ComboBoxItem.node because it is shared with ComboBoxListItemNode.
      const itemNodeWrapper = new Node();

      // Vertical separator between the item and arrow that is the full height of the button.
      const vSeparator = new VSeparator( maxItemHeight + 2 * options.yMargin, {
        stroke: 'black',
        lineWidth: options.lineWidth,
        centerX: itemAreaStrut.right,
        centerY: itemAreaStrut.centerY
      } );

      // Margins are different in the item and button areas. And we want the vertical separator to extend
      // beyond the margin.  We've handled those margins above, so the actual margins propagated to the button
      // need to be zero.
      options.xMargin = 0;
      options.yMargin = 0;

      assert && assert( !options.content, 'ComboBoxButton sets content' );
      options.content = new Node( {
        children: [ itemAreaStrut, arrowAreaStrut, itemNodeWrapper, vSeparator, arrow ]
      } );

      super( options );

      const updateItemLayout = () => {
        if ( options.align === 'left' ) {
          itemNodeWrapper.left = itemAreaStrut.left + itemXMargin;
        }
        else if ( options.align === 'right' ) {
          itemNodeWrapper.right = itemAreaStrut.right - itemXMargin;
        }
        else {
          itemNodeWrapper.centerX = itemAreaStrut.centerX;
        }
        itemNodeWrapper.centerY = itemAreaStrut.centerY;
      };

      // When Property's value changes, show the corresponding item's Node on the button.
      let item = null;
      const propertyObserver = ( value ) => {

        // Remove bounds listener from previous item.node
        if ( item && item.node.hasListener( 'bounds', updateItemLayout ) ) {
          item.node.off( 'bounds', updateItemLayout );
        }

        // remove the node for the previous item
        itemNodeWrapper.removeAllChildren();

        // find the ComboBoxItem whose value matches the property's value
        item = _.find( items, item => item.value === value );
        assert && assert( item, 'no item found for value: ' + value );

        // add the associated node
        itemNodeWrapper.addChild( item.node );

        // Update layout if bounds change, see https://github.com/phetsims/scenery-phet/issues/482
        item.node.on( 'bounds', updateItemLayout );

        updateItemLayout();

        // a11y
        this.innerContent = item.a11yLabel;
      };
      property.link( propertyObserver );

      // Add aria-labelledby attribute to the button.
      // The button is aria-labelledby its own label sibling, and then (second) its primary sibling in the PDOM.
      // Order matters!
      assert && assert( !options.ariaLabelledbyAssociations, 'ComboBoxButton sets ariaLabelledbyAssociations' );
      this.ariaLabelledbyAssociations = [
        {
          otherNode: this,
          otherElementName: AccessiblePeer.LABEL_SIBLING,
          thisElementName: AccessiblePeer.PRIMARY_SIBLING
        },
        {
          otherNode: this,
          otherElementName: AccessiblePeer.PRIMARY_SIBLING,
          thisElementName: AccessiblePeer.PRIMARY_SIBLING
        }
      ];

      // signify to AT that this button opens a menu
      this.setAccessibleAttribute( 'aria-haspopup', 'listbox' );

      // @private
      this.disposeComboBoxButton = () => {
        property.unlink( propertyObserver );
      };

      // @private needed by methods
      this.arrow = arrow;
      this.vSeparator = vSeparator;
    }

    /**
     * Sets the button to look like a value display instead of a combo box button.
     * See https://github.com/phetsims/sun/issues/451
     * @param {boolean} displayOnly
     * @public
     */
    setDisplayOnly( displayOnly ) {
      this.arrow.visible = !displayOnly;
      this.vSeparator.visible = !displayOnly;
    }

    /**
     * @public
     * @override
     */
    dispose() {
      this.disposeComboBoxButton();
      super.dispose();
    }
  }

  return sun.register( 'ComboBoxButton', ComboBoxButton );
} );<|endoftext|>


<commit_before>import React from 'react';
import { Component, PropTypes, findDOMNode } from '../utils/';

export default class HeatMapSVG extends Component {
  constructor(props) {
    super(props);
    this.state = {
      days: 0,
    }
    this.onClick = this.onClick.bind(this)
    this.onMouseMove = this.onMouseMove.bind(this)
  }
  componentDidMount() {
    // 根据宽度来生成多少天的图形
    const { days, rectWidth } = this.parent().props;
    const $this = findDOMNode(this);
    const width = $this.parentNode.offsetWidth;
    const col = parseInt(width / (rectWidth + 2), 10);
    const daycount = col * 7 - 14;
    this.setState({
      days: days || daycount
    })
  }
  // 返回不同深浅的颜色
  isExistColor(num) {
    const { panelColors } = this.parent().props;
    let color = '', keys = [], nums = Object.keys(panelColors);
    // 转换成数字
    for (let a = 0; a < nums.length; a++) {
      keys.push(parseInt(nums[a], 10));
    }
    // 排序
    keys = this.numberSort(keys);
    // 判断使用什么颜色
    for (let a = 0; a < keys.length; a++) {
      if (keys[a] > num) {
        color = panelColors[keys[a]];
        break;
      }
      color = panelColors[keys[a]];
    }
    return color;
  }
  onClick(e, curdate, curdt) {
    const { onClick } = this.parent().props;
    onClick(e, curdate, curdt)
  }

  isCurrentData(date) { // 判断传进来的数据，并返回颜色
    const { values, panelColors } = this.parent().props;
    let curdt = {};
    for (var i = 0; i < values.length; i++) {
      let curdate = new Date(values[i]['date']);
      curdate = `${curdate.getFullYear()}/${curdate.getMonth() + 1}/${curdate.getDate()}`;
      if (curdate === date) {
        curdt = values[i];
        break;
      }
    }
    curdt['date'] = date;
    if (curdt.count && curdt.count > 0) {
      curdt.color = this.isExistColor(curdt.count);
    } else {
      curdt.color = panelColors[0] || '#EBEDF0';
    }
    return curdt
  }
  onMouseLeave(e) {
    this.rect = null
    this.parent().hideTooltip();
  }
  onMouseMove(e, curdatestr, curdt) {
    if (e.target.tagName === 'rect' && e.target.dataset && e.target.dataset.date) {
      if (this.rect !== e.target) {
        this.rect = e.target;
        this.parent().onMouseOver(e, e.target.dataset.date, curdt)
      }
    }
  }
  renderPanelColors() {
    const { panelColors, rectWidth, rectHeight } = this.parent().props;
    let col = rectWidth + 2, rectPanelColors = [];
    // 颜色说明栏
    let nums = Object.keys(panelColors);
    for (let i = 0; i < nums.length; i++) {
      let xl = i * col;
      rectPanelColors.push(<rect key={i} width={rectWidth} height={rectHeight} x={xl} y="0" fill={panelColors[nums[i]]}></rect>)
    }
    return rectPanelColors;
  }
  parent() {
    return this.context.component;
  }
  // 排序 比较函数
  numberSort(keys) {
    return keys.sort((x, y) => {//比较函数
      if (x < y) return -1;
      else if (x > y) return 1;
      else return 0;
    })
  }
  renderPanelHeader(ty) {
    const { endDate, weekLables, rectWidth, rectHeight, monthLables } = this.parent().props;
    let { days } = this.state;
    let dayDate = [], oneday = 86400000;
    let timestamp = endDate.getTime();
    let curweek = new Date(timestamp).getDay();
    days = days + curweek + 1;

    for (let i = 0; i < days; i++) {
      dayDate.push(timestamp - (oneday * i));
    }
    dayDate = this.numberSort(dayDate);
    // 日历
    var rectdays = [], rectweeks = [], rectMonth = [], col = rectWidth + 2;
    for (let i = 0; i < days; i++) {
      let xl = parseInt(i / 7, 10) * col;
      let yl = 21 + parseInt(i % 7, 10) * col;
      let curdate = new Date(dayDate[i]);
      let curdatestr = `${curdate.getFullYear()}/${curdate.getMonth() + 1}/${curdate.getDate()}`;
      let curdt = this.isCurrentData(curdatestr);
      // 日方块
      rectdays.push(<rect
        data-date={curdatestr}
        key={i}
        fill={curdt.color}
        x={col + xl}
        y={yl}
        onClick={(e) => this.onClick(e, curdatestr, curdt)}
        onMouseMove={(e) => this.onMouseMove(e, curdatestr, curdt)}
        // onMouseMove={this.onMouseMove.bind(this)}
        width={rectWidth} height={rectHeight}></rect>);
      // 周标题
      if (Object.keys(weekLables).indexOf(i.toString()) > -1 && i < 7) {
        rectweeks.push(<text key={i} x={xl + 7} y={yl} width={rectWidth + 10} height={rectHeight}>{weekLables[i]}</text>);
      }
      // 月标题
      if (parseInt(curdate.getDate(), 10) === 1) {
        rectMonth.push(<text key={i} x={xl + 12}> {monthLables[parseInt(curdate.getMonth(), 10)]} </text>)
      }
    }
    if (ty === 'week') {
      return rectweeks
    } else if (ty === 'month') {
      return rectMonth
    } else if (ty === 'day') {
      return rectdays
    }
  }
  render() {
    const { prefixCls, className } = this.props;
    const { rectWidth, rectHeight } = this.parent().props;
    const cls = this.classNames(prefixCls, className);
    return (
      <svg ref="svg" className={cls} width={`100%`} height={`${rectHeight * 7 + 60}px`}>
        <g className={`${prefixCls}-week`} transform="translate(0, 10)">
          {this.renderPanelHeader('week')}
        </g>
        <g className={`${prefixCls}-month`} transform={`translate(${rectWidth}, ${16})`}>
          {this.renderPanelHeader('month')}
        </g>
        <g transform={`translate(${rectWidth + 2}, ${rectHeight * 7 + 40})`}> {this.renderPanelColors()} </g>
        <g onMouseLeave={this.onMouseLeave.bind(this)}>
          {this.renderPanelHeader('day')}
        </g>
      </svg>
    );
  }
}

HeatMapSVG.propTypes = {
  prefixCls: PropTypes.string,
}

HeatMapSVG.defaultProps = {
  prefixCls: "w-heatmap",
}

HeatMapSVG.contextTypes = {
  component: PropTypes.any
};<commit_msg>fix(Calendar.HeatMap): remove string refs.<commit_after>import React from 'react';
import { Component, PropTypes, findDOMNode } from '../utils/';

export default class HeatMapSVG extends Component {
  constructor(props) {
    super(props);
    this.state = {
      days: 0,
    }
    this.onClick = this.onClick.bind(this)
    this.onMouseMove = this.onMouseMove.bind(this)
  }
  componentDidMount() {
    // 根据宽度来生成多少天的图形
    const { days, rectWidth } = this.parent().props;
    const $this = findDOMNode(this);
    const width = $this.parentNode.offsetWidth;
    const col = parseInt(width / (rectWidth + 2), 10);
    const daycount = col * 7 - 14;
    this.setState({
      days: days || daycount
    })
  }
  // 返回不同深浅的颜色
  isExistColor(num) {
    const { panelColors } = this.parent().props;
    let color = '', keys = [], nums = Object.keys(panelColors);
    // 转换成数字
    for (let a = 0; a < nums.length; a++) {
      keys.push(parseInt(nums[a], 10));
    }
    // 排序
    keys = this.numberSort(keys);
    // 判断使用什么颜色
    for (let a = 0; a < keys.length; a++) {
      if (keys[a] > num) {
        color = panelColors[keys[a]];
        break;
      }
      color = panelColors[keys[a]];
    }
    return color;
  }
  onClick(e, curdate, curdt) {
    const { onClick } = this.parent().props;
    onClick(e, curdate, curdt)
  }

  isCurrentData(date) { // 判断传进来的数据，并返回颜色
    const { values, panelColors } = this.parent().props;
    let curdt = {};
    for (var i = 0; i < values.length; i++) {
      let curdate = new Date(values[i]['date']);
      curdate = `${curdate.getFullYear()}/${curdate.getMonth() + 1}/${curdate.getDate()}`;
      if (curdate === date) {
        curdt = values[i];
        break;
      }
    }
    curdt['date'] = date;
    if (curdt.count && curdt.count > 0) {
      curdt.color = this.isExistColor(curdt.count);
    } else {
      curdt.color = panelColors[0] || '#EBEDF0';
    }
    return curdt
  }
  onMouseLeave(e) {
    this.rect = null
    this.parent().hideTooltip();
  }
  onMouseMove(e, curdatestr, curdt) {
    if (e.target.tagName === 'rect' && e.target.dataset && e.target.dataset.date) {
      if (this.rect !== e.target) {
        this.rect = e.target;
        this.parent().onMouseOver(e, e.target.dataset.date, curdt)
      }
    }
  }
  renderPanelColors() {
    const { panelColors, rectWidth, rectHeight } = this.parent().props;
    let col = rectWidth + 2, rectPanelColors = [];
    // 颜色说明栏
    let nums = Object.keys(panelColors);
    for (let i = 0; i < nums.length; i++) {
      let xl = i * col;
      rectPanelColors.push(<rect key={i} width={rectWidth} height={rectHeight} x={xl} y="0" fill={panelColors[nums[i]]}></rect>)
    }
    return rectPanelColors;
  }
  parent() {
    return this.context.component;
  }
  // 排序 比较函数
  numberSort(keys) {
    return keys.sort((x, y) => {//比较函数
      if (x < y) return -1;
      else if (x > y) return 1;
      else return 0;
    })
  }
  renderPanelHeader(ty) {
    const { endDate, weekLables, rectWidth, rectHeight, monthLables } = this.parent().props;
    let { days } = this.state;
    let dayDate = [], oneday = 86400000;
    let timestamp = endDate.getTime();
    let curweek = new Date(timestamp).getDay();
    days = days + curweek + 1;

    for (let i = 0; i < days; i++) {
      dayDate.push(timestamp - (oneday * i));
    }
    dayDate = this.numberSort(dayDate);
    // 日历
    var rectdays = [], rectweeks = [], rectMonth = [], col = rectWidth + 2;
    for (let i = 0; i < days; i++) {
      let xl = parseInt(i / 7, 10) * col;
      let yl = 21 + parseInt(i % 7, 10) * col;
      let curdate = new Date(dayDate[i]);
      let curdatestr = `${curdate.getFullYear()}/${curdate.getMonth() + 1}/${curdate.getDate()}`;
      let curdt = this.isCurrentData(curdatestr);
      // 日方块
      rectdays.push(<rect
        data-date={curdatestr}
        key={i}
        fill={curdt.color}
        x={col + xl}
        y={yl}
        onClick={(e) => this.onClick(e, curdatestr, curdt)}
        onMouseMove={(e) => this.onMouseMove(e, curdatestr, curdt)}
        // onMouseMove={this.onMouseMove.bind(this)}
        width={rectWidth} height={rectHeight}></rect>);
      // 周标题
      if (Object.keys(weekLables).indexOf(i.toString()) > -1 && i < 7) {
        rectweeks.push(<text key={i} x={xl + 7} y={yl} width={rectWidth + 10} height={rectHeight}>{weekLables[i]}</text>);
      }
      // 月标题
      if (parseInt(curdate.getDate(), 10) === 1) {
        rectMonth.push(<text key={i} x={xl + 12}> {monthLables[parseInt(curdate.getMonth(), 10)]} </text>)
      }
    }
    if (ty === 'week') {
      return rectweeks
    } else if (ty === 'month') {
      return rectMonth
    } else if (ty === 'day') {
      return rectdays
    }
  }
  render() {
    const { prefixCls, className } = this.props;
    const { rectWidth, rectHeight } = this.parent().props;
    const cls = this.classNames(prefixCls, className);
    return (
      <svg className={cls} width={`100%`} height={`${rectHeight * 7 + 60}px`}>
        <g className={`${prefixCls}-week`} transform="translate(0, 10)">
          {this.renderPanelHeader('week')}
        </g>
        <g className={`${prefixCls}-month`} transform={`translate(${rectWidth}, ${16})`}>
          {this.renderPanelHeader('month')}
        </g>
        <g transform={`translate(${rectWidth + 2}, ${rectHeight * 7 + 40})`}> {this.renderPanelColors()} </g>
        <g onMouseLeave={this.onMouseLeave.bind(this)}>
          {this.renderPanelHeader('day')}
        </g>
      </svg>
    );
  }
}

HeatMapSVG.propTypes = {
  prefixCls: PropTypes.string,
}

HeatMapSVG.defaultProps = {
  prefixCls: "w-heatmap",
}

HeatMapSVG.contextTypes = {
  component: PropTypes.any
};
<|endoftext|>


<commit_before>'use strict';

var myMatter = (function() {

  var myMatter = {
    // virtual mouse
    mouseDownEvent: mouseDownEvent,
    mouseMoveEvent: mouseMoveEvent,
    mouseUpEvent: mouseUpEvent,

    // render
    togglePlay: togglePlay,

    // add bodies
    addRandomRectangle: addRandomRectangle,
    addRectangle: addRectangle,
    addCircle: addCircle,
    addVector: addVector,

    // edit bodies
    setStaticOfBody: setStaticOfBody,
    setAngleOfBody: setAngleOfBody,
    setScaleOfBody: setScaleOfBody,
    removeBodyAt: removeBodyAt,

    // state
    state: {
      isHandling: false,
      isDragging: false,
      playMode: true,
      multipleBodiesMode: false,
      selectedBody: null
    },

    // matter
    engine: null,
    render: null,
    world: null,
    mouse: null
  };

  // constants
  var CLICK_DELAY_MS = 200;

  // internals
  var draggedBody = null;
  var startPoint = null;
  var startTimestamp = null;

  // Matter.js module aliases
  var Engine = Matter.Engine,
    Render = Matter.Render,
    World = Matter.World,
    Bodies = Matter.Bodies,
    Composites = Matter.Composites,
    Constraint = Matter.Constraint,
    Mouse = Matter.Mouse;

  init();

  ////////////

  function init() {
    // create a Matter.js engine
    myMatter.engine = Engine.create();
    myMatter.world = myMatter.engine.world;

    myMatter.render = Render.create({
      element: document.getElementById('canvas-container'),
      engine: myMatter.engine
    });

    // setup
    createDefaultBodies();
    createVirtualMouse();
    setRenderOptions();

    // run the engine
    Engine.run(myMatter.engine);

    // run the renderer
    Render.run(myMatter.render);

    // play mode
    togglePlay();
  }

  function createDefaultBodies() {
    // create bodies
    var stack = Composites.stack(250, 255, 1, 6, 0, 0, function(x, y) {
      var body = Bodies.rectangle(x, y, 30, 30);
      body.isMoveable = true;
      return body;
    });

    var catapult = Bodies.rectangle(400, 520, 320, 20, {});
    catapult.isMoveable = true;
    var catapult_stand_left = Constraint.create({bodyA: catapult, pointB: {x: 390, y: 580}});
    var catapult_stand_right = Constraint.create({bodyA: catapult, pointB: {x: 410, y: 580}});
    var holder = Bodies.rectangle(250, 555, 20, 50, {isStatic: true});

    var ball = Bodies.circle(560, 100, 50, {density: 0.005});
    ball.isMoveable = true;
    var ground = Bodies.rectangle(400, 610, 810, 60.5, {isStatic: true});
    ground.render.fillStyle = '#222222';

    var elements = [
      stack,
      catapult,
      catapult_stand_left,
      catapult_stand_right,
      holder,
      ball,
      ground
    ];

    // add all of the bodies to the world
    World.add(myMatter.world, elements);
  }

  // add mouse control
  function createVirtualMouse() {
    myMatter.mouse = Mouse.create(myMatter.render.canvas);
    var MouseConstraint = Matter.MouseConstraint;
    var mouseConstraint = MouseConstraint.create(myMatter.engine, {
      element: myMatter.render.canvas,
      mouse: myMatter.mouse
    });
    World.add(myMatter.world, mouseConstraint);
  }

  function setRenderOptions() {
    var renderOptions = myMatter.render.options;
    renderOptions.wireframes = false;
    renderOptions.hasBounds = false;
    renderOptions.showDebug = false;
    renderOptions.showBroadphase = false;
    renderOptions.showBounds = false;
    renderOptions.showVelocity = false;
    renderOptions.showCollisions = false;
    renderOptions.showAxes = false;
    renderOptions.showPositions = false;
    renderOptions.showAngleIndicator = false;
    renderOptions.showIds = false;
    renderOptions.showShadows = false;
    renderOptions.showVertexNumbers = false;
    renderOptions.showConvexHulls = false;
    renderOptions.showInternalEdges = false;
    renderOptions.showSeparations = false;
    renderOptions.background = '#222222';
  }

  // pause movement of all elements
  function togglePlay() {
    myMatter.state.playMode = !myMatter.state.playMode;

    var allBodies = Matter.Composite.allBodies(myMatter.world);
    for (var i in allBodies) {
      if (allBodies[i].isMoveable) {
        allBodies[i].isStatic = !myMatter.state.playMode;
      }
    }

    // This should do the trick, but it doesn't work
    // http://brm.io/matter-js/docs/#property_timing.timeScale
    // engine.timing.timeScale = 0;
  }

  function addRandomRectangle() {
    var x = rand(30, 790);
    addRectangle(x, 150, 40, 40);
  }

  function addRectangle(x, y, width, height, options) {
    var newRectangle = Bodies.rectangle(x, y, width, height, options);
    newRectangle.isMoveable = true;
    if (!myMatter.state.playMode) {
      newRectangle.isStatic = true;
    }
    World.add(myMatter.world, newRectangle);
  }

  function addCircle(x, y, radius, options) {
    var newCircle = Bodies.circle(x, y, radius, options);
    newCircle.isMoveable = true;
    if (!myMatter.state.playMode) {
      newCircle.isStatic = true;
    }
    World.add(myMatter.world, newCircle);
  }

  function addVector(arrow) {
    var endPoint = {
      x: arrow.Start.x + arrow.Direction.x ,
      y: arrow.Start.y + arrow.Direction.y
    };
    var newArrow = Constraint.create({bodyA: myMatter.selectedBody, pointB: endPoint});

    World.add(myMatter.world, newArrow);
  }

  // pass events to virtual mouse
  function mouseDownEvent(event) {
    var element = elementOnPoint({x: event.layerX, y: event.layerY});

    // handle new body clicks
    if (element && myMatter.selectedBody != element.bodies[element.index]) {
      // start dragging
      draggedBody = element.bodies[element.index];
      myMatter.state.isHandling = true;

      // click Handler
      startPoint = new Point(event.layerX, event.layerY);
      startTimestamp = Date.now();
    }

    // reset selected element
    if (myMatter.selectedBody && (!element || myMatter.selectedBody != element.bodies[element.index]) && !myMatter.state.isDrawing) {
      myMatter.selectedBody.render.lineWidth = 1.5;
      myMatter.selectedBody.render.strokeStyle = Matter.Common.shadeColor( myMatter.selectedBody.render.fillStyle, -20);
      myMatter.selectedBody = null;
    }

    myMatter.mouse.mousedown(event);
  }

  function mouseMoveEvent(event) {
    if (myMatter.state.isHandling && !myMatter.state.isDragging) {
      // only move when not a click
      var currentTimestamp = Date.now();
      if (currentTimestamp - startTimestamp > CLICK_DELAY_MS) {
        myMatter.state.isDragging = true;
        draggedBody.isStatic = false;
      }
    }

    if (myMatter.state.isDragging) {
      myMatter.mouse.mousemove(event);
    }
  }

  function mouseUpEvent(event) {
    if (myMatter.state.isHandling) {

      // click Handler
      var endTimestamp = Date.now();
      if (endTimestamp - startTimestamp < CLICK_DELAY_MS) {
        showMenu(startPoint, draggedBody);
        myMatter.selectedBody = draggedBody;
        myMatter.selectedBody.render.lineWidth = 3;
        myMatter.selectedBody.render.strokeStyle = Matter.Common.shadeColor( myMatter.selectedBody.render.fillStyle, +20);
      }
      startPoint = undefined;
      startTimestamp = undefined;

      // stop dragging
      if (!myMatter.state.playMode || !draggedBody.isMoveable) {
        draggedBody.isStatic = true;
      }
      myMatter.state.isDragging = false;
      draggedBody = null;
    }
    myMatter.state.isHandling = false;
    myMatter.mouse.mouseup(event);
  }

  function removeBodyAt(point) {
    var found = bodyOnPoint(point);
    if (found) {
      Matter.Composite.remove(myMatter.world, found, true);
      return true;
    } else {
      return false;
    }

    //TODO: Constrains
  }

  function bodyOnPoint(point) {
    var result = elementOnPoint(point);
    if (result) {
      return result.bodies[result.index];
    }
    return false;
  }

  function elementOnPoint(point) {
    var bodies = myMatter.world.bodies;
    var found = elementOfBodiesOnPoint(bodies, point);
    if (found) {
      return found;
    }

    for (var i = 0; i < myMatter.world.composites.length; i++) {
      bodies = myMatter.world.composites[i].bodies;
      return elementOfBodiesOnPoint(bodies, point);
    }
  }

  function elementOfBodiesOnPoint(bodies, point) {
    for (var i = 0; i < bodies.length; i++) {
      if (Matter.Bounds.contains(bodies[i].bounds, point)) {
        return {
          bodies: bodies,
          index: i
        };
      }
    }
    return false;
  }

  function setStaticOfBody(body, isStatic) {
    if (isStatic) {
      body.isMoveable = false;
      body.isStatic = true;
    } else {
      body.isMoveable = true;
      body.isStatic = false;
    }

    // render properties (http://brm.io/matter-js/docs/files/src_body_Body.js.html#l151)
    var newFillStyle = (!body.isMoveable ? '#eeeeee' : Matter.Common.choose(['#556270', '#4ECDC4', '#C7F464', '#FF6B6B', '#C44D58']));
    var newStrokeStyle = Matter.Common.shadeColor(newFillStyle, -20);
    body.render.fillStyle = newFillStyle;
    body.render.strokeStyle = newStrokeStyle;

    // play mode
    if (!myMatter.state.playMode) {
      body.isStatic = true;
    }
  }

  function setAngleOfBody(body, angle) {
    Matter.Body.setAngle(body, angle);
  }

  function setScaleOfBody(body, scaleX, scaleY) {
    Matter.Body.scale(body, scaleX, scaleY);
  }

  return myMatter;

})();
<commit_msg>apply force instead of drawing vector in play mode<commit_after>'use strict';

var myMatter = (function() {

  var myMatter = {
    // virtual mouse
    mouseDownEvent: mouseDownEvent,
    mouseMoveEvent: mouseMoveEvent,
    mouseUpEvent: mouseUpEvent,

    // render
    togglePlay: togglePlay,

    // add bodies
    addRandomRectangle: addRandomRectangle,
    addRectangle: addRectangle,
    addCircle: addCircle,
    addVector: addVector,

    // edit bodies
    setStaticOfBody: setStaticOfBody,
    setAngleOfBody: setAngleOfBody,
    setScaleOfBody: setScaleOfBody,
    removeBodyAt: removeBodyAt,

    // state
    state: {
      isHandling: false,
      isDragging: false,
      playMode: true,
      multipleBodiesMode: false,
      selectedBody: null
    },

    // matter
    engine: null,
    render: null,
    world: null,
    mouse: null
  };

  // constants
  var CLICK_DELAY_MS = 200;

  // internals
  var draggedBody = null;
  var startPoint = null;
  var startTimestamp = null;

  // Matter.js module aliases
  var Engine = Matter.Engine,
    Render = Matter.Render,
    World = Matter.World,
    Bodies = Matter.Bodies,
    Composites = Matter.Composites,
    Constraint = Matter.Constraint,
    Mouse = Matter.Mouse;

  init();

  ////////////

  function init() {
    // create a Matter.js engine
    myMatter.engine = Engine.create();
    myMatter.world = myMatter.engine.world;

    myMatter.render = Render.create({
      element: document.getElementById('canvas-container'),
      engine: myMatter.engine
    });

    // setup
    createDefaultBodies();
    createVirtualMouse();
    setRenderOptions();

    // run the engine
    Engine.run(myMatter.engine);

    // run the renderer
    Render.run(myMatter.render);

    // play mode
    togglePlay();
  }

  function createDefaultBodies() {
    // create bodies
    var stack = Composites.stack(250, 255, 1, 6, 0, 0, function(x, y) {
      var body = Bodies.rectangle(x, y, 30, 30);
      body.isMoveable = true;
      return body;
    });

    var catapult = Bodies.rectangle(400, 520, 320, 20, {});
    catapult.isMoveable = true;
    var catapult_stand_left = Constraint.create({bodyA: catapult, pointB: {x: 390, y: 580}});
    var catapult_stand_right = Constraint.create({bodyA: catapult, pointB: {x: 410, y: 580}});
    var holder = Bodies.rectangle(250, 555, 20, 50, {isStatic: true});

    var ball = Bodies.circle(560, 100, 50, {density: 0.005});
    ball.isMoveable = true;
    var ground = Bodies.rectangle(400, 610, 810, 60.5, {isStatic: true});
    ground.render.fillStyle = '#222222';

    var elements = [
      stack,
      catapult,
      catapult_stand_left,
      catapult_stand_right,
      holder,
      ball,
      ground
    ];

    // add all of the bodies to the world
    World.add(myMatter.world, elements);
  }

  // add mouse control
  function createVirtualMouse() {
    myMatter.mouse = Mouse.create(myMatter.render.canvas);
    var MouseConstraint = Matter.MouseConstraint;
    var mouseConstraint = MouseConstraint.create(myMatter.engine, {
      element: myMatter.render.canvas,
      mouse: myMatter.mouse
    });
    World.add(myMatter.world, mouseConstraint);
  }

  function setRenderOptions() {
    var renderOptions = myMatter.render.options;
    renderOptions.wireframes = false;
    renderOptions.hasBounds = false;
    renderOptions.showDebug = false;
    renderOptions.showBroadphase = false;
    renderOptions.showBounds = false;
    renderOptions.showVelocity = false;
    renderOptions.showCollisions = false;
    renderOptions.showAxes = false;
    renderOptions.showPositions = false;
    renderOptions.showAngleIndicator = false;
    renderOptions.showIds = false;
    renderOptions.showShadows = false;
    renderOptions.showVertexNumbers = false;
    renderOptions.showConvexHulls = false;
    renderOptions.showInternalEdges = false;
    renderOptions.showSeparations = false;
    renderOptions.background = '#222222';
  }

  // pause movement of all elements
  function togglePlay() {
    myMatter.state.playMode = !myMatter.state.playMode;

    var allBodies = Matter.Composite.allBodies(myMatter.world);
    for (var i in allBodies) {
      if (allBodies[i].isMoveable) {
        allBodies[i].isStatic = !myMatter.state.playMode;
      }
    }

    // This should do the trick, but it doesn't work
    // http://brm.io/matter-js/docs/#property_timing.timeScale
    // engine.timing.timeScale = 0;
  }

  function addRandomRectangle() {
    var x = rand(30, 790);
    addRectangle(x, 150, 40, 40);
  }

  function addRectangle(x, y, width, height, options) {
    var newRectangle = Bodies.rectangle(x, y, width, height, options);
    newRectangle.isMoveable = true;
    if (!myMatter.state.playMode) {
      newRectangle.isStatic = true;
    }
    World.add(myMatter.world, newRectangle);
  }

  function addCircle(x, y, radius, options) {
    var newCircle = Bodies.circle(x, y, radius, options);
    newCircle.isMoveable = true;
    if (!myMatter.state.playMode) {
      newCircle.isStatic = true;
    }
    World.add(myMatter.world, newCircle);
  }

  function addVector(arrow) {
    if (myMatter.state.playMode) {
      var body = myMatter.selectedBody;
      var position = body.position;
      var forceDivider = (40 / body.mass) * 30;
      var force = Matter.Vector.create(
        arrow.Direction.x / forceDivider,
        arrow.Direction.y / forceDivider
      );
      Matter.Body.applyForce(body, position, force);
    } else {
      var endPoint = {
        x: arrow.Start.x + arrow.Direction.x ,
        y: arrow.Start.y + arrow.Direction.y
      };
      var newArrow = Constraint.create({bodyA: myMatter.selectedBody, pointB: endPoint});
      World.add(myMatter.world, newArrow);
    }
  }

  // pass events to virtual mouse
  function mouseDownEvent(event) {
    var element = elementOnPoint({x: event.layerX, y: event.layerY});

    // handle new body clicks
    if (element && myMatter.selectedBody != element.bodies[element.index]) {
      // start dragging
      draggedBody = element.bodies[element.index];
      myMatter.state.isHandling = true;

      // click Handler
      startPoint = new Point(event.layerX, event.layerY);
      startTimestamp = Date.now();
    }

    // reset selected element
    if (myMatter.selectedBody && (!element || myMatter.selectedBody != element.bodies[element.index]) && !myMatter.state.isDrawing) {
      myMatter.selectedBody.render.lineWidth = 1.5;
      myMatter.selectedBody.render.strokeStyle = Matter.Common.shadeColor( myMatter.selectedBody.render.fillStyle, -20);
      myMatter.selectedBody = null;
    }

    myMatter.mouse.mousedown(event);
  }

  function mouseMoveEvent(event) {
    if (myMatter.state.isHandling && !myMatter.state.isDragging) {
      // only move when not a click
      var currentTimestamp = Date.now();
      if (currentTimestamp - startTimestamp > CLICK_DELAY_MS) {
        myMatter.state.isDragging = true;
        draggedBody.isStatic = false;
      }
    }

    if (myMatter.state.isDragging) {
      myMatter.mouse.mousemove(event);
    }
  }

  function mouseUpEvent(event) {
    if (myMatter.state.isHandling) {

      // click Handler
      var endTimestamp = Date.now();
      if (endTimestamp - startTimestamp < CLICK_DELAY_MS) {
        showMenu(startPoint, draggedBody);
        myMatter.selectedBody = draggedBody;
        myMatter.selectedBody.render.lineWidth = 3;
        myMatter.selectedBody.render.strokeStyle = Matter.Common.shadeColor( myMatter.selectedBody.render.fillStyle, +20);
      }
      startPoint = undefined;
      startTimestamp = undefined;

      // stop dragging
      if (!myMatter.state.playMode || !draggedBody.isMoveable) {
        draggedBody.isStatic = true;
      }
      myMatter.state.isDragging = false;
      draggedBody = null;
    }
    myMatter.state.isHandling = false;
    myMatter.mouse.mouseup(event);
  }

  function removeBodyAt(point) {
    var found = bodyOnPoint(point);
    if (found) {
      Matter.Composite.remove(myMatter.world, found, true);
      return true;
    } else {
      return false;
    }

    //TODO: Constrains
  }

  function bodyOnPoint(point) {
    var result = elementOnPoint(point);
    if (result) {
      return result.bodies[result.index];
    }
    return false;
  }

  function elementOnPoint(point) {
    var bodies = myMatter.world.bodies;
    var found = elementOfBodiesOnPoint(bodies, point);
    if (found) {
      return found;
    }

    for (var i = 0; i < myMatter.world.composites.length; i++) {
      bodies = myMatter.world.composites[i].bodies;
      return elementOfBodiesOnPoint(bodies, point);
    }
  }

  function elementOfBodiesOnPoint(bodies, point) {
    for (var i = 0; i < bodies.length; i++) {
      if (Matter.Bounds.contains(bodies[i].bounds, point)) {
        return {
          bodies: bodies,
          index: i
        };
      }
    }
    return false;
  }

  function setStaticOfBody(body, isStatic) {
    if (isStatic) {
      body.isMoveable = false;
      body.isStatic = true;
    } else {
      body.isMoveable = true;
      body.isStatic = false;
    }

    // render properties (http://brm.io/matter-js/docs/files/src_body_Body.js.html#l151)
    var newFillStyle = (!body.isMoveable ? '#eeeeee' : Matter.Common.choose(['#556270', '#4ECDC4', '#C7F464', '#FF6B6B', '#C44D58']));
    var newStrokeStyle = Matter.Common.shadeColor(newFillStyle, -20);
    body.render.fillStyle = newFillStyle;
    body.render.strokeStyle = newStrokeStyle;

    // play mode
    if (!myMatter.state.playMode) {
      body.isStatic = true;
    }
  }

  function setAngleOfBody(body, angle) {
    Matter.Body.setAngle(body, angle);
  }

  function setScaleOfBody(body, scaleX, scaleY) {
    Matter.Body.scale(body, scaleX, scaleY);
  }

  return myMatter;

})();
<|endoftext|>


<commit_before>function tokenizeAndDesugaring(source){

	/* Temp copied from parse.js */
	var isVarNameToken = function (t) {
		if (reservedKeywords.indexOf(t.value) != -1){
			return false;
		}
		return t.type === 'name';
	};
	var isOperatorToken = t => t && t.type === 'operator';
	var isOperatorTokenWithValue = (t, v) => isOperatorToken(t) && t.value === v;
	var isClosingBracketToken = t => isOperatorTokenWithValue(t, ')');
	var isNewlineToken = t => t && t.type === 'newline';
	const reservedKeywords = ['var', 'func', 'import', 'class',
	'if', 'else', 'switch', 'fallthrough', 'case', 'default',
	'continue', 'break', 'while', 'do', 'for', 'in', 'by', 'return'];

	// var program_string_without_comments
	// = source.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, '$1');
	let original_tokens = source.tokens('=<>!+-*&|/%^*.:', '=<>&|*+-.');
	let desugared_tokens = [];

	// desugaring
	let brace_count = 0;
	let class_level = 0;
	let class_arg_positions = _list();
	let class_constructor = false;
	let class_constructor_arg_tokens = [];
	let class_constructor_stack = _list();
	let decorator_stack = _list();
	let decorator_positions = _list();
	let decorated_funcs = _list();
	for (let i = 0; i < original_tokens.length; i++) {
		let t = original_tokens[i];
		if (t.type !== 'operator' && t.type !== 'name') {
			desugared_tokens.push(t);
			continue;
		}
		switch (t.value) {
			case '[':
				if (i > 0 &&
					// array[x]
					(isVarNameToken(original_tokens[i-1]) ||
						// ...[x][x]
						isOperatorTokenWithValue(original_tokens[i-1], ']') ||
						// application[x]
						isOperatorTokenWithValue(original_tokens[i-1], ')'))) {
					desugared_tokens.push(original_tokens[i]); // '['
				} else {
					// list
					desugared_tokens.push(new Token('name', '$list', t.line));
				}
				desugared_tokens.push(new Token('operator', '(', t.line));
				break;
			case ']':
				if (desugared_tokens.length > 0) {
					let prev_token = desugared_tokens[desugared_tokens.length-1];
					if (isOperatorTokenWithValue(',')) {
						// remove trailing comma
						desugared_tokens.pop();
					}
				}
				desugared_tokens.push(new Token('operator', ')', t.line));
				break;
			case 'class':
				class_constructor_stack = _pair(false, class_constructor_stack);
				desugared_tokens.push(new Token('name', 'func', t.line));
				i++;
				t = original_tokens[i];
				desugared_tokens.push(t); // class name
				desugared_tokens.push(new Token('operator', '(', t.line));
				class_arg_positions = _pair(desugared_tokens.length, class_arg_positions);
				desugared_tokens.push(new Token('operator', ')', t.line));

				// inheritance (Python-like)
				if (original_tokens[i+1].value === '(') {
					t = original_tokens[++i]; // '('
					desugared_tokens.push(new Token('name', 'extends', t.line));
					t = original_tokens[++i]; // first parent
					while (!isClosingBracketToken(t)) {
						desugared_tokens.push(t); // parent name or ','
						t = original_tokens[++i];
					}
				}
				class_level++;
				break;
			case '@':
				class_constructor_stack = _pair(true, _tail(class_constructor_stack));
				// constructor
				if (class_level > 0 && brace_count === class_level) {
					if (_is_empty(class_arg_positions)){
						throwError(t, 'Invalid constructor position.');
					}
					desugared_tokens.push(new Token('name', 'func', t.line));
					desugared_tokens.push(t); // '@'
					i++;
					t = original_tokens[i];
					desugared_tokens.push(t); // '('
					let position = _head(class_arg_positions);
					i++;
					t = original_tokens[i];
					class_constructor_arg_tokens = [];
					while (t.value !== ')') {

						desugared_tokens.push(t);
						let trans_token;
						if (t.type === 'name') {
							// prepend @ to argument name
							// to prevent conflict with other vars in the class
							trans_token = new Token('name', '@' + t.value, t.line);
						} else {
							trans_token = t;
						}

						desugared_tokens.splice(position++, 0, trans_token);
						class_constructor_arg_tokens.push(trans_token);
						i++;
						t = original_tokens[i];
					}
					desugared_tokens.push(t); // ')'
					class_constructor = true;
				} else {
					// other usage of '@'
					desugared_tokens.push(t);
				}
				break;
			case '#':
				// decorator
				let decorators = [];
				let func_arg_tokens = [];
				while (isOperatorTokenWithValue(t, '#')) {
					t = original_tokens[++i];
					if (isVarNameToken(t)) {
						decorators.push(t);
					} else {
						throwError(t, 'Invalid decorator: ' + t.value);
					}

					t = original_tokens[++i];
					if (isNewlineToken(t)) {
						t = original_tokens[++i];
					}
				}

				decorator_stack = _pair(decorators, decorator_stack);
				decorator_positions = _pair(brace_count, decorator_positions);
				//desugared_tokens.push(t); // func
				t = original_tokens[i+1];
				decorated_funcs = _pair(t, decorated_funcs);
				i--;
				//desugared_tokens.push(t); // func name
				break;
			case '{':
				desugared_tokens.push(t);
				brace_count++;
				if (class_level > 0 && brace_count === class_level) {
					// start of class
					desugared_tokens.push(new Token('name', 'this', t.line));
					desugared_tokens.push(new Token('operator', ':=', t.line));
					desugared_tokens.push(new Token('name', 'func', t.line));
					desugared_tokens.push(new Token('operator', '(', t.line));
					desugared_tokens.push(new Token('operator', ')', t.line));
					desugared_tokens.push(new Token('operator', '{', t.line));
					desugared_tokens.push(new Token('operator', '}', t.line));
				}
				break;
			case '}':
				if (class_level > 0 && brace_count === class_level) {
					// end of class
					if (_head(class_constructor_stack)) {
						desugared_tokens.push(new Token('name', '@', t.line));
						desugared_tokens.push(new Token('operator', '(', t.line));
						desugared_tokens = desugared_tokens.concat(class_constructor_arg_tokens);
						desugared_tokens.push(new Token('operator', ')', t.line));
					}
					class_constructor_stack = _tail(class_constructor_stack);
					desugared_tokens.push(new Token('name', 'return', t.line));
					desugared_tokens.push(new Token('name', 'this', t.line));
					class_level--;
					class_arg_positions = _tail(class_arg_positions);
				} else if(class_level > 0 && class_constructor && brace_count === class_level+1) {
					// end of constructor
					class_constructor = false;
				}
				desugared_tokens.push(t); // '}'

				brace_count--;

				if (!_is_empty(decorator_positions) && brace_count === _head(decorator_positions)) {
					let decorators = _head(decorator_stack);
					let decorated_func = _head(decorated_funcs);
					for (let i = 0; i < decorators.length; i++) {
						let decorator = decorators[i];
						desugared_tokens.push(new Token('name', decorated_func.value, decorator.line));
						desugared_tokens.push(new Token('operator', '=', decorator.line));
						desugared_tokens.push(new Token('name', decorator.value, decorator.line));
						desugared_tokens.push(new Token('operator', '(', decorator.line));
						desugared_tokens.push(new Token('name', decorated_func.value, decorator.line));
						desugared_tokens.push(new Token('operator', ')', decorator.line));
						desugared_tokens.push(new Token('newline', '', decorator.line));
					}
					decorator_positions = _tail(decorator_positions);
					decorator_stack = _tail(decorator_stack);
					decorated_funcs = _tail(decorated_funcs);
				}
				break;
			default:
				if (t.type === 'name' && t.value && t.value.length > 0) {
					let firstChar = t.value.charAt(0);
					if (firstChar === '@') {
						// prevent defining system generated variables
						throwError(t, 'Invalid variable name: ' + t.value);
					}
				}
				desugared_tokens.push(t);
		}
	}
/*
	for (let i = 0 ; i < desugared_tokens.length; i++) {
		console.log(desugared_tokens[i].value);
	}*/

	return desugared_tokens;
}
<commit_msg>Support decorator with params<commit_after>function tokenizeAndDesugaring(source){

	/* Temp copied from parse.js */
	var isVarNameToken = function (t) {
		if (reservedKeywords.indexOf(t.value) != -1){
			return false;
		}
		return t.type === 'name';
	};
	var isOperatorToken = t => t && t.type === 'operator';
	var isOperatorTokenWithValue = (t, v) => isOperatorToken(t) && t.value === v;
	var isClosingBracketToken = t => isOperatorTokenWithValue(t, ')');
	var isNewlineToken = t => t && t.type === 'newline';
	const reservedKeywords = ['var', 'func', 'import', 'class',
	'if', 'else', 'switch', 'fallthrough', 'case', 'default',
	'continue', 'break', 'while', 'do', 'for', 'in', 'by', 'return'];

	// var program_string_without_comments
	// = source.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, '$1');
	let original_tokens = source.tokens('=<>!+-*&|/%^*.:', '=<>&|*+-.');
	let desugared_tokens = [];

	// desugaring
	let brace_count = 0;
	let class_level = 0;
	let class_arg_positions = _list();
	let class_constructor = false;
	let class_constructor_arg_tokens = [];
	let class_constructor_stack = _list();
	let decorator_stack = _list();
	let decorator_positions = _list();
	let decorated_funcs = _list();
	for (let i = 0; i < original_tokens.length; i++) {
		let t = original_tokens[i];
		if (t.type !== 'operator' && t.type !== 'name') {
			desugared_tokens.push(t);
			continue;
		}
		switch (t.value) {
			case '[':
				if (i > 0 &&
					// array[x]
					(isVarNameToken(original_tokens[i-1]) ||
						// ...[x][x]
						isOperatorTokenWithValue(original_tokens[i-1], ']') ||
						// application[x]
						isOperatorTokenWithValue(original_tokens[i-1], ')'))) {
					desugared_tokens.push(original_tokens[i]); // '['
				} else {
					// list
					desugared_tokens.push(new Token('name', '$list', t.line));
				}
				desugared_tokens.push(new Token('operator', '(', t.line));
				break;
			case ']':
				if (desugared_tokens.length > 0) {
					let prev_token = desugared_tokens[desugared_tokens.length-1];
					if (isOperatorTokenWithValue(',')) {
						// remove trailing comma
						desugared_tokens.pop();
					}
				}
				desugared_tokens.push(new Token('operator', ')', t.line));
				break;
			case 'class':
				class_constructor_stack = _pair(false, class_constructor_stack);
				desugared_tokens.push(new Token('name', 'func', t.line));
				i++;
				t = original_tokens[i];
				desugared_tokens.push(t); // class name
				desugared_tokens.push(new Token('operator', '(', t.line));
				class_arg_positions = _pair(desugared_tokens.length, class_arg_positions);
				desugared_tokens.push(new Token('operator', ')', t.line));

				// inheritance (Python-like)
				if (original_tokens[i+1].value === '(') {
					t = original_tokens[++i]; // '('
					desugared_tokens.push(new Token('name', 'extends', t.line));
					t = original_tokens[++i]; // first parent
					while (!isClosingBracketToken(t)) {
						desugared_tokens.push(t); // parent name or ','
						t = original_tokens[++i];
					}
				}
				class_level++;
				break;
			case '@':
				class_constructor_stack = _pair(true, _tail(class_constructor_stack));
				// constructor
				if (class_level > 0 && brace_count === class_level) {
					if (_is_empty(class_arg_positions)){
						throwError(t, 'Invalid constructor position.');
					}
					desugared_tokens.push(new Token('name', 'func', t.line));
					desugared_tokens.push(t); // '@'
					i++;
					t = original_tokens[i];
					desugared_tokens.push(t); // '('
					let position = _head(class_arg_positions);
					i++;
					t = original_tokens[i];
					class_constructor_arg_tokens = [];
					while (t.value !== ')') {

						desugared_tokens.push(t);
						let trans_token;
						if (t.type === 'name') {
							// prepend @ to argument name
							// to prevent conflict with other vars in the class
							trans_token = new Token('name', '@' + t.value, t.line);
						} else {
							trans_token = t;
						}

						desugared_tokens.splice(position++, 0, trans_token);
						class_constructor_arg_tokens.push(trans_token);
						i++;
						t = original_tokens[i];
					}
					desugared_tokens.push(t); // ')'
					class_constructor = true;
				} else {
					// other usage of '@'
					desugared_tokens.push(t);
				}
				break;
			case '#':
				// decorator
				let decorators = [];
				let func_arg_tokens = [];
				while (isOperatorTokenWithValue(t, '#')) {
					decorator_stmt = [];
					t = original_tokens[++i];
					while (!isNewlineToken(t)) {
						decorator_stmt.push(t);
						t = original_tokens[++i];
					}
					decorators.push(decorator_stmt);

					t = original_tokens[++i];
					if (isNewlineToken(t)) {
						t = original_tokens[++i];
					}
				}

				decorator_stack = _pair(decorators, decorator_stack);
				decorator_positions = _pair(brace_count, decorator_positions);
				//desugared_tokens.push(t); // func
				t = original_tokens[i+1];
				decorated_funcs = _pair(t, decorated_funcs);
				i--;
				//desugared_tokens.push(t); // func name
				break;
			case '{':
				desugared_tokens.push(t);
				brace_count++;
				if (class_level > 0 && brace_count === class_level) {
					// start of class
					desugared_tokens.push(new Token('name', 'this', t.line));
					desugared_tokens.push(new Token('operator', ':=', t.line));
					desugared_tokens.push(new Token('name', 'func', t.line));
					desugared_tokens.push(new Token('operator', '(', t.line));
					desugared_tokens.push(new Token('operator', ')', t.line));
					desugared_tokens.push(new Token('operator', '{', t.line));
					desugared_tokens.push(new Token('operator', '}', t.line));
				}
				break;
			case '}':
				if (class_level > 0 && brace_count === class_level) {
					// end of class
					if (_head(class_constructor_stack)) {
						desugared_tokens.push(new Token('name', '@', t.line));
						desugared_tokens.push(new Token('operator', '(', t.line));
						desugared_tokens = desugared_tokens.concat(class_constructor_arg_tokens);
						desugared_tokens.push(new Token('operator', ')', t.line));
					}
					class_constructor_stack = _tail(class_constructor_stack);
					desugared_tokens.push(new Token('name', 'return', t.line));
					desugared_tokens.push(new Token('name', 'this', t.line));
					class_level--;
					class_arg_positions = _tail(class_arg_positions);
				} else if(class_level > 0 && class_constructor && brace_count === class_level+1) {
					// end of constructor
					class_constructor = false;
				}
				desugared_tokens.push(t); // '}'

				brace_count--;

				// run decorator
				if (!_is_empty(decorator_positions) && brace_count === _head(decorator_positions)) {
					let decorators = _head(decorator_stack);
					let decorated_func = _head(decorated_funcs);
					for (let i = 0; i < decorators.length; i++) {
						let decorator_stmt = decorators[i];
						let decorator = decorator_stmt[0];
						desugared_tokens.push(new Token('name', decorated_func.value, decorator.line));
						desugared_tokens.push(new Token('operator', '=', decorator.line));

						desugared_tokens = desugared_tokens.concat(decorator_stmt);
						//desugared_tokens.push(new Token('name', decorator.value, decorator.line));

						desugared_tokens.push(new Token('operator', '(', decorator.line));
						desugared_tokens.push(new Token('name', decorated_func.value, decorator.line));
						desugared_tokens.push(new Token('operator', ')', decorator.line));
						desugared_tokens.push(new Token('newline', '', decorator.line));
					}
					decorator_positions = _tail(decorator_positions);
					decorator_stack = _tail(decorator_stack);
					decorated_funcs = _tail(decorated_funcs);
				}
				break;
			default:
				if (t.type === 'name' && t.value && t.value.length > 0) {
					let firstChar = t.value.charAt(0);
					if (firstChar === '@') {
						// prevent defining system generated variables
						throwError(t, 'Invalid variable name: ' + t.value);
					}
				}
				desugared_tokens.push(t);
		}
	}
/*
	for (let i = 0 ; i < desugared_tokens.length; i++) {
		console.log(desugared_tokens[i].value);
	}*/

	return desugared_tokens;
}
<|endoftext|>