<commit_before>/**
 * @ngdoc module
 * @name material.components.input
 */

angular.module('material.components.input', [
  'material.core'
])
  .directive('mdInputContainer', mdInputContainerDirective)
  .directive('label', labelDirective)
  .directive('input', inputTextareaDirective)
  .directive('textarea', inputTextareaDirective)
  .directive('mdMaxlength', mdMaxlengthDirective)
  .directive('placeholder', placeholderDirective)
  .directive('ngMessages', ngMessagesDirective);

/**
 * @ngdoc directive
 * @name mdInputContainer
 * @module material.components.input
 *
 * @restrict E
 *
 * @description
 * `<md-input-container>` is the parent of any input or textarea element.
 *
 * Input and textarea elements will not behave properly unless the md-input-container
 * parent is provided.
 *
 * @param md-is-error {expression=} When the given expression evaluates to true, the input container
 *   will go into error state. Defaults to erroring if the input has been touched and is invalid.
 * @param md-no-float {boolean=} When present, placeholders will not be converted to floating
 *   labels.
 *
 * @usage
 * <hljs lang="html">
 *
 * <md-input-container>
 *   Username
 *   <input type="text" ng-model="user.name">
 * </md-input-container>
 *
 * <md-input-container>
 *   Description
 *   <textarea ng-model="user.description"></textarea>
 * </md-input-container>
 *
 * </hljs>
 */
function mdInputContainerDirective($mdTheming, $parse) {
  return {
    restrict: 'E',
    link: postLink,
    controller: ContainerCtrl
  };

  function postLink(scope, element, attr) {
    $mdTheming(element);
  }

  function ContainerCtrl($scope, $element, $attrs) {
    var self = this;

    self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError);

    self.delegateClick = function() {
      self.input.focus();
    };
    self.element = $element;
    self.setFocused = function(isFocused) {
      $element.toggleClass('md-input-focused', !!isFocused);
    };
    self.setHasValue = function(hasValue) {
      $element.toggleClass('md-input-has-value', !!hasValue);
    };
    self.setHasMessages = function(hasMessages) {
      $element.toggleClass('md-input-has-messages', !!hasMessages);
    };
    self.setHasPlaceholder = function(hasPlaceholder) {
      $element.toggleClass('md-input-has-placeholder', !!hasPlaceholder);
    };
    self.setInvalid = function(isInvalid) {
      $element.toggleClass('md-input-invalid', !!isInvalid);
    };
    $scope.$watch(function() {
      return self.label && self.input;
    }, function(hasLabelAndInput) {
      if (hasLabelAndInput && !self.label.attr('for')) {
        self.label.attr('for', self.input.attr('id'));
      }
    });
  }
}

function labelDirective() {
  return {
    restrict: 'E',
    require: '^?mdInputContainer',
    link: function(scope, element, attr, containerCtrl) {
      if (!containerCtrl || attr.mdNoFloat) return;

      containerCtrl.label = element;
      scope.$on('$destroy', function() {
        containerCtrl.label = null;
      });
    }
  };
}

/**
 * @ngdoc directive
 * @name mdInput
 * @restrict E
 * @module material.components.input
 *
 * @description
 * Use the `<input>` or the  `<textarea>` as a child of an `<md-input-container>`.
 *
 * @param {number=} md-maxlength The maximum number of characters allowed in this input. If this is
 *   specified, a character counter will be shown underneath the input.

 *   The purpose of **`md-maxlength`** is exactly to show the max length counter text. If you don't
 *   want the counter text and only need "plain" validation, you can use the "simple" `ng-maxlength`
 *   or maxlength attributes.
 * @param {string=} aria-label Aria-label is required when no label is present.  A warning message
 *   will be logged in the console if not present.
 * @param {string=} placeholder An alternative approach to using aria-label when the label is not
 *   PRESENT. The placeholder text is copied to the aria-label attribute.
 * @param md-no-autogrow {boolean=} When present, textareas will not grow automatically.
 *
 * @usage
 * <hljs lang="html">
 * <md-input-container>
 *   Color
 *   <input type="text" ng-model="color" required md-maxlength="10">
 * </md-input-container>
 * </hljs>
 *
With Errors

 *
 * <hljs lang="html">
 * <form name="userForm">
 *   <md-input-container>
 *     Last Name
 *     <input name="lastName" ng-model="lastName" required md-maxlength="10" minlength="4">
 *

 *
This is required!

 *
That's too long!

 *
That's too short!

 *

 *   </md-input-container>
 *   <md-input-container>
 *     Biography
 *     <textarea name="bio" ng-model="biography" required md-maxlength="150"></textarea>
 *

 *
This is required!

 *
That's too long!

 *

 *   </md-input-container>
 *   <md-input-container>
 *     <input aria-label='title' ng-model='title'>
 *   </md-input-container>
 *   <md-input-container>
 *     <input placeholder='title' ng-model='title'>
 *   </md-input-container>
 * </form>
 * </hljs>
 *
 * Requires [ngMessages](https://docs.angularjs.org/api/ngMessages).
 * Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).
 *
 */

function inputTextareaDirective($mdUtil, $window, $mdAria) {
  return {
    restrict: 'E',
    require: ['^?mdInputContainer', '?ngModel'],
    link: postLink
  };

  function postLink(scope, element, attr, ctrls) {

    var containerCtrl = ctrls[0];
    var hasNgModel = !!ctrls[1];
    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
    var isReadonly = angular.isDefined(attr.readonly);

    if (!containerCtrl) return;
    if (containerCtrl.input) {
      throw new Error("<md-input-container> can only have *one* <input>, <textarea> or <md-select> child element!");
    }
    containerCtrl.input = element;

    if (!containerCtrl.label) {
      $mdAria.expect(element, 'aria-label', element.attr('placeholder'));
    }

    element.addClass('md-input');
    if (!element.attr('id')) {
      element.attr('id', 'input_' + $mdUtil.nextUid());
    }

    if (element[0].tagName.toLowerCase() === 'textarea') {
      setupTextarea();
    }

    // If the input doesn't have an ngModel, it may have a static value. For that case,
    // we have to do one initial check to determine if the container should be in the
    // "has a value" state.
    if (!hasNgModel) {
      inputCheckValue();
    }

    var isErrorGetter = containerCtrl.isErrorGetter || function() {
        return ngModelCtrl.$invalid && ngModelCtrl.$touched;
      };
    scope.$watch(isErrorGetter, containerCtrl.setInvalid);

    ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
    ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);

    element.on('input', inputCheckValue);

    if (!isReadonly) {
      element
        .on('focus', function(ev) {
          containerCtrl.setFocused(true);
        })
        .on('blur', function(ev) {
          containerCtrl.setFocused(false);
          inputCheckValue();
        });

    }

    //ngModelCtrl.$setTouched();
    //if( ngModelCtrl.$invalid ) containerCtrl.setInvalid();

    scope.$on('$destroy', function() {
      containerCtrl.setFocused(false);
      containerCtrl.setHasValue(false);
      containerCtrl.input = null;
    });

    /**
     *
     */
    function ngModelPipelineCheckValue(arg) {
      containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(arg));
      return arg;
    }

    function inputCheckValue() {
      // An input's value counts if its length > 0,
      // or if the input's validity state says it has bad input (eg string in a number input)
      containerCtrl.setHasValue(element.val().length > 0 || (element[0].validity || {}).badInput);
    }

    function setupTextarea() {
      if (angular.isDefined(element.attr('md-no-autogrow'))) {
        return;
      }

      var node = element[0];
      var container = containerCtrl.element[0];

      var min_rows = NaN;
      var lineHeight = null;
      // can't check if height was or not explicity set,
      // so rows attribute will take precedence if present
      if (node.hasAttribute('rows')) {
        min_rows = parseInt(node.getAttribute('rows'));
      }

      var onChangeTextarea = $mdUtil.debounce(growTextarea, 1);

      function pipelineListener(value) {
        onChangeTextarea();
        return value;
      }

      if (ngModelCtrl) {
        ngModelCtrl.$formatters.push(pipelineListener);
        ngModelCtrl.$viewChangeListeners.push(pipelineListener);
      } else {
        onChangeTextarea();
      }
      element.on('keydown input', onChangeTextarea);

      if (isNaN(min_rows)) {
        element.attr('rows', '1');

        element.on('scroll', onScroll);
      }

      angular.element($window).on('resize', onChangeTextarea);

      scope.$on('$destroy', function() {
        angular.element($window).off('resize', onChangeTextarea);
      });

      function growTextarea() {
        // sets the md-input-container height to avoid jumping around
        container.style.height = container.offsetHeight + 'px';

        // temporarily disables element's flex so its height 'runs free'
        element.addClass('md-no-flex');

        if (isNaN(min_rows)) {
          node.style.height = "auto";
          node.scrollTop = 0;
          var height = getHeight();
          if (height) node.style.height = height + 'px';
        } else {
          node.setAttribute("rows", 1);

          if (!lineHeight) {
            node.style.minHeight = '0';

            lineHeight = element.prop('clientHeight');

            node.style.minHeight = null;
          }

          var rows = Math.max(min_rows, Math.round(node.scrollHeight / lineHeight));
          node.setAttribute("rows", rows);
        }

        // reset everything back to normal
        element.removeClass('md-no-flex');
        container.style.height = 'auto';
      }

      function getHeight() {
        var line = node.scrollHeight - node.offsetHeight;
        return node.offsetHeight + (line > 0 ? line : 0);
      }

      function onScroll(e) {
        node.scrollTop = 0;
        // for smooth new line adding
        var line = node.scrollHeight - node.offsetHeight;
        var height = node.offsetHeight + line;
        node.style.height = height + 'px';
      }
    }
  }
}

function mdMaxlengthDirective($animate) {
  return {
    restrict: 'A',
    require: ['ngModel', '^mdInputContainer'],
    link: postLink
  };

  function postLink(scope, element, attr, ctrls) {
    var maxlength;
    var ngModelCtrl = ctrls[0];
    var containerCtrl = ctrls[1];
    var charCountEl = angular.element('
');
    var input = angular.element(containerCtrl.element[0].querySelector('[md-maxlength]'));

    // Stop model from trimming. This makes it so whitespace
    // over the maxlength still counts as invalid.
    attr.$set('ngTrim', 'false');

    var ngMessagesSelectors = [
      'ng-messages',
      'data-ng-messages',
      'x-ng-messages',
      '[ng-messages]',
      '[data-ng-messages]',
      '[x-ng-messages]'
    ];

    var ngMessages = containerCtrl.element[0].querySelector(ngMessagesSelectors.join(','));

    // If we have an ngMessages container, put the counter at the top; otherwise, put it after the
    // input so it will be positioned properly in the SCSS
    if (ngMessages) {
      angular.element(ngMessages).prepend(charCountEl);
    } else {
      input.after(charCountEl);
    }

    ngModelCtrl.$formatters.push(renderCharCount);
    ngModelCtrl.$viewChangeListeners.push(renderCharCount);
    element.on('input keydown', function() {
      renderCharCount(); //make sure it's called with no args
    });

    scope.$watch(attr.mdMaxlength, function(value) {
      maxlength = value;
      if (angular.isNumber(value) && value > 0) {
        if (!charCountEl.parent().length) {
          $animate.enter(charCountEl, containerCtrl.element, input);
        }
        renderCharCount();
      } else {
        $animate.leave(charCountEl);
      }
    });

    ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
      if (!angular.isNumber(maxlength) || maxlength < 0) {
        return true;
      }
      return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
    };

    function renderCharCount(value) {
      // Force the value into a string since it may be a number,
      // which does not have a length property.
      charCountEl.text(String(element.val() || value || '').length + '/' + maxlength);
      return value;
    }
  }
}

function placeholderDirective($log) {
  return {
    restrict: 'A',
    require: '^^?mdInputContainer',
    priority: 200,
    link: postLink
  };

  function postLink(scope, element, attr, inputContainer) {
    // If there is no input container, just return
    if (!inputContainer) return;

    // Add a placeholder class so we can target it in the CSS
    inputContainer.setHasPlaceholder(true);

    var label = inputContainer.element.find('label');
    var hasNoFloat = angular.isDefined(inputContainer.element.attr('md-no-float'));

    // If we have a label, or they specify the md-no-float attribute, just return
    if ((label && label.length) || hasNoFloat) return;

    // Otherwise, grab/remove the placeholder
    var placeholderText = attr.placeholder;
    element.removeAttr('placeholder');

    // And add the placeholder text as a separate label
    if (inputContainer.input && inputContainer.input[0].nodeName != 'MD-SELECT') {
      var placeholder = '' + placeholderText + '';

      inputContainer.element.addClass('md-icon-float');
      inputContainer.element.prepend(placeholder);
    }
  }
}

function ngMessagesDirective() {
  return {
    restrict: 'EA',
    link: postLink,

    // This is optional because we don't want target *all* ngMessage instances, just those inside of
    // mdInputContainer.
    require: '^^?mdInputContainer'
  };

  function postLink(scope, element, attr, inputContainer) {
    // If we are not a child of an input container, don't do anything
    if (!inputContainer) return;

    // Tell our parent input container we have messages so we can set the proper classes
    inputContainer.setHasMessages(true);

    // When destroyed, inform our input container
    scope.$on('$destroy', function() {
      inputContainer.setHasMessages(false);
    });
  }
}
<commit_msg>Renders character count on keyup See http://codepen.io/Anaphase/pen/KdWPjy for details.<commit_after>/**
 * @ngdoc module
 * @name material.components.input
 */

angular.module('material.components.input', [
  'material.core'
])
  .directive('mdInputContainer', mdInputContainerDirective)
  .directive('label', labelDirective)
  .directive('input', inputTextareaDirective)
  .directive('textarea', inputTextareaDirective)
  .directive('mdMaxlength', mdMaxlengthDirective)
  .directive('placeholder', placeholderDirective)
  .directive('ngMessages', ngMessagesDirective);

/**
 * @ngdoc directive
 * @name mdInputContainer
 * @module material.components.input
 *
 * @restrict E
 *
 * @description
 * `<md-input-container>` is the parent of any input or textarea element.
 *
 * Input and textarea elements will not behave properly unless the md-input-container
 * parent is provided.
 *
 * @param md-is-error {expression=} When the given expression evaluates to true, the input container
 *   will go into error state. Defaults to erroring if the input has been touched and is invalid.
 * @param md-no-float {boolean=} When present, placeholders will not be converted to floating
 *   labels.
 *
 * @usage
 * <hljs lang="html">
 *
 * <md-input-container>
 *   Username
 *   <input type="text" ng-model="user.name">
 * </md-input-container>
 *
 * <md-input-container>
 *   Description
 *   <textarea ng-model="user.description"></textarea>
 * </md-input-container>
 *
 * </hljs>
 */
function mdInputContainerDirective($mdTheming, $parse) {
  return {
    restrict: 'E',
    link: postLink,
    controller: ContainerCtrl
  };

  function postLink(scope, element, attr) {
    $mdTheming(element);
  }

  function ContainerCtrl($scope, $element, $attrs) {
    var self = this;

    self.isErrorGetter = $attrs.mdIsError && $parse($attrs.mdIsError);

    self.delegateClick = function() {
      self.input.focus();
    };
    self.element = $element;
    self.setFocused = function(isFocused) {
      $element.toggleClass('md-input-focused', !!isFocused);
    };
    self.setHasValue = function(hasValue) {
      $element.toggleClass('md-input-has-value', !!hasValue);
    };
    self.setHasMessages = function(hasMessages) {
      $element.toggleClass('md-input-has-messages', !!hasMessages);
    };
    self.setHasPlaceholder = function(hasPlaceholder) {
      $element.toggleClass('md-input-has-placeholder', !!hasPlaceholder);
    };
    self.setInvalid = function(isInvalid) {
      $element.toggleClass('md-input-invalid', !!isInvalid);
    };
    $scope.$watch(function() {
      return self.label && self.input;
    }, function(hasLabelAndInput) {
      if (hasLabelAndInput && !self.label.attr('for')) {
        self.label.attr('for', self.input.attr('id'));
      }
    });
  }
}

function labelDirective() {
  return {
    restrict: 'E',
    require: '^?mdInputContainer',
    link: function(scope, element, attr, containerCtrl) {
      if (!containerCtrl || attr.mdNoFloat) return;

      containerCtrl.label = element;
      scope.$on('$destroy', function() {
        containerCtrl.label = null;
      });
    }
  };
}

/**
 * @ngdoc directive
 * @name mdInput
 * @restrict E
 * @module material.components.input
 *
 * @description
 * Use the `<input>` or the  `<textarea>` as a child of an `<md-input-container>`.
 *
 * @param {number=} md-maxlength The maximum number of characters allowed in this input. If this is
 *   specified, a character counter will be shown underneath the input.

 *   The purpose of **`md-maxlength`** is exactly to show the max length counter text. If you don't
 *   want the counter text and only need "plain" validation, you can use the "simple" `ng-maxlength`
 *   or maxlength attributes.
 * @param {string=} aria-label Aria-label is required when no label is present.  A warning message
 *   will be logged in the console if not present.
 * @param {string=} placeholder An alternative approach to using aria-label when the label is not
 *   PRESENT. The placeholder text is copied to the aria-label attribute.
 * @param md-no-autogrow {boolean=} When present, textareas will not grow automatically.
 *
 * @usage
 * <hljs lang="html">
 * <md-input-container>
 *   Color
 *   <input type="text" ng-model="color" required md-maxlength="10">
 * </md-input-container>
 * </hljs>
 *
With Errors

 *
 * <hljs lang="html">
 * <form name="userForm">
 *   <md-input-container>
 *     Last Name
 *     <input name="lastName" ng-model="lastName" required md-maxlength="10" minlength="4">
 *

 *
This is required!

 *
That's too long!

 *
That's too short!

 *

 *   </md-input-container>
 *   <md-input-container>
 *     Biography
 *     <textarea name="bio" ng-model="biography" required md-maxlength="150"></textarea>
 *

 *
This is required!

 *
That's too long!

 *

 *   </md-input-container>
 *   <md-input-container>
 *     <input aria-label='title' ng-model='title'>
 *   </md-input-container>
 *   <md-input-container>
 *     <input placeholder='title' ng-model='title'>
 *   </md-input-container>
 * </form>
 * </hljs>
 *
 * Requires [ngMessages](https://docs.angularjs.org/api/ngMessages).
 * Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).
 *
 */

function inputTextareaDirective($mdUtil, $window, $mdAria) {
  return {
    restrict: 'E',
    require: ['^?mdInputContainer', '?ngModel'],
    link: postLink
  };

  function postLink(scope, element, attr, ctrls) {

    var containerCtrl = ctrls[0];
    var hasNgModel = !!ctrls[1];
    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
    var isReadonly = angular.isDefined(attr.readonly);

    if (!containerCtrl) return;
    if (containerCtrl.input) {
      throw new Error("<md-input-container> can only have *one* <input>, <textarea> or <md-select> child element!");
    }
    containerCtrl.input = element;

    if (!containerCtrl.label) {
      $mdAria.expect(element, 'aria-label', element.attr('placeholder'));
    }

    element.addClass('md-input');
    if (!element.attr('id')) {
      element.attr('id', 'input_' + $mdUtil.nextUid());
    }

    if (element[0].tagName.toLowerCase() === 'textarea') {
      setupTextarea();
    }

    // If the input doesn't have an ngModel, it may have a static value. For that case,
    // we have to do one initial check to determine if the container should be in the
    // "has a value" state.
    if (!hasNgModel) {
      inputCheckValue();
    }

    var isErrorGetter = containerCtrl.isErrorGetter || function() {
        return ngModelCtrl.$invalid && ngModelCtrl.$touched;
      };
    scope.$watch(isErrorGetter, containerCtrl.setInvalid);

    ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
    ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);

    element.on('input', inputCheckValue);

    if (!isReadonly) {
      element
        .on('focus', function(ev) {
          containerCtrl.setFocused(true);
        })
        .on('blur', function(ev) {
          containerCtrl.setFocused(false);
          inputCheckValue();
        });

    }

    //ngModelCtrl.$setTouched();
    //if( ngModelCtrl.$invalid ) containerCtrl.setInvalid();

    scope.$on('$destroy', function() {
      containerCtrl.setFocused(false);
      containerCtrl.setHasValue(false);
      containerCtrl.input = null;
    });

    /**
     *
     */
    function ngModelPipelineCheckValue(arg) {
      containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(arg));
      return arg;
    }

    function inputCheckValue() {
      // An input's value counts if its length > 0,
      // or if the input's validity state says it has bad input (eg string in a number input)
      containerCtrl.setHasValue(element.val().length > 0 || (element[0].validity || {}).badInput);
    }

    function setupTextarea() {
      if (angular.isDefined(element.attr('md-no-autogrow'))) {
        return;
      }

      var node = element[0];
      var container = containerCtrl.element[0];

      var min_rows = NaN;
      var lineHeight = null;
      // can't check if height was or not explicity set,
      // so rows attribute will take precedence if present
      if (node.hasAttribute('rows')) {
        min_rows = parseInt(node.getAttribute('rows'));
      }

      var onChangeTextarea = $mdUtil.debounce(growTextarea, 1);

      function pipelineListener(value) {
        onChangeTextarea();
        return value;
      }

      if (ngModelCtrl) {
        ngModelCtrl.$formatters.push(pipelineListener);
        ngModelCtrl.$viewChangeListeners.push(pipelineListener);
      } else {
        onChangeTextarea();
      }
      element.on('keydown input', onChangeTextarea);

      if (isNaN(min_rows)) {
        element.attr('rows', '1');

        element.on('scroll', onScroll);
      }

      angular.element($window).on('resize', onChangeTextarea);

      scope.$on('$destroy', function() {
        angular.element($window).off('resize', onChangeTextarea);
      });

      function growTextarea() {
        // sets the md-input-container height to avoid jumping around
        container.style.height = container.offsetHeight + 'px';

        // temporarily disables element's flex so its height 'runs free'
        element.addClass('md-no-flex');

        if (isNaN(min_rows)) {
          node.style.height = "auto";
          node.scrollTop = 0;
          var height = getHeight();
          if (height) node.style.height = height + 'px';
        } else {
          node.setAttribute("rows", 1);

          if (!lineHeight) {
            node.style.minHeight = '0';

            lineHeight = element.prop('clientHeight');

            node.style.minHeight = null;
          }

          var rows = Math.max(min_rows, Math.round(node.scrollHeight / lineHeight));
          node.setAttribute("rows", rows);
        }

        // reset everything back to normal
        element.removeClass('md-no-flex');
        container.style.height = 'auto';
      }

      function getHeight() {
        var line = node.scrollHeight - node.offsetHeight;
        return node.offsetHeight + (line > 0 ? line : 0);
      }

      function onScroll(e) {
        node.scrollTop = 0;
        // for smooth new line adding
        var line = node.scrollHeight - node.offsetHeight;
        var height = node.offsetHeight + line;
        node.style.height = height + 'px';
      }
    }
  }
}

function mdMaxlengthDirective($animate) {
  return {
    restrict: 'A',
    require: ['ngModel', '^mdInputContainer'],
    link: postLink
  };

  function postLink(scope, element, attr, ctrls) {
    var maxlength;
    var ngModelCtrl = ctrls[0];
    var containerCtrl = ctrls[1];
    var charCountEl = angular.element('
');
    var input = angular.element(containerCtrl.element[0].querySelector('[md-maxlength]'));

    // Stop model from trimming. This makes it so whitespace
    // over the maxlength still counts as invalid.
    attr.$set('ngTrim', 'false');

    var ngMessagesSelectors = [
      'ng-messages',
      'data-ng-messages',
      'x-ng-messages',
      '[ng-messages]',
      '[data-ng-messages]',
      '[x-ng-messages]'
    ];

    var ngMessages = containerCtrl.element[0].querySelector(ngMessagesSelectors.join(','));

    // If we have an ngMessages container, put the counter at the top; otherwise, put it after the
    // input so it will be positioned properly in the SCSS
    if (ngMessages) {
      angular.element(ngMessages).prepend(charCountEl);
    } else {
      input.after(charCountEl);
    }

    ngModelCtrl.$formatters.push(renderCharCount);
    ngModelCtrl.$viewChangeListeners.push(renderCharCount);
    element.on('input keydown keyup', function() {
      renderCharCount(); //make sure it's called with no args
    });

    scope.$watch(attr.mdMaxlength, function(value) {
      maxlength = value;
      if (angular.isNumber(value) && value > 0) {
        if (!charCountEl.parent().length) {
          $animate.enter(charCountEl, containerCtrl.element, input);
        }
        renderCharCount();
      } else {
        $animate.leave(charCountEl);
      }
    });

    ngModelCtrl.$validators['md-maxlength'] = function(modelValue, viewValue) {
      if (!angular.isNumber(maxlength) || maxlength < 0) {
        return true;
      }
      return ( modelValue || element.val() || viewValue || '' ).length <= maxlength;
    };

    function renderCharCount(value) {
      // Force the value into a string since it may be a number,
      // which does not have a length property.
      charCountEl.text(String(element.val() || value || '').length + '/' + maxlength);
      return value;
    }
  }
}

function placeholderDirective($log) {
  return {
    restrict: 'A',
    require: '^^?mdInputContainer',
    priority: 200,
    link: postLink
  };

  function postLink(scope, element, attr, inputContainer) {
    // If there is no input container, just return
    if (!inputContainer) return;

    // Add a placeholder class so we can target it in the CSS
    inputContainer.setHasPlaceholder(true);

    var label = inputContainer.element.find('label');
    var hasNoFloat = angular.isDefined(inputContainer.element.attr('md-no-float'));

    // If we have a label, or they specify the md-no-float attribute, just return
    if ((label && label.length) || hasNoFloat) return;

    // Otherwise, grab/remove the placeholder
    var placeholderText = attr.placeholder;
    element.removeAttr('placeholder');

    // And add the placeholder text as a separate label
    if (inputContainer.input && inputContainer.input[0].nodeName != 'MD-SELECT') {
      var placeholder = '' + placeholderText + '';

      inputContainer.element.addClass('md-icon-float');
      inputContainer.element.prepend(placeholder);
    }
  }
}

function ngMessagesDirective() {
  return {
    restrict: 'EA',
    link: postLink,

    // This is optional because we don't want target *all* ngMessage instances, just those inside of
    // mdInputContainer.
    require: '^^?mdInputContainer'
  };

  function postLink(scope, element, attr, inputContainer) {
    // If we are not a child of an input container, don't do anything
    if (!inputContainer) return;

    // Tell our parent input container we have messages so we can set the proper classes
    inputContainer.setHasMessages(true);

    // When destroyed, inform our input container
    scope.$on('$destroy', function() {
      inputContainer.setHasMessages(false);
    });
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';

class PostsIndex extends Component {
	//a lifecycle method - only called when the element is about to be mounted on the DOM
	componentWillMount() {
		this.props.fetchPosts();
	}

	render() {
		return (

				<Link to='/posts/new' className='btn btn-primary'>
					Add a Post
				</Link>

				List of Blog Posts

		);
	}
}

export default connect(null, { fetchPosts })(PostsIndex);<commit_msg>list of post titles<commit_after>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';

class PostsIndex extends Component {
	//a lifecycle method - only called when the element is about to be mounted on the DOM
	componentWillMount() {
		this.props.fetchPosts();
	}

	renderPosts() {
		return this.props.posts.map((post) => {
			return (

					{post.categories}
					{post.title}

			);
		})
	}

	render() {
		return (

				<Link to='/posts/new' className='btn btn-primary'>
					Add a Post
				</Link>

Posts

				{this.renderPosts()}

		);
	}
}

function mapStateToProps(state) {
	return {
		posts: state.posts.all
	}
}

export default connect(mapStateToProps, { fetchPosts })(PostsIndex);<|endoftext|>


<commit_before>// @flow

import React from 'react'

import Waw from '../../lib/waw/waw'
import { Waveforms } from '../waveforms/Waveforms'
import { find } from '../../lib/helpers'

type propTypes = {
  keys: any[],
  onKeyClick: any,
  waveforms: any[],
  onWaveformClick: any,
  currentWaveform: string,
  pressedKeys: string[],
  addKey: any,
  removeKey: any,
  removeAllKeys: any,
}

class Synth extends React.Component {
  constructor(props: propTypes) {
    super()

    this.props = props
    this.waw = new Waw()
    this.waw.init()
  }

  componentWillMount() {
    global.window.addEventListener('keydown', this.handleKeyDown, false)
    global.window.addEventListener('keyup', this.handleKeyUp, false)
    global.window.addEventListener('blur', this.removeAllKeys, false)
  }

  componentWillUnmount() {
    global.window.removeEventListener('keydown', this.handleKeyDown, false)
    global.window.removeEventListener('keyup', this.handleKeyUp, false)
    global.window.removeEventListener('blur', this.removeAllKeys, false)
  }

  props: propTypes
  waw: Waw

  canFindKey = (key: string): boolean =>
    find(this.props.pressedKeys, key)

  handleKeyDown = ({ key }: {key: string}) => {
    if (this.canFindKey(key)) return
    if (!this.props.keys.some(pressedKey => pressedKey.keyPress === key)) return
    const { frequency } = this.props.keys.filter(k => k.keyPress === key)[0]

    const oscillator = this.waw.createNewOscillator({
      key,
      frequency,
      waveform: this.props.currentWaveform,
    })

    this.props.addKey(key)
  }

  handleKeyUp = ({ key }: {key: string}) => {
    this.waw.deleteOscillator(key)
    this.props.removeKey(key)
  }

  removeAllKeys = () =>
    this.props.removeAllKeys()

  render() {
    return (

        <Waveforms
          waveforms={this.props.waveforms}
          currentWaveform={this.props.currentWaveform}
          onWaveformClick={this.props.onWaveformClick}
        />

    )
  }
}

export default Synth
<commit_msg>computer keyboard HOC<commit_after>// @flow

import React from 'react'

import Waw from '../../lib/waw/waw'
import { Waveforms } from '../waveforms/Waveforms'
import { find } from '../../lib/helpers'
import { KeyType } from '../../lib/types/types'

type propTypes = {
  keys: any[],
  onKeyClick: any,
  waveforms: any[],
  onWaveformClick: any,
  currentWaveform: string,
  pressedKeys: string[],
  addKey: any,
  removeKey: any,
  removeAllKeys: any,
}

function ComputerKeyboardEnhancer(WrappedComponent) {
  return class ComputerKeyboard extends WrappedComponent {
    constructor(props: propTypes) {
      super(props)
      this.props = props

      this.waw = new Waw()
      this.waw.init()
    }

    componentWillMount() {
      global.window.addEventListener('keydown', this.handleKeyDown, false)
      global.window.addEventListener('keyup', this.handleKeyUp, false)
      global.window.addEventListener('blur', this.removeAllKeys, false)
    }

    componentWillUnmount() {
      global.window.removeEventListener('keydown', this.handleKeyDown, false)
      global.window.removeEventListener('keyup', this.handleKeyUp, false)
      global.window.removeEventListener('blur', this.removeAllKeys, false)
    }

    props: propTypes
    waw: Waw

    keyIsPressed = (key: string): boolean =>
      find(this.props.pressedKeys, key)

    filterByKeyPress = (list: any[], key: string): KeyType =>
      list.filter(k => k.keyPress === key)[0]

    keyIsNotAssigned = (key: any): boolean =>
      !this.props.keys.some(k => k.keyPress === key)

    handleKeyDown = ({ key }: {key: string}) => {
      if (this.keyIsPressed(key) || this.keyIsNotAssigned(key)) return

      const { frequency } = this.filterByKeyPress(this.props.keys, key)

      const oscillator = this.waw.createNewOscillator({
        key,
        frequency,
        waveform: this.props.currentWaveform,
      })

      this.props.addKey(key)
    }

    handleKeyUp = ({ key }: {key: string}) => {
      this.waw.deleteOscillator(key)
      this.props.removeKey(key)
    }

    removeAllKeys = () => {
      this.props.removeAllKeys()
      this.waw.deleteAllOscillators()
    }

    render() {
      return super.render()
    }
  }
}

class Synth extends React.Component {
  constructor(props) {
    super(props)
    this.props = props
  }

  render() {
    return (

      {this.props.pressedKeys.map(k => {

k

      })}
      <Waveforms
        waveforms={this.props.waveforms}
        currentWaveform={this.props.currentWaveform}
        onWaveformClick={this.props.onWaveformClick}
      />

)
  }
}

export default ComputerKeyboardEnhancer(Synth)
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes, { connect, withViewport, storeUtil } from '../PropTypes.js';
import moment from 'moment';
import cn from 'classnames';
import Immutable from 'immutable';
import http from '../../utils/httpClient.js';

import TabbedContainer from '../controls/TabbedContainer.js';

import { OwnClubId } from '../../models/ClubModel.js';
import { editMatchPlayers } from '../../actions/matchActions.js';

import DivisionRanking from './DivisionRanking.js';
import TeamOverview from './TeamOverview.js';
import TeamHeader, { TeamTabTitle } from './TeamHeader.js';
import { Icon, TrophyIcon, Badgy } from '../controls';
import ButtonStack from '../controls/ButtonStack.js';
import PlayersCardGallery from '../players/PlayersCardGallery.js';
import MatchesTable from '../matches/MatchesTable.js';

export function getFirstOrLastMatches(allMatchesToCome, filter) {
  const firstMatches = allMatchesToCome.filter(x => x.date.month() >= 7);
  const lastMatches = allMatchesToCome.filter(x => x.date.month() < 7);
  if (filter === 'first' && firstMatches.length !== 0) {
    return {
      matches: firstMatches,
      hasMore: lastMatches.length !== 0
    };
  }
  return {
    matches: lastMatches,
    hasMore: firstMatches.length !== 0
  };
}
export function getFirstOrLast() {
  const today = moment();
  return today.month() >= 7 && !(today.month() === 11 && today.date() > 20) ? 'first' : 'last';
}

@connect(state => {
  return {
    config: state.config,
    user: state.user,
    players: state.players,
    clubs: state.clubs,
    matches: state.matches,
    teams: state.teams,
  };
}, {editMatchPlayers})
@withViewport
export default class Teams extends Component {
  static contextTypes = PropTypes.contextTypes;
  static propTypes = {
    config: PropTypes.object.isRequired,
    user: PropTypes.UserModel.isRequired,
    matches: PropTypes.MatchModelList.isRequired,
    teams: PropTypes.TeamModelList.isRequired,
    viewport: PropTypes.viewport,
    editMatchPlayers: PropTypes.func.isRequired,

    params: PropTypes.shape({
      tabKey: PropTypes.string,
      competition: PropTypes.oneOf(['vttl', 'sporta']).isRequired
    }).isRequired,
  }

  constructor(props) {
    super(props);
    this.state = {
      view: 'main',
      matchesFilter: getFirstOrLast(),
      editMode: false,
      tablePlayers: this._getAlreadyPicked(props),
      tableMatches: [],
    };
  }
  componentWillReceiveProps(nextProps) {
    // TODO: BUG: if you resize the screen (or something else happens like a broadcast), already picked players are lost
    this.setState({tablePlayers: this._getAlreadyPicked(nextProps)});
  }
  _getAlreadyPicked(props) {
    if (props.user.canEditMatchesOrIsCaptain()) {
      var alreadyPicked = [];
      props.matches.forEach(match => {
        const team = match.getTeam();
        const formation = match.getPlayerFormation(this._getPlayerStatus());
        const matchPicked = formation.map(frm => Object.assign({}, frm, {matchId: match.id})).toArray();
        Array.prototype.push.apply(alreadyPicked, matchPicked);
      });
      return alreadyPicked;
    }
    return [];
  }

  _getCompetition() {
    return this.props.params.competition[0].toUpperCase() + this.props.params.competition.substr(1);
  }
  getDefaultTeam() {
    if (this.props.user.playerId) {
      const yourTeam = this.props.user.getTeams().find(team => team.competition === this._getCompetition());
      if (yourTeam) {
        return yourTeam.teamCode;
      }
    }
    return 'A';
  }

  _downloadExcel() {
    if (this.state.isDownloading) {
      return;
    }
    this.setState({isDownloading: true});
    http.download.teamsExcel(this.context.t('teamCalendar.downloadExcelFileName'))
      .catch(err => {
        console.error('err', err);
      })
      .then(() => this.setState({isDownloading: false}));
  }
  _isSmall() {
    return this.props.viewport.width < 700;
  }

  _renderTabContent(teamCode) {
    const team = this.props.teams.find(t => t.teamCode === teamCode && t.competition === this._getCompetition());
    if (!team) {
      return null;
    }

    var viewsConfig = [{
      key: 'main',
      text: this.context.t('teamCalendar.viewMain'),
    }, {
      key: 'matches',
      text: this.context.t('teamCalendar.viewMatches')
    }, {
      key: 'ranking',
      text: this.context.t('teamCalendar.viewRanking')
    }, {
      key: 'players',
      text: this.context.t('match.tabs.players')
    }];
    if (this.props.user.playerId && this.props.viewport.width > 1100) {
      viewsConfig.splice(2, 0, {
        key: 'matchesTable',
        text: this.context.t('teamCalendar.playersPicked')
      });
    }

    const {matches} = getFirstOrLastMatches(team.getMatches(), this.state.matchesFilter);
    return (

          <ButtonStack
            config={viewsConfig}
            small={this._isSmall()}
            activeView={this.state.view}
            onClick={view => this.setState({view})} />

          {this.state.view.startsWith('matches') && this.props.user.canEditMatchesOrIsCaptain() && matches.some(m => !m.isSyncedWithFrenoy) ? (

              {this.state.editMode && this.state.view != 'matches' ? (
                <button className="btn btn-danger" style={{marginRight: 5}} onClick={::this._saveAndBlockAll}>
                  {this.context.t('match.plys.saveAndBlockAll')}
                </button>
              ) : null}
              <button onClick={() => this.setState({editMode: !this.state.editMode})} className="btn btn-default">
                <Icon fa="fa fa-pencil-square-o" />
              </button>

          ) : null}

          {this.props.user.playerId ? (
            <button onClick={::this._downloadExcel} className="btn btn-default pull-right">
              <Icon fa={this.state.isDownloading ? 'fa fa-spinner fa-pulse' : 'fa fa-file-excel-o'} />
            </button>
          ) : null}

            <button className="btn btn-default">{this.context.t('teamCalendar.frenoyResults')}</button>

        <TeamHeader team={team} t={this.context.t} showRanking={!this._isSmall()} />
        {this._renderTabViewContent(team, matches)}

    );
  }

  _getPlayerStatus() {
    return this.props.user.canManageTeams() ? 'Major' : 'Captain';
  }
  _saveAndBlockAll() {
    if (!this.state.tableMatches.length) {
      return;
    }

    _(this.state.tablePlayers)
    .filter(tb => this.state.tableMatches.indexOf(tb.matchId) !== -1)
    .groupBy('matchId')
    .forOwn((plyInfos, matchId) => {
      this.props.editMatchPlayers({
        matchId: matchId,
        playerIds: plyInfos.map(x => x.id),
        blockAlso: true,
        newStatus: this._getPlayerStatus()
      }, false)
      .catch(e => console.error('saveAndBlockAll', e));
    });
    this.setState({tableMatches: []});
  }
  _renderTabViewContent(team, matches) {
    switch (this.state.view) {
    case 'main':
      return <TeamOverview team={team} t={this.context.t} user={this.props.user} small={this._isSmall()} />;
    case 'matches':
    case 'matchesTable':
      return (

          <MatchesTable
            matches={matches}
            allowOpponentOnly
            striped
            user={this.props.user}
            editMode={this.state.editMode}

            tableForm={this.state.view === 'matchesTable'}
            team={team}
            tablePlayers={this.state.tablePlayers}
            onTablePlayerSelect={(plyInfos, match) => this.setState({tablePlayers: plyInfos, tableMatches: this.state.tableMatches.concat([match.id])})}
          />

          <SwitchBetweenFirstAndLastRoundButton setState={::this.setState} matchesFilter={this.state.matchesFilter} t={this.context.t} />

      );
    case 'ranking':
      return <DivisionRanking team={team} t={this.context.t} />;
    case 'players':
      return <PlayersCardGallery players={Immutable.List(team.getPlayers().map(x => x.player))} />;
    default:
      return
;
    }
  }

  render() {
    const t = this.context.t;
    const tabConfig = this.props.teams.filter(team => team.competition === this._getCompetition()).toArray().map(team => {
      return {
        key: team.teamCode,
        title: '',
        headerChildren: <TeamTabTitle team={team} t={t} showRanking={this._isSmall()} />,
      };
    });

    return (

        <TabbedContainer
          params={this.props.params}
          defaultTabKey={this.getDefaultTeam()}
          tabKeys={tabConfig}
          tabRenderer={::this._renderTabContent}
          route={{base: t.route('teams', {competition: this.props.params.competition})}}
          widthTreshold={750} />

    );
  }
}

export const SwitchBetweenFirstAndLastRoundButton = ({t, setState, matchesFilter}) => (

    <button
      className="btn btn-default"
      onClick={() => setState({matchesFilter: matchesFilter === 'first' ? 'last' : 'first'})}>
      <Icon fa="fa fa-chevron-circle-down" />

      {t('comp.round' + (matchesFilter === 'first' ? 'Back' : 'First'))}
    </button>

);
<commit_msg>Teams: default opened tab of team where player is not 'reserve'<commit_after>import React, { Component } from 'react';
import PropTypes, { connect, withViewport, storeUtil } from '../PropTypes.js';
import moment from 'moment';
import cn from 'classnames';
import Immutable from 'immutable';
import http from '../../utils/httpClient.js';

import TabbedContainer from '../controls/TabbedContainer.js';

import { OwnClubId } from '../../models/ClubModel.js';
import { editMatchPlayers } from '../../actions/matchActions.js';

import DivisionRanking from './DivisionRanking.js';
import TeamOverview from './TeamOverview.js';
import TeamHeader, { TeamTabTitle } from './TeamHeader.js';
import { Icon, TrophyIcon, Badgy } from '../controls';
import ButtonStack from '../controls/ButtonStack.js';
import PlayersCardGallery from '../players/PlayersCardGallery.js';
import MatchesTable from '../matches/MatchesTable.js';

export function getFirstOrLastMatches(allMatchesToCome, filter) {
  const firstMatches = allMatchesToCome.filter(x => x.date.month() >= 7);
  const lastMatches = allMatchesToCome.filter(x => x.date.month() < 7);
  if (filter === 'first' && firstMatches.length !== 0) {
    return {
      matches: firstMatches,
      hasMore: lastMatches.length !== 0
    };
  }
  return {
    matches: lastMatches,
    hasMore: firstMatches.length !== 0
  };
}
export function getFirstOrLast() {
  const today = moment();
  return today.month() >= 7 && !(today.month() === 11 && today.date() > 20) ? 'first' : 'last';
}

@connect(state => {
  return {
    config: state.config,
    user: state.user,
    players: state.players,
    clubs: state.clubs,
    matches: state.matches,
    teams: state.teams,
  };
}, {editMatchPlayers})
@withViewport
export default class Teams extends Component {
  static contextTypes = PropTypes.contextTypes;
  static propTypes = {
    config: PropTypes.object.isRequired,
    user: PropTypes.UserModel.isRequired,
    matches: PropTypes.MatchModelList.isRequired,
    teams: PropTypes.TeamModelList.isRequired,
    viewport: PropTypes.viewport,
    editMatchPlayers: PropTypes.func.isRequired,

    params: PropTypes.shape({
      tabKey: PropTypes.string,
      competition: PropTypes.oneOf(['vttl', 'sporta']).isRequired
    }).isRequired,
  }

  constructor(props) {
    super(props);
    this.state = {
      view: 'main',
      matchesFilter: getFirstOrLast(),
      editMode: false,
      tablePlayers: this._getAlreadyPicked(props),
      tableMatches: [],
    };
  }
  componentWillReceiveProps(nextProps) {
    // TODO: BUG: if you resize the screen (or something else happens like a broadcast), already picked players are lost
    this.setState({tablePlayers: this._getAlreadyPicked(nextProps)});
  }
  _getAlreadyPicked(props) {
    if (props.user.canEditMatchesOrIsCaptain()) {
      var alreadyPicked = [];
      props.matches.forEach(match => {
        const team = match.getTeam();
        const formation = match.getPlayerFormation(this._getPlayerStatus());
        const matchPicked = formation.map(frm => Object.assign({}, frm, {matchId: match.id})).toArray();
        Array.prototype.push.apply(alreadyPicked, matchPicked);
      });
      return alreadyPicked;
    }
    return [];
  }

  _getCompetition() {
    return this.props.params.competition[0].toUpperCase() + this.props.params.competition.substr(1);
  }
  getDefaultTeam() {
    if (this.props.user.playerId) {
      const yourTeams = this.props.user.getTeams().filter(team => team.competition === this._getCompetition());
      if (yourTeams.length === 0) {
        return 'A';
      }
      if (yourTeams.length === 1) {
        return yourTeams[0].teamCode;
      }
      const notReserve = yourTeams.find(t => t.getPlayers('standard').some(p => p.player.id === this.props.user.playerId));
      return notReserve ? notReserve.teamCode : yourTeams[0].teamCode;
    }
    return 'A';
  }

  _downloadExcel() {
    if (this.state.isDownloading) {
      return;
    }
    this.setState({isDownloading: true});
    http.download.teamsExcel(this.context.t('teamCalendar.downloadExcelFileName'))
      .catch(err => {
        console.error('err', err);
      })
      .then(() => this.setState({isDownloading: false}));
  }
  _isSmall() {
    return this.props.viewport.width < 700;
  }

  _renderTabContent(teamCode) {
    const team = this.props.teams.find(t => t.teamCode === teamCode && t.competition === this._getCompetition());
    if (!team) {
      return null;
    }

    var viewsConfig = [{
      key: 'main',
      text: this.context.t('teamCalendar.viewMain'),
    }, {
      key: 'matches',
      text: this.context.t('teamCalendar.viewMatches')
    }, {
      key: 'ranking',
      text: this.context.t('teamCalendar.viewRanking')
    }, {
      key: 'players',
      text: this.context.t('match.tabs.players')
    }];
    if (this.props.user.playerId && this.props.viewport.width > 1100) {
      viewsConfig.splice(2, 0, {
        key: 'matchesTable',
        text: this.context.t('teamCalendar.playersPicked')
      });
    }

    const {matches} = getFirstOrLastMatches(team.getMatches(), this.state.matchesFilter);
    return (

          <ButtonStack
            config={viewsConfig}
            small={this._isSmall()}
            activeView={this.state.view}
            onClick={view => this.setState({view})} />

          {this.state.view.startsWith('matches') && this.props.user.canEditMatchesOrIsCaptain() && matches.some(m => !m.isSyncedWithFrenoy) ? (

              {this.state.editMode && this.state.view != 'matches' ? (
                <button className="btn btn-danger" style={{marginRight: 5}} onClick={::this._saveAndBlockAll}>
                  {this.context.t('match.plys.saveAndBlockAll')}
                </button>
              ) : null}
              <button onClick={() => this.setState({editMode: !this.state.editMode})} className="btn btn-default">
                <Icon fa="fa fa-pencil-square-o" />
              </button>

          ) : null}

          {this.props.user.playerId ? (
            <button onClick={::this._downloadExcel} className="btn btn-default pull-right">
              <Icon fa={this.state.isDownloading ? 'fa fa-spinner fa-pulse' : 'fa fa-file-excel-o'} />
            </button>
          ) : null}

            <button className="btn btn-default">{this.context.t('teamCalendar.frenoyResults')}</button>

        <TeamHeader team={team} t={this.context.t} showRanking={!this._isSmall()} />
        {this._renderTabViewContent(team, matches)}

    );
  }

  _getPlayerStatus() {
    return this.props.user.canManageTeams() ? 'Major' : 'Captain';
  }
  _saveAndBlockAll() {
    if (!this.state.tableMatches.length) {
      return;
    }

    _(this.state.tablePlayers)
    .filter(tb => this.state.tableMatches.indexOf(tb.matchId) !== -1)
    .groupBy('matchId')
    .forOwn((plyInfos, matchId) => {
      this.props.editMatchPlayers({
        matchId: matchId,
        playerIds: plyInfos.map(x => x.id),
        blockAlso: true,
        newStatus: this._getPlayerStatus()
      }, false)
      .catch(e => console.error('saveAndBlockAll', e));
    });
    this.setState({tableMatches: []});
  }
  _renderTabViewContent(team, matches) {
    switch (this.state.view) {
    case 'main':
      return <TeamOverview team={team} t={this.context.t} user={this.props.user} small={this._isSmall()} />;
    case 'matches':
    case 'matchesTable':
      return (

          <MatchesTable
            matches={matches}
            allowOpponentOnly
            striped
            user={this.props.user}
            editMode={this.state.editMode}

            tableForm={this.state.view === 'matchesTable'}
            team={team}
            tablePlayers={this.state.tablePlayers}
            onTablePlayerSelect={(plyInfos, match) => this.setState({tablePlayers: plyInfos, tableMatches: this.state.tableMatches.concat([match.id])})}
          />

          <SwitchBetweenFirstAndLastRoundButton setState={::this.setState} matchesFilter={this.state.matchesFilter} t={this.context.t} />

      );
    case 'ranking':
      return <DivisionRanking team={team} t={this.context.t} />;
    case 'players':
      return <PlayersCardGallery players={Immutable.List(team.getPlayers().map(x => x.player))} />;
    default:
      return
;
    }
  }

  render() {
    const t = this.context.t;
    const tabConfig = this.props.teams.filter(team => team.competition === this._getCompetition()).toArray().map(team => {
      return {
        key: team.teamCode,
        title: '',
        headerChildren: <TeamTabTitle team={team} t={t} showRanking={this._isSmall()} />,
      };
    });

    return (

        <TabbedContainer
          params={this.props.params}
          defaultTabKey={this.getDefaultTeam()}
          tabKeys={tabConfig}
          tabRenderer={::this._renderTabContent}
          route={{base: t.route('teams', {competition: this.props.params.competition})}}
          widthTreshold={750} />

    );
  }
}

export const SwitchBetweenFirstAndLastRoundButton = ({t, setState, matchesFilter}) => (

    <button
      className="btn btn-default"
      onClick={() => setState({matchesFilter: matchesFilter === 'first' ? 'last' : 'first'})}>
      <Icon fa="fa fa-chevron-circle-down" />

      {t('comp.round' + (matchesFilter === 'first' ? 'Back' : 'First'))}
    </button>

);
<|endoftext|>


<commit_before>'use strict';

Connector.playerSelector = '.player';

Connector.trackArtSelector = '#currently-playing-album-cover img';

Connector.artistSelector = '#currently-playing-composer';

Connector.trackSelector = '.currently-playing .movement';

Connector.albumSelector = '#currently-playing-work';

Connector.currentTimeSelector = '.progress span[title="Current Time"]';

Connector.getTimeInfo = () => {
	let currentTime = Connector.getCurrentTime();
	let duration = Util.stringToSeconds(Util.getTextFromSelectors('#toggle-remainging span'));

	// check if it's actually remaining time
	if (duration < 0) {
		duration = currentTime - duration;
	}

	return { currentTime, duration };
};

Connector.playButtonSelector = '#player-play-pause img[title="Play"]';
<commit_msg>Update primephonic connector<commit_after>'use strict';

Connector.playerSelector = '.player';

Connector.trackArtSelector = '#currently-playing-album-cover img';

Connector.artistSelector = '#currently-playing-composer';

Connector.trackSelector = '.currently-playing .movement';

Connector.albumSelector = '#currently-playing-work';

Connector.currentTimeSelector = '.progress span[title="Current Time"]';

/*
 * The connector has toggleable remaining time/duration.
 * Either duration or remaining time is visible at the moment.
 * If duration is hidden, we calculate it ising remaining time.
 */
Connector.getTimeInfo = () => {
	let duration = Util.stringToSeconds(
		Util.getTextFromSelectors('.progress span[title="Total Time"]')
	);
	const currentTime = Connector.getCurrentTime();

	if (!duration) {
		const remainingTime = Math.abs(Util.stringToSeconds(
			Util.getTextFromSelectors('.progress span[title="Remaining Time"]')
		));
		duration = remainingTime + currentTime;
	}

	return { currentTime, duration };
};

Connector.playButtonSelector = '#player-play-pause img[title="Play"]';
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { Row, Col } from 'react-flexbox-grid/lib';
import FlatButton from 'material-ui/FlatButton';
import { List, ListItem } from 'material-ui/List';
import Divider from 'material-ui/Divider';
import ChevronRight from 'material-ui/svg-icons/navigation/chevron-right';

import SentimentVerySatisfied from 'material-ui/svg-icons/social/sentiment-very-satisfied';
import SentimentSatisfied from 'material-ui/svg-icons/social/sentiment-satisfied';
import SentimentNeutral from 'material-ui/svg-icons/social/sentiment-neutral';
import SentimentVeryDissatisfied from 'material-ui/svg-icons/social/sentiment-very-dissatisfied';

import {
    blue500,
    green500,
    red500
} from 'material-ui/styles/colors';

function getLeftIcon(i, length) {
    if (i === 0) return <SentimentVerySatisfied color={blue500} />;
    if (i > 0 && i <= 2) return <SentimentSatisfied color={green500} />;
    if (i === length - 1) return <SentimentVeryDissatisfied color={red500} />;

    return <SentimentNeutral color={'rgb(117, 117, 117)'} />;
}

class PlayersList extends Component {
    render() {
        const { playersByRank } = this.props;

        return (
            <Row>
                <Col xs={12} className="no-padding-left no-padding-right">
                    <FlatButton
                        label="Påmeldingsskjema"
                        primary
                        linkButton
                        href="http://goo.gl/forms/gsCOF58Ak0dBofiD3"
                        target="_blank"
                        className="margin"
                    />
                    <List>
                        {playersByRank.map((player, i) =>
                            (
                            <Link
                                key={player._id}
                                to={`/player/${player._id}`}
                            >
                                <Divider />
                                <ListItem
                                    primaryText={player.name}
                                    secondaryText={`${player.totalPoints} poeng`}
                                    leftIcon={

                                            {getLeftIcon(i, playersByRank.length)}

                                    }
                                    rightIcon={<ChevronRight />}
                                />
                            </Link>
                            )
                        )}
                    </List>
                </Col>
            </Row>
        );
    }
}

PlayersList.propTypes = {
    params: PropTypes.object.isRequired,
    playersByRank: PropTypes.array.isRequired
};

export default PlayersList;
<commit_msg>Disabled sign up button<commit_after>import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { Row, Col } from 'react-flexbox-grid/lib';
import FlatButton from 'material-ui/FlatButton';
import { List, ListItem } from 'material-ui/List';
import Divider from 'material-ui/Divider';
import ChevronRight from 'material-ui/svg-icons/navigation/chevron-right';

import SentimentVerySatisfied from 'material-ui/svg-icons/social/sentiment-very-satisfied';
import SentimentSatisfied from 'material-ui/svg-icons/social/sentiment-satisfied';
import SentimentNeutral from 'material-ui/svg-icons/social/sentiment-neutral';
import SentimentVeryDissatisfied from 'material-ui/svg-icons/social/sentiment-very-dissatisfied';

import {
    blue500,
    green500,
    red500
} from 'material-ui/styles/colors';

function getLeftIcon(i, length) {
    if (i === 0) return <SentimentVerySatisfied color={blue500} />;
    if (i > 0 && i <= 2) return <SentimentSatisfied color={green500} />;
    if (i === length - 1) return <SentimentVeryDissatisfied color={red500} />;

    return <SentimentNeutral color={'rgb(117, 117, 117)'} />;
}

class PlayersList extends Component {
    render() {
        const { playersByRank } = this.props;

        return (
            <Row>
                <Col xs={12} className="no-padding-left no-padding-right">
                    <FlatButton
                        label="Påmeldingsskjema"
                        primary
                        linkButton
                        href="http://goo.gl/forms/gsCOF58Ak0dBofiD3"
                        target="_blank"
                        className="margin"
                        disabled
                    />
                    <List>
                        {playersByRank.map((player, i) =>
                            (
                            <Link
                                key={player._id}
                                to={`/player/${player._id}`}
                            >
                                <Divider />
                                <ListItem
                                    primaryText={player.name}
                                    secondaryText={`${player.totalPoints} poeng`}
                                    leftIcon={

                                            {getLeftIcon(i, playersByRank.length)}

                                    }
                                    rightIcon={<ChevronRight />}
                                />
                            </Link>
                            )
                        )}
                    </List>
                </Col>
            </Row>
        );
    }
}

PlayersList.propTypes = {
    params: PropTypes.object.isRequired,
    playersByRank: PropTypes.array.isRequired
};

export default PlayersList;
<|endoftext|>


<commit_before>const ContentRegExpHandler = {
    ContentRegExpHandler(regExp) {
        this.regExp = regExp;
    },
    canHandle(message) {
        return this.regExp.test(message.content);
    }
};

module.exports = ContentRegExpHandler;
<commit_msg>ContentRegExpHandler now matches commands correctly<commit_after>const ContentRegExpHandler = {
    ContentRegExpHandler(regExp) {
        this.regExp = new RegExp(regExp.source + "($|\\s+)");
    },
    canHandle(message) {
        return this.regExp.test(message.content);
    }
};

module.exports = ContentRegExpHandler;
<|endoftext|>


<commit_before>const express = require('express')
const passport = require('passport')
const users = require('../models/users')

const PROVIDER = 'google'

const authentication = require('./authentication')
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy

// Helper function for loading secret keys mainly. If they aren't found, just
// crash
function loadEnv (envVar) {
  if (!process.env[envVar]) throw new Error(`Environment variable '${envVar}' not found`)
  return process.env[envVar]
}

const GOOGLE_CLIENT_ID = loadEnv('CLIENT')
const GOOGLE_CLIENT_SECRET = loadEnv('SECRET')

function convertToUser (profile) {
  return {
    authProvider: PROVIDER,
    authId: profile.id,
    displayName: profile.displayName,
    email: profile.emails && profile.emails.length > 0 ? profile.emails[0].value : null
    // oauthProfile: profile
  }
}

function applyToUser (profile, user) {
  if (profile.displayName) {
    user.displayName = profile.displayName
  }

  if (profile.emails && profile.emails.length > 0) {
    user.email = profile.emails[0].value
  }

  user.oathProfile = profile
  return user
}

module.exports.Strategy = () => {
  return new GoogleStrategy({
    clientID: GOOGLE_CLIENT_ID,
    clientSecret: GOOGLE_CLIENT_SECRET,
    callbackURL: `http://localhost:${process.env.PORT || 3000}/auth/google/callback`
  }, (accessToken, refreshToken, profile, done) => {
    // see if user already exists
    users.getById(profile.id)
      .then(user => {
        console.log('got a user by ID')
        if (!user) {
          return users.insert(profile.id, convertToUser(profile))
        } else {
          return users.update(applyToUser(profile, user))
        }
      })
      .then(user => {
        console.log('got a user, calling done function')
        console.log(user)
        done(null, user)
      })
      .catch(done)
  })
}

module.exports.GoogleRouter = () => {
  var router = express.Router()

  router.get('/signin/google', passport.authenticate(PROVIDER, {scope: ['email', 'profile']}))

  router.get('/auth/google/callback', passport.authenticate(PROVIDER, {scope: 'profile'}),
    authentication.backToRequestedUrl)

  return router
}

module.exports.Router = () => {
  var router = express.Router()

  router.get('/session', ({user, session}, res) => {
    res.json({user, session})
  })

  router.post('/signin', passport.authenticate(PROVIDER), (req, res) => {
    var response = {message: 'authenticated'}

    if (req.session.afterAuthRedirectTo) {
      response.redirectTo = req.session.afterAuthRedirectTo
      delete req.session.afterAuthRedirectTo
    }

    res.json(response)
  })

  // router.post('/signup', (req, res, next) => {
  //   var newUser = req.body

  //   // ensure new user has required properties
  //   if (!newUser || !newUser.email || !newUser.displayName || !newUser.password) {
  //     return next({status: 400, message: 'Email, display name, and password are all required.'})
  //   }

  //   newUser.authProvider = PROVIDER
  //   newUser.authId = newUser.email

  //   users.doesUserExist(PROVIDER, newUser.email)
  //     .then(alreadyRegistered => {
  //       if (alreadyRegistered) {
  //         throw new Error({status: 400, message: 'That email address is already registered.'})
  //       }

  //       return hashPassword(newUser)
  //     })
  //     .then(() => {
  //       return users.insert(newUser)
  //     })
  //     .then(() => {
  //       return loginUser(newUser, req)
  //     })
  //     .then(() => {
  //       res.status(201).json({message: 'created and authenticated new user'})
  //     })
  //     .catch(next)
  // })

  router.get('/signout', (req, res) => {
    req.logout()
    res.json({message: 'signed out'})
  })

  return router
}
<commit_msg>Added oathprofile to user object<commit_after>const express = require('express')
const passport = require('passport')
const users = require('../models/users')

const PROVIDER = 'google'

const authentication = require('./authentication')
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy

// Helper function for loading secret keys mainly. If they aren't found, just
// crash
function loadEnv (envVar) {
  if (!process.env[envVar]) throw new Error(`Environment variable '${envVar}' not found`)
  return process.env[envVar]
}

const GOOGLE_CLIENT_ID = loadEnv('CLIENT')
const GOOGLE_CLIENT_SECRET = loadEnv('SECRET')

function convertToUser (profile) {
  return {
    authProvider: PROVIDER,
    authId: profile.id,
    displayName: profile.displayName,
    email: profile.emails && profile.emails.length > 0 ? profile.emails[0].value : null,
    oauthProfile: profile
  }
}

function applyToUser (profile, user) {
  if (profile.displayName) {
    user.displayName = profile.displayName
  }

  if (profile.emails && profile.emails.length > 0) {
    user.email = profile.emails[0].value
  }

  user.oathProfile = profile
  return user
}

module.exports.Strategy = () => {
  return new GoogleStrategy({
    clientID: GOOGLE_CLIENT_ID,
    clientSecret: GOOGLE_CLIENT_SECRET,
    callbackURL: `http://localhost:${process.env.PORT || 3000}/auth/google/callback`
  }, (accessToken, refreshToken, profile, done) => {
    // see if user already exists
    users.getById(profile.id)
      .then(user => {
        console.log('got a user by ID')
        if (!user) {
          return users.insert(profile.id, convertToUser(profile))
        } else {
          return users.update(applyToUser(profile, user))
        }
      })
      .then(user => {
        console.log('got a user, calling done function')
        console.log(user)
        done(null, user)
      })
      .catch(done)
  })
}

module.exports.GoogleRouter = () => {
  var router = express.Router()

  router.get('/signin/google', passport.authenticate(PROVIDER, {scope: ['email', 'profile']}))

  router.get('/auth/google/callback', passport.authenticate(PROVIDER, {scope: 'profile'}),
    authentication.backToRequestedUrl)

  return router
}

module.exports.Router = () => {
  var router = express.Router()

  router.get('/session', ({user, session}, res) => {
    res.json({user, session})
  })

  router.post('/signin', passport.authenticate(PROVIDER), (req, res) => {
    var response = {message: 'authenticated'}

    if (req.session.afterAuthRedirectTo) {
      response.redirectTo = req.session.afterAuthRedirectTo
      delete req.session.afterAuthRedirectTo
    }

    res.json(response)
  })

  // router.post('/signup', (req, res, next) => {
  //   var newUser = req.body

  //   // ensure new user has required properties
  //   if (!newUser || !newUser.email || !newUser.displayName || !newUser.password) {
  //     return next({status: 400, message: 'Email, display name, and password are all required.'})
  //   }

  //   newUser.authProvider = PROVIDER
  //   newUser.authId = newUser.email

  //   users.doesUserExist(PROVIDER, newUser.email)
  //     .then(alreadyRegistered => {
  //       if (alreadyRegistered) {
  //         throw new Error({status: 400, message: 'That email address is already registered.'})
  //       }

  //       return hashPassword(newUser)
  //     })
  //     .then(() => {
  //       return users.insert(newUser)
  //     })
  //     .then(() => {
  //       return loginUser(newUser, req)
  //     })
  //     .then(() => {
  //       res.status(201).json({message: 'created and authenticated new user'})
  //     })
  //     .catch(next)
  // })

  router.get('/signout', (req, res) => {
    req.logout()
    res.json({message: 'signed out'})
  })

  return router
}
<|endoftext|>


<commit_before><commit_msg>Fix CameraControl pivot for ortho<commit_after><|endoftext|>


<commit_before>import { Store }                from 'airflux';
import Firebase                 from 'firebase';
import AuthStore                from './AuthStore';

import * as NotificationActions from './NotificationActions';

class NotificationStore extends Store {

    constructor() {
        super();
        this.state = {
            notifs : []
        };

        this.listenTo( AuthStore, this._onAuthSuccess.bind( this ) );

        NotificationActions.pushNotif.listen( this._pushNotif.bind( this ) );
        NotificationActions.removeNotif.listen( this._removeNotif.bind( this ) );

    }

    _onAuthSuccess( authStoreState ) {
        const { firebaseUrl , boardKey } = authStoreState.appConfig;

        this.notifRef = new Firebase( `${firebaseUrl}/notifications/${boardKey}` );

        this.notifRef.off();

        this.notifRef.on( 'child_added', this._onAddNotif.bind( this ) );
        this.notifRef.on( 'child_removed', this._onRemoveNotif.bind( this ) );

        this.state.interval = setInterval( this.removeOldNotif.bind( this ), 1000 );

    }

    removeOldNotif(){
        var now = Date.now();
        var cutoff = now - 7 * 1000;
        var old = this.notifRef.orderByChild('timestamp').endAt(cutoff).limitToLast(1);
        var listener = old.on('child_added', function(snapshot) {
            snapshot.ref().remove();
        });
    }

    _onAddNotif( dataSnapshot ){
        let { notifs } = this.state;
        notifs.push( { key : dataSnapshot.key(), val : dataSnapshot.val() } );
        this.state.notifs = notifs;
        this.publishState();
    }

    _onRemoveNotif( oldDataSnapshot ) {
        const notifKey = oldDataSnapshot.key();
        let { notifs } = this.state;
        _.remove( notifs , n => { return n.key === notifKey; } );
        this.state.notifs = notifs;
        this.publishState();
    }

    _pushNotif( notif ){
        notif.user = AuthStore.currentUser;
        notif.timestamp = Date.now();
        this.notifRef.push( notif, (error) => {
            error ? console.log(error) : null;
        });
    }

    _removeNotif(){
        const { firebaseUrl , boardKey } = this.authStoreState.appConfig;
        let notif = new Firebase( `${firebaseUrl}/notifications/${boardKey}/${notifKey}` );
        notif.remove();
        notif.off();
    }

}

export default new NotificationStore();
<commit_msg>Const notifDuration<commit_after>import { Store }                from 'airflux';
import Firebase                 from 'firebase';
import AuthStore                from './AuthStore';

import * as NotificationActions from './NotificationActions';

const notifDuration = 7;

class NotificationStore extends Store {

    constructor() {
        super();
        this.state = {
            notifs : []
        };

        this.listenTo( AuthStore, this._onAuthSuccess.bind( this ) );

        NotificationActions.pushNotif.listen( this._pushNotif.bind( this ) );
        NotificationActions.removeNotif.listen( this._removeNotif.bind( this ) );

    }

    _onAuthSuccess( authStoreState ) {
        const { firebaseUrl , boardKey } = authStoreState.appConfig;

        this.notifRef = new Firebase( `${firebaseUrl}/notifications/${boardKey}` );

        this.notifRef.off();

        this.notifRef.on( 'child_added', this._onAddNotif.bind( this ) );
        this.notifRef.on( 'child_removed', this._onRemoveNotif.bind( this ) );

        this.state.interval = setInterval( this.removeOldNotif.bind( this ), 1000 );

    }

    removeOldNotif(){
        var now = Date.now();
        var cutoff = now - notifDuration * 1000;
        var old = this.notifRef.orderByChild('timestamp').endAt(cutoff).limitToLast(1);
        var listener = old.on('child_added', function(snapshot) {
            snapshot.ref().remove();
        });
    }

    _onAddNotif( dataSnapshot ){
        let { notifs } = this.state;
        notifs.push( { key : dataSnapshot.key(), val : dataSnapshot.val() } );
        this.state.notifs = notifs;
        this.publishState();
    }

    _onRemoveNotif( oldDataSnapshot ) {
        const notifKey = oldDataSnapshot.key();
        let { notifs } = this.state;
        _.remove( notifs , n => { return n.key === notifKey; } );
        this.state.notifs = notifs;
        this.publishState();
    }

    _pushNotif( notif ){
        notif.user = AuthStore.currentUser;
        notif.timestamp = Date.now();
        this.notifRef.push( notif, (error) => {
            error ? console.log(error) : null;
        });
    }

    _removeNotif(){
        const { firebaseUrl , boardKey } = this.authStoreState.appConfig;
        let notif = new Firebase( `${firebaseUrl}/notifications/${boardKey}/${notifKey}` );
        notif.remove();
        notif.off();
    }

}

export default new NotificationStore();
<|endoftext|>


<commit_before>import Observable from './Observable';
import isAshNodeAshElement from '../internals/isAshNodeAshElement';
import constants from '../internals/constants';
import findNode from '../DOM/findNode';
import isFunction from '../internals/isFunction';

const LIFECYCLE_UNMOUNTED = constants.LIFECYCLE_UNMOUNTED;
const LIFECYCLE_MOUNTING = constants.LIFECYCLE_MOUNTING;
const LIFECYCLE_MOUNTED = constants.LIFECYCLE_MOUNTED;
const LIFECYCLE_UNINITIALIZED = constants.LIFECYCLE_UNINITIALIZED;

class Component extends Observable {
	constructor(props) {
		// autobind methods
		Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach((value) => {
			let descriptor = Object.getOwnPropertyDescriptor(this, value);

			if (isFunction(this[value]) && value !== 'constructor' && !(descriptor && (descriptor.get || descriptor.set))) {
				this[value] = this[value].bind(this);
			}
		});

		this.props = props || {};
		this.state = this.state || {};

		this.__isDirty = false;
		this.__previousLifecycle = LIFECYCLE_UNINITIALIZED;
		this.__currentLifecycle = LIFECYCLE_UNMOUNTED;
	}

	get isDirty() {
		return this.__isDirty;
	}

	set isDirty(value) {
		this.__isDirty = !!value;

		if (this.__isDirty && this.__element.stage) {
			this.__element.stage.update(this);
		}
	}

	get __lifecycle() {
		return this.__currentLifecycle;
	}

	set __lifecycle(nextLifecycle) {
		if (nextLifecycle !== LIFECYCLE_UNMOUNTED && nextLifecycle !== LIFECYCLE_MOUNTING && nextLifecycle !== LIFECYCLE_MOUNTED) {
			throw new Error(`${nextLifecycle} must be "${LIFECYCLE_UNMOUNTED}", "${LIFECYCLE_MOUNTING}" or "${LIFECYCLE_MOUNTED}". Also, this property is for internal use only. Do not change it!`);
		}

		this.__previousLifecycle = this.__currentLifecycle;
		this.__currentLifecycle = nextLifecycle;

		if (this.__previousLifecycle !== this.__currentLifecycle) {
			if (this.__currentLifecycle === LIFECYCLE_MOUNTING) {
				this.onBeforeMount();
			} else if (this.__currentLifecycle === LIFECYCLE_MOUNTED) {
				this.onMount();
			} else if (this.__currentLifecycle === LIFECYCLE_UNMOUNTED) {
				this.onUnmount();
			}
		}
	}

	get isMounted() {
		return this.__currentLifecycle === LIFECYCLE_MOUNTED;
	}

	get domNode() {
		if (this.isMounted && isAshNodeAshElement(this.__element.children[0])) {
			return findNode(this.__element.stage.getRootNode(), this.__element.children[0].instance.index);
		}

		return null;
	}

	shouldUpdate(newProps) {
		return this.props !== newProps;
	}

	onBeforeMount() {}

	onMount() {}

	onUnmount() {}

	onBeforeReceiveProps() {}

	render() {
		return null;
	}
}

export default Component;
<commit_msg>Component methods autobinding fix<commit_after>import Observable from './Observable';
import isAshNodeAshElement from '../internals/isAshNodeAshElement';
import constants from '../internals/constants';
import findNode from '../DOM/findNode';
import isFunction from '../internals/isFunction';

const LIFECYCLE_UNMOUNTED = constants.LIFECYCLE_UNMOUNTED;
const LIFECYCLE_MOUNTING = constants.LIFECYCLE_MOUNTING;
const LIFECYCLE_MOUNTED = constants.LIFECYCLE_MOUNTED;
const LIFECYCLE_UNINITIALIZED = constants.LIFECYCLE_UNINITIALIZED;

class Component extends Observable {
	constructor(props) {
		// autobind methods
		var prototype = Object.getPrototypeOf(this);
		Object.getOwnPropertyNames(prototype).forEach((value) => {
			let descriptor = Object.getOwnPropertyDescriptor(prototype, value);

			if (!(descriptor && (typeof descriptor.get !== 'undefined' || typeof descriptor.set !== 'undefined')) && isFunction(this[value]) && value !== 'constructor') {
				this[value] = this[value].bind(this);
			}
		});

		this.props = props || {};
		this.state = this.state || {};

		this.__isDirty = false;
		this.__previousLifecycle = LIFECYCLE_UNINITIALIZED;
		this.__currentLifecycle = LIFECYCLE_UNMOUNTED;
	}

	get isDirty() {
		return this.__isDirty;
	}

	set isDirty(value) {
		this.__isDirty = !!value;

		if (this.__isDirty && this.__element.stage) {
			this.__element.stage.update(this);
		}
	}

	get __lifecycle() {
		return this.__currentLifecycle;
	}

	set __lifecycle(nextLifecycle) {
		if (nextLifecycle !== LIFECYCLE_UNMOUNTED && nextLifecycle !== LIFECYCLE_MOUNTING && nextLifecycle !== LIFECYCLE_MOUNTED) {
			throw new Error(`${nextLifecycle} must be "${LIFECYCLE_UNMOUNTED}", "${LIFECYCLE_MOUNTING}" or "${LIFECYCLE_MOUNTED}". Also, this property is for internal use only. Do not change it!`);
		}

		this.__previousLifecycle = this.__currentLifecycle;
		this.__currentLifecycle = nextLifecycle;

		if (this.__previousLifecycle !== this.__currentLifecycle) {
			if (this.__currentLifecycle === LIFECYCLE_MOUNTING) {
				this.onBeforeMount();
			} else if (this.__currentLifecycle === LIFECYCLE_MOUNTED) {
				this.onMount();
			} else if (this.__currentLifecycle === LIFECYCLE_UNMOUNTED) {
				this.onUnmount();
			}
		}
	}

	get isMounted() {
		return this.__currentLifecycle === LIFECYCLE_MOUNTED;
	}

	get domNode() {
		if (this.isMounted && isAshNodeAshElement(this.__element.children[0])) {
			return findNode(this.__element.stage.getRootNode(), this.__element.children[0].instance.index);
		}

		return null;
	}

	shouldUpdate(newProps) {
		return this.props !== newProps;
	}

	onBeforeMount() {}

	onMount() {}

	onUnmount() {}

	onBeforeReceiveProps() {}

	render() {
		return null;
	}
}

export default Component;
<|endoftext|>