<commit_before>// ELMO.Views.OptionSetForm
//
// View model for the option sets form.
(function(ns, klass) {

  // constructor
  ns.OptionSetForm = klass = function(params) { var self = this;

    self.done = false;

    self.params = params;

    // setup OptionLevelsField view
    self.option_levels_field = new ELMO.Views.OptionLevelsField({
      wrapper: $("#option-levels-wrapper"),
      modal: $("#edit-option-level"),
      option_levels: params.option_set.levels,
      options_levels_read_only: self.params.options_levels_read_only,
      can_reorder: true,
      can_remove: self.params.can_remove_options,
      edit_link: self.params.edit_link,
      remove_link: self.params.remove_link
    });

    // setup OptionsField view
    self.options_field = new ELMO.Views.OptionsField({
      wrapper: $("#options-wrapper"),
      modal: $("#edit-option"),
      children: params.option_set.children,
      options_levels_read_only: self.params.options_levels_read_only,
      can_reorder: self.params.can_reorder,
      can_remove: self.params.can_remove_options,
      edit_link: self.params.edit_link,
      remove_link: self.params.remove_link
    });

    // find the allow_coordinates field
    self.allow_coordinates_field = $('.form_field[data-field-name=allow_coordinates]');

    // add option button click event
    $('div.add_options input[type=button]').on('click', function() { self.add_options(); });

    // add option level link click event
    $('.option_set_option_levels a.add-link').on('click', function(e) {
      self.option_levels_field.add();
      e.preventDefault();
    });

    // watch for changes to geographic property
    $('#option_set_geographic').on('change', function() { self.geographic_changed(); });
    self.geographic_changed();

    // watch for changes to allow_coordinates property
    $('#option_set_allow_coordinates').on('change', function() { self.allow_coordinates_changed(); });
    self.allow_coordinates_changed();

    // watch for changes to multilevel property
    $('#option_set_multi_level').on('change', function() { self.multilevel_changed(); });
    self.multilevel_changed();

    // events to enable/disable multilevel checkbox
    self.options_field.list.on('change', function(){ self.enable_multilevel_checkbox(); });
    self.option_levels_field.list.on('change', function(){ self.enable_multilevel_checkbox(); });
    self.enable_multilevel_checkbox();

    // setup the tokenInput control
    $('input.add_options_box').tokenInput(params.suggest_path, {
      theme: 'elmo',
      hintText: I18n.t('option_set.type_to_add_new'),
      noResultsText: I18n.t('option_set.none_found'),
      searchingText: I18n.t('option_set.searching'),
      resultsFormatter: self.format_token_result,
      preventDuplicates: true,
      tokenValue: 'name',
      onResult: function(results){ return self.process_token_results(results); },
      onAdd: function(item){ return self.token_added(item); },
      // this event hook is custom, added by tomsmyth. see the tokenInput source code.
      onEnter: function(){ self.add_options(); }
    });

    // Set maxlength on the inner token input box to enforce option name length on creation.
    $('#token-input-').attr('maxlength', self.params.max_option_name_length);

    // hookup form submit
    $('form.option_set_form').on('submit', function(){ return self.form_submitted(); });

    // hookup leave page warning unless ajax request
    if (!self.params.modal_mode)
      window.onbeforeunload = function(){
        if (self.dirty() && !self.done)
          return I18n.t('option_set.leave_page_warning');
      };
  };

  // checks if there have been any changes
  klass.prototype.dirty = function() { var self = this;
    return self.options_field.list.dirty || self.option_levels_field.list.dirty;
  };

  // enables/disables multi_level checkbox depending on option levels and optioning depths
  // should be disabled unless there are 0 option levels and all options have depth 1
  klass.prototype.enable_multilevel_checkbox = function() { var self = this;
    $('#option_set_multi_level').prop('disabled',
      !(self.option_levels_field.list.count() == 0 && self.options_field.list.max_depth() <= 1));
  };

  // reacts to changes to geographic checkbox
  klass.prototype.geographic_changed = function() { var self = this;
    var checked;
    // Check if geographic checkbox is read only
    if ($('#geographic div.ro-val').length > 0)
      checked = $('#geographic div.ro-val').data('val');
    else
      checked = $('#option_set_geographic').is(':checked');

    // show/hide the allow coordinates field
    if (checked) {
      self.allow_coordinates_field.show();
    } else {
      self.allow_coordinates_field.hide();
      self.allow_coordinates_field.find('input[type=checkbox]').attr('checked', false);
    }
  };

  // reacts to changes to allow_coordinates checkbox
  klass.prototype.allow_coordinates_changed = function() { var self = this;
    var checked;
    // Check if allow_coordinates checkbox is read only
    if ($('#allow_coordinates div.ro-val').length > 0)
      checked = $('#allow_coordinates div.ro-val').data('val');
    else
      checked = $('#option_set_allow_coordinates').is(':checked');

    // Update whether coordinates can be edited in the options_field
    self.options_field.list.allow_coordinates = checked;
  };

  // reacts to changes to multilevel checkbox
  klass.prototype.multilevel_changed = function() { var self = this;
    var checked;
    // Check if multilevel checkbox is read only
    if ($('#multi_level div.ro-val').length > 0)
      checked = $('#multi_level div.ro-val').data('val');
    else
      checked = $('#option_set_multi_level').is(':checked');

    // show/hide the option levels field
    self.option_levels_field.show(checked);

    // enable/disable nested options
    self.options_field.list.allow_nesting(checked);
  };

  // returns the html to insert in the token input result list
  klass.prototype.format_token_result = function(item) { var self = this;
    var details, css = "details";
    // if this is the new placeholder, add a string about that
    if (item.id == null) {
      details = I18n.t('option_set.create_new');
      css = "details create_new"
    // otherwise if no option sets were returned, use the none string
    } else if (item.set_names == '')
      details = '[' + I18n.t('common.none') + ']'
    // otherwise just use item.sets verbatim
    else
      details = item.set_names;

    return '
' + item.name + '
' + details + '

';
  };

  // strips duplicates from token results
  // this doesn't work if the result is cached
  klass.prototype.process_token_results = function(results) { var self = this;
    return results.filter(function(r){ return !self.options_field.list.has_with_name(r.name); });
  };

  // if the added token is a duplicate, delete it!
  klass.prototype.token_added = function(item) { var self = this;
    if (self.options_field.list.has_with_name(item.name))
      $('input.add_options_box').tokenInput("remove", {name: item.name});
  };

  // adds options from the token input control to the view and data model
  klass.prototype.add_options = function() { var self = this;
    var chosen = $('input.add_options_box').tokenInput('get');
    var ol = $('div#options-wrapper > ol');

    // loop over chosen options
    chosen.forEach(function(option_attribs){ self.options_field.add(option_attribs); });

    // clear out the add box
    $('input.add_options_box').tokenInput('clear');
  };

  // prepares the form to be submitted by setting up the right fields
  // or if in ajax mode, submits the form via ajax and returns false
  klass.prototype.form_submitted = function() { var self = this;

    // set flag so we don't raise warning on navigation
    self.done = true;

    // do client side validations
    self.clear_errors();
    if (self.validate())
      self.submit_via_ajax();

    // so form won't submit normally
    return false;
  };

  // traverses the option tree and generates a hash representing the full option set
  klass.prototype.prepare_data = function() { var self = this;
    // temporarily enable any disabled items else serialization will fail
    var disabled = $('form.option_set_form').find(':input:disabled').removeAttr('disabled');

    // get with basic form data
    var data = $('form.option_set_form').serializeHash();

    // re-disable
    disabled.attr('disabled', 'disabled');

    // Add nodes unless read only.
    if (!self.params.options_levels_read_only) {
      data.option_set = {};
      data.option_set.level_names = self.prepare_option_levels();
      data.option_set.children_attribs = self.prepare_options();
    }

    // Update some params OptionSet model, as this may be used by modal
    self.params.option_set.name = data['option_set[name]'];
    self.params.option_set.multi_level = data['option_set[multi_level]'] == '1';

    return data;
  };

  // prepares the list of option levels
  klass.prototype.prepare_option_levels = function() { var self = this;
    // we know the item tree in this case will be 'flat'
    return self.option_levels_field.list.item_tree().map(function(node){
      // each 'node' in the 'tree' will be a NamedItem, so we just take the name_translations hash
      return node.item.name_translations;
    });
  };

  // prepares the options, including the destroyed ones
  // see OptionNodeSupport modules for the expected format
  klass.prototype.prepare_options = function() { var self = this;
    // get the main tree
    return self.prepare_option_tree(self.options_field.list.item_tree());
  };

  // prepares an option tree
  // nodes - a list of the top level nodes in the tree
  klass.prototype.prepare_option_tree = function(nodes) { var self = this;
    return nodes.map(function(node){
      // in this case, the item will be an Optioning, which is also a NamedItem
      var prepared = {option_attribs: {name_translations: node.item.name_translations, latitude: node.item.latitude, longitude: node.item.longitude}};

      // include IDs if available
      if (node.item.id)
        prepared.id = node.item.id;

      if (node.item.option.id)
        prepared.option_attribs.id = node.item.option.id;

      // recurse
      prepared.children_attribs = node.children && node.children.length ? self.prepare_option_tree(node.children) : 'NONE';

      return prepared;
    });
  };

  // submits form via ajax
  klass.prototype.submit_via_ajax = function() { var self = this;

    // get data and set modal if applicable
    var data = self.prepare_data();
    if (self.params.modal_mode)
      data.modal_mode = 1;

    // show loading
    $('form.option_set_form .loading_indicator img').show();

    $.ajax({
      url: $('form.option_set_form').attr('action'),
      type: 'POST',
      data: data,
      success: function(data, status, jqxhr) {
        // if content type was json, that means success
        if (jqxhr.getResponseHeader('Content-Type').match('application/json')) {

          // if we're in modal mode, we need to do different stuff
          if (self.params.modal_mode) {

            // the data holds the new option set's ID
            self.params.option_set.id = parseInt(data);

            // trigger the custom event
            $('form.option_set_form').trigger('option_set_form_submit_success', [self.params.option_set]);
          }

          // else, not modal mode, just redirect (URL given as json response)
          else
            window.location.href = data;

        // otherwise we got an error,
        // so replace the div with the new partial (this will instantiate a new instance of this class)
        } else {
          $('div.option_set_form').replaceWith(jqxhr.responseText);
        }
      },
      error: function(jqxhr) {
        // if we get an HTTP error, it's some server thing so just display a generic message
        $('div.option_set_form').replaceWith("Server Error");
      }
    });
  };

  klass.prototype.validate = function() { var self = this;
    var checks = ['name_presence'];
    if (!self.params.options_levels_read_only)
      checks.push('option_presence', 'option_depths');
    var valid = true;

    // Run each check even if an early one fails.
    checks.forEach(function(c) {
      if (!self['validate_' + c]()) valid = false;
    });

    return valid;
  };

  klass.prototype.validate_name_presence = function() { var self = this;
    // Ensure name field is editable.
    if ($('input#option_set_name').length == 0) return true;

    if ($('#option_set_name').val().trim() == '') {
      self.add_error('.option_set_name', I18n.t('activerecord.errors.messages.blank'));
      return false;
    }
    return true;
  };

  klass.prototype.validate_option_presence = function() { var self = this;
    if (self.options_field.list.size() == 0) {
      self.add_error('.option_set_options', I18n.t('activerecord.errors.models.option_set.at_least_one'));
      return false;
    }
    return true;
  };

  // checks if number of option levels and option depths are compatible
  // returns whether submission should proceed
  klass.prototype.validate_option_depths = function() { var self = this;
    if ($('#option_set_multi_level').is(':checked')) {
      var levels = self.option_levels_field.list.size();
      var depth = self.options_field.list.max_depth();
      if (levels != depth) {
        self.add_error('.option_set_options', I18n.t('activerecord.errors.models.option_set.wrong_depth',
          {levels: levels, depth: depth}));
        return false;
      }
    }
    return true;
  };

  // adds a validation error to the field with the given selector
  klass.prototype.add_error = function(selector, msg) { var self = this;
    $(selector + ' .control').prepend($('
').addClass('form-errors').html(msg));
  };

  // clears error messages
  klass.prototype.clear_errors = function() { var self = this;
    $('form.option_set_form').find('div.form-errors').remove();
  };

})(ELMO.Views);
<commit_msg>2337: Only submit lat/long when allow_coordinates is set<commit_after>// ELMO.Views.OptionSetForm
//
// View model for the option sets form.
(function(ns, klass) {

  // constructor
  ns.OptionSetForm = klass = function(params) { var self = this;

    self.done = false;

    self.params = params;

    // setup OptionLevelsField view
    self.option_levels_field = new ELMO.Views.OptionLevelsField({
      wrapper: $("#option-levels-wrapper"),
      modal: $("#edit-option-level"),
      option_levels: params.option_set.levels,
      options_levels_read_only: self.params.options_levels_read_only,
      can_reorder: true,
      can_remove: self.params.can_remove_options,
      edit_link: self.params.edit_link,
      remove_link: self.params.remove_link
    });

    // setup OptionsField view
    self.options_field = new ELMO.Views.OptionsField({
      wrapper: $("#options-wrapper"),
      modal: $("#edit-option"),
      children: params.option_set.children,
      options_levels_read_only: self.params.options_levels_read_only,
      can_reorder: self.params.can_reorder,
      can_remove: self.params.can_remove_options,
      edit_link: self.params.edit_link,
      remove_link: self.params.remove_link
    });

    // find the allow_coordinates field
    self.allow_coordinates_field = $('.form_field[data-field-name=allow_coordinates]');

    // add option button click event
    $('div.add_options input[type=button]').on('click', function() { self.add_options(); });

    // add option level link click event
    $('.option_set_option_levels a.add-link').on('click', function(e) {
      self.option_levels_field.add();
      e.preventDefault();
    });

    // watch for changes to geographic property
    $('#option_set_geographic').on('change', function() { self.geographic_changed(); });
    self.geographic_changed();

    // watch for changes to allow_coordinates property
    $('#option_set_allow_coordinates').on('change', function() { self.allow_coordinates_changed(); });
    self.allow_coordinates_changed();

    // watch for changes to multilevel property
    $('#option_set_multi_level').on('change', function() { self.multilevel_changed(); });
    self.multilevel_changed();

    // events to enable/disable multilevel checkbox
    self.options_field.list.on('change', function(){ self.enable_multilevel_checkbox(); });
    self.option_levels_field.list.on('change', function(){ self.enable_multilevel_checkbox(); });
    self.enable_multilevel_checkbox();

    // setup the tokenInput control
    $('input.add_options_box').tokenInput(params.suggest_path, {
      theme: 'elmo',
      hintText: I18n.t('option_set.type_to_add_new'),
      noResultsText: I18n.t('option_set.none_found'),
      searchingText: I18n.t('option_set.searching'),
      resultsFormatter: self.format_token_result,
      preventDuplicates: true,
      tokenValue: 'name',
      onResult: function(results){ return self.process_token_results(results); },
      onAdd: function(item){ return self.token_added(item); },
      // this event hook is custom, added by tomsmyth. see the tokenInput source code.
      onEnter: function(){ self.add_options(); }
    });

    // Set maxlength on the inner token input box to enforce option name length on creation.
    $('#token-input-').attr('maxlength', self.params.max_option_name_length);

    // hookup form submit
    $('form.option_set_form').on('submit', function(){ return self.form_submitted(); });

    // hookup leave page warning unless ajax request
    if (!self.params.modal_mode)
      window.onbeforeunload = function(){
        if (self.dirty() && !self.done)
          return I18n.t('option_set.leave_page_warning');
      };
  };

  // checks if there have been any changes
  klass.prototype.dirty = function() { var self = this;
    return self.options_field.list.dirty || self.option_levels_field.list.dirty;
  };

  // enables/disables multi_level checkbox depending on option levels and optioning depths
  // should be disabled unless there are 0 option levels and all options have depth 1
  klass.prototype.enable_multilevel_checkbox = function() { var self = this;
    $('#option_set_multi_level').prop('disabled',
      !(self.option_levels_field.list.count() == 0 && self.options_field.list.max_depth() <= 1));
  };

  // reacts to changes to geographic checkbox
  klass.prototype.geographic_changed = function() { var self = this;
    var checked;
    // Check if geographic checkbox is read only
    if ($('#geographic div.ro-val').length > 0)
      checked = $('#geographic div.ro-val').data('val');
    else
      checked = $('#option_set_geographic').is(':checked');

    // show/hide the allow coordinates field
    if (checked) {
      self.allow_coordinates_field.show();
    } else {
      self.allow_coordinates_field.hide();
      self.allow_coordinates_field.find('input[type=checkbox]').attr('checked', false);
    }
  };

  // reacts to changes to allow_coordinates checkbox
  klass.prototype.allow_coordinates_changed = function() { var self = this;
    var checked;
    // Check if allow_coordinates checkbox is read only
    if ($('#allow_coordinates div.ro-val').length > 0)
      checked = $('#allow_coordinates div.ro-val').data('val');
    else
      checked = $('#option_set_allow_coordinates').is(':checked');

    // Update whether coordinates can be edited in the options_field
    self.options_field.list.allow_coordinates = checked;
  };

  // reacts to changes to multilevel checkbox
  klass.prototype.multilevel_changed = function() { var self = this;
    var checked;
    // Check if multilevel checkbox is read only
    if ($('#multi_level div.ro-val').length > 0)
      checked = $('#multi_level div.ro-val').data('val');
    else
      checked = $('#option_set_multi_level').is(':checked');

    // show/hide the option levels field
    self.option_levels_field.show(checked);

    // enable/disable nested options
    self.options_field.list.allow_nesting(checked);
  };

  // returns the html to insert in the token input result list
  klass.prototype.format_token_result = function(item) { var self = this;
    var details, css = "details";
    // if this is the new placeholder, add a string about that
    if (item.id == null) {
      details = I18n.t('option_set.create_new');
      css = "details create_new"
    // otherwise if no option sets were returned, use the none string
    } else if (item.set_names == '')
      details = '[' + I18n.t('common.none') + ']'
    // otherwise just use item.sets verbatim
    else
      details = item.set_names;

    return '
' + item.name + '
' + details + '

';
  };

  // strips duplicates from token results
  // this doesn't work if the result is cached
  klass.prototype.process_token_results = function(results) { var self = this;
    return results.filter(function(r){ return !self.options_field.list.has_with_name(r.name); });
  };

  // if the added token is a duplicate, delete it!
  klass.prototype.token_added = function(item) { var self = this;
    if (self.options_field.list.has_with_name(item.name))
      $('input.add_options_box').tokenInput("remove", {name: item.name});
  };

  // adds options from the token input control to the view and data model
  klass.prototype.add_options = function() { var self = this;
    var chosen = $('input.add_options_box').tokenInput('get');
    var ol = $('div#options-wrapper > ol');

    // loop over chosen options
    chosen.forEach(function(option_attribs){ self.options_field.add(option_attribs); });

    // clear out the add box
    $('input.add_options_box').tokenInput('clear');
  };

  // prepares the form to be submitted by setting up the right fields
  // or if in ajax mode, submits the form via ajax and returns false
  klass.prototype.form_submitted = function() { var self = this;

    // set flag so we don't raise warning on navigation
    self.done = true;

    // do client side validations
    self.clear_errors();
    if (self.validate())
      self.submit_via_ajax();

    // so form won't submit normally
    return false;
  };

  // traverses the option tree and generates a hash representing the full option set
  klass.prototype.prepare_data = function() { var self = this;
    // temporarily enable any disabled items else serialization will fail
    var disabled = $('form.option_set_form').find(':input:disabled').removeAttr('disabled');

    // get with basic form data
    var data = $('form.option_set_form').serializeHash();

    // re-disable
    disabled.attr('disabled', 'disabled');

    // Add nodes unless read only.
    if (!self.params.options_levels_read_only) {
      data.option_set = {};
      data.option_set.level_names = self.prepare_option_levels();
      data.option_set.children_attribs = self.prepare_options();
    }

    // Update some params OptionSet model, as this may be used by modal
    self.params.option_set.name = data['option_set[name]'];
    self.params.option_set.multi_level = data['option_set[multi_level]'] == '1';

    return data;
  };

  // prepares the list of option levels
  klass.prototype.prepare_option_levels = function() { var self = this;
    // we know the item tree in this case will be 'flat'
    return self.option_levels_field.list.item_tree().map(function(node){
      // each 'node' in the 'tree' will be a NamedItem, so we just take the name_translations hash
      return node.item.name_translations;
    });
  };

  // prepares the options, including the destroyed ones
  // see OptionNodeSupport modules for the expected format
  klass.prototype.prepare_options = function() { var self = this;
    // get the main tree
    return self.prepare_option_tree(self.options_field.list.item_tree());
  };

  // prepares an option tree
  // nodes - a list of the top level nodes in the tree
  klass.prototype.prepare_option_tree = function(nodes) { var self = this;
    return nodes.map(function(node){
      // in this case, the item will be an Optioning, which is also a NamedItem
      var prepared = {option_attribs: {name_translations: node.item.name_translations}};

      // include IDs if available
      if (node.item.id)
        prepared.id = node.item.id;

      if (node.item.option.id)
        prepared.option_attribs.id = node.item.option.id;

      // include latitude and longitude if allow_coordinates is set
      if ($('#option_set_allow_coordinates').is(':checked')) {
        prepared.option_attribs.latitude = node.item.latitude;
        prepared.option_attribs.longitude = node.item.longitude;
      }

      // recurse
      prepared.children_attribs = node.children && node.children.length ? self.prepare_option_tree(node.children) : 'NONE';

      return prepared;
    });
  };

  // submits form via ajax
  klass.prototype.submit_via_ajax = function() { var self = this;

    // get data and set modal if applicable
    var data = self.prepare_data();
    if (self.params.modal_mode)
      data.modal_mode = 1;

    // show loading
    $('form.option_set_form .loading_indicator img').show();

    $.ajax({
      url: $('form.option_set_form').attr('action'),
      type: 'POST',
      data: data,
      success: function(data, status, jqxhr) {
        // if content type was json, that means success
        if (jqxhr.getResponseHeader('Content-Type').match('application/json')) {

          // if we're in modal mode, we need to do different stuff
          if (self.params.modal_mode) {

            // the data holds the new option set's ID
            self.params.option_set.id = parseInt(data);

            // trigger the custom event
            $('form.option_set_form').trigger('option_set_form_submit_success', [self.params.option_set]);
          }

          // else, not modal mode, just redirect (URL given as json response)
          else
            window.location.href = data;

        // otherwise we got an error,
        // so replace the div with the new partial (this will instantiate a new instance of this class)
        } else {
          $('div.option_set_form').replaceWith(jqxhr.responseText);
        }
      },
      error: function(jqxhr) {
        // if we get an HTTP error, it's some server thing so just display a generic message
        $('div.option_set_form').replaceWith("Server Error");
      }
    });
  };

  klass.prototype.validate = function() { var self = this;
    var checks = ['name_presence'];
    if (!self.params.options_levels_read_only)
      checks.push('option_presence', 'option_depths');
    var valid = true;

    // Run each check even if an early one fails.
    checks.forEach(function(c) {
      if (!self['validate_' + c]()) valid = false;
    });

    return valid;
  };

  klass.prototype.validate_name_presence = function() { var self = this;
    // Ensure name field is editable.
    if ($('input#option_set_name').length == 0) return true;

    if ($('#option_set_name').val().trim() == '') {
      self.add_error('.option_set_name', I18n.t('activerecord.errors.messages.blank'));
      return false;
    }
    return true;
  };

  klass.prototype.validate_option_presence = function() { var self = this;
    if (self.options_field.list.size() == 0) {
      self.add_error('.option_set_options', I18n.t('activerecord.errors.models.option_set.at_least_one'));
      return false;
    }
    return true;
  };

  // checks if number of option levels and option depths are compatible
  // returns whether submission should proceed
  klass.prototype.validate_option_depths = function() { var self = this;
    if ($('#option_set_multi_level').is(':checked')) {
      var levels = self.option_levels_field.list.size();
      var depth = self.options_field.list.max_depth();
      if (levels != depth) {
        self.add_error('.option_set_options', I18n.t('activerecord.errors.models.option_set.wrong_depth',
          {levels: levels, depth: depth}));
        return false;
      }
    }
    return true;
  };

  // adds a validation error to the field with the given selector
  klass.prototype.add_error = function(selector, msg) { var self = this;
    $(selector + ' .control').prepend($('
').addClass('form-errors').html(msg));
  };

  // clears error messages
  klass.prototype.clear_errors = function() { var self = this;
    $('form.option_set_form').find('div.form-errors').remove();
  };

})(ELMO.Views);
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { FormattedMessage, injectIntl } from 'react-intl'
import jp from 'jsonpath'
import Chip from 'material-ui/Chip'
import FlatButton from 'material-ui/FlatButton'
import RaisedButton from 'material-ui/RaisedButton'
import { Link } from 'react-router'
// import ImmutablePropTypes from 'react-immutable-proptypes'
import H3 from 'components/H3'
import { IMAGES_URL } from 'services/config'
import P from 'components/P'
import Divider from 'material-ui/Divider'
import tagMessages from 'components/PictogramTags/messages'
import messages from './messages'

const styles = {
  chip: {
    margin: '4px',
  },
}
class PictogramCategories extends Component {
  static propTypes = {
    categories: PropTypes.object.isRequired,
    pictoCategories: PropTypes.array.isRequired,
    tags: ImmutablePropTypes.list.isRequired,
    showAAC: PropTypes.bool.isRequired,
  }

  render() {
    const { categories, pictoCategories, tags, intl, showAAC } = this.props
    const tree = categories.toJSON()
    const nodes = jp.nodes(tree, '$..text')
    const nodesKeywords = jp.nodes(tree, '$..keywords')
    const categoryPaths = nodes
      .filter((node) => {
        const lastCategoryCount = node.path.length - 1
        const lastCategory = node.path[lastCategoryCount - 1]
        return pictoCategories.indexOf(lastCategory) !== -1
      })
      .sort((a, b) => {
        const aLastCategoryCount = a.path.length - 1
        const aLastCategory = a.path[aLastCategoryCount - 1]
        const bLastCategoryCount = b.path.length - 1
        const bLastCategory = b.path[bLastCategoryCount - 1]
        return (
          pictoCategories.indexOf(aLastCategory) -
          pictoCategories.indexOf(bLastCategory)
        )
      })
      .map((node) => node.path)
      .map((path) => {
        const categoryPath = []
        path.forEach((category) => {
          if (['$', 'children', 'text'].indexOf(category) === -1)
            categoryPath.push(category)
        })
        return categoryPath
      })
      .map((path) =>
        path.map((category) => {
          const text = nodes.filter((node) => {
            const lastCategoryCount = node.path.length - 1
            const lastCategory = node.path[lastCategoryCount - 1]
            return lastCategory === category
          })
          const keywords = nodesKeywords.filter((node) => {
            const lastCategoryCount = node.path.length - 1
            const lastCategory = node.path[lastCategoryCount - 1]
            return lastCategory === category
          })
          return { text: text[0].value, keyword: keywords[0].value[0] }
        })
      )
    const renderPaths = categoryPaths.map((categoryPath, index) => (

        {categoryPath.map((item, index) => {
          if (!index) {
            return !item.keyword ? (
              <FlatButton key={item.text} label={item.text} disabled={true} />
            ) : (
              <Link to={`/pictograms/search/${item.keyword}`}>
                <FlatButton key={item.text} label={item.text} />
              </Link>
            )
          } else {
            return item.keyword ? (

 /

                <Link to={`/pictograms/search/${item.keyword}`}>
                  <FlatButton key={item.text} label={item.text} />
                </Link>

            ) : (

 /

                <FlatButton label={item.text} disabled={true} />

            )
          }
        })}

    ))
    return (

{<FormattedMessage {...messages.taxonomy} />}

        <Divider />
        {showAAC && (

              <img
                style={{ width: '40px', height: '40px' }}
                src={`${IMAGES_URL}/core-vocabulary-icon.svg`}
              />

                <FormattedMessage {...messages.coreVocabularyIntro} />

            <Link
              to={`/pictograms/search/${intl.formatMessage(
                tagMessages['core vocabulary']
              )}`}
            >
              <RaisedButton
                label={<FormattedMessage {...messages.showCoreVocabulary} />}
                secondary={true}
                style={{ marginRight: '20px' }}
              />
            </Link>

              <RaisedButton
                label={<FormattedMessage {...messages.knowMore} />}
                secondary={true}
              />

        )}

{<FormattedMessage {...messages.pictoBelongs} />}

        {renderPaths.map((path) => (

{path}

        ))}

{<FormattedMessage {...messages.pictoLabels} />}

        {tags && (

            {tags.map((tag) => (
              <Chip key={tag} style={styles.chip}>
                {<FormattedMessage {...tagMessages[tag]} />}
              </Chip>
            ))}

        )}

    )
  }
}

export default injectIntl(PictogramCategories)
<commit_msg>fix style issue, button min width<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { FormattedMessage, injectIntl } from 'react-intl'
import jp from 'jsonpath'
import Chip from 'material-ui/Chip'
import FlatButton from 'material-ui/FlatButton'
import RaisedButton from 'material-ui/RaisedButton'
import { Link } from 'react-router'
// import ImmutablePropTypes from 'react-immutable-proptypes'
import H3 from 'components/H3'
import { IMAGES_URL } from 'services/config'
import P from 'components/P'
import Divider from 'material-ui/Divider'
import tagMessages from 'components/PictogramTags/messages'
import messages from './messages'

const styles = {
  chip: {
    margin: '4px',
  },
}
class PictogramCategories extends Component {
  static propTypes = {
    categories: PropTypes.object.isRequired,
    pictoCategories: PropTypes.array.isRequired,
    tags: ImmutablePropTypes.list.isRequired,
    showAAC: PropTypes.bool.isRequired,
  }

  render() {
    const { categories, pictoCategories, tags, intl, showAAC } = this.props
    const tree = categories.toJSON()
    const nodes = jp.nodes(tree, '$..text')
    const nodesKeywords = jp.nodes(tree, '$..keywords')
    const categoryPaths = nodes
      .filter((node) => {
        const lastCategoryCount = node.path.length - 1
        const lastCategory = node.path[lastCategoryCount - 1]
        return pictoCategories.indexOf(lastCategory) !== -1
      })
      .sort((a, b) => {
        const aLastCategoryCount = a.path.length - 1
        const aLastCategory = a.path[aLastCategoryCount - 1]
        const bLastCategoryCount = b.path.length - 1
        const bLastCategory = b.path[bLastCategoryCount - 1]
        return (
          pictoCategories.indexOf(aLastCategory) -
          pictoCategories.indexOf(bLastCategory)
        )
      })
      .map((node) => node.path)
      .map((path) => {
        const categoryPath = []
        path.forEach((category) => {
          if (['$', 'children', 'text'].indexOf(category) === -1)
            categoryPath.push(category)
        })
        return categoryPath
      })
      .map((path) =>
        path.map((category) => {
          const text = nodes.filter((node) => {
            const lastCategoryCount = node.path.length - 1
            const lastCategory = node.path[lastCategoryCount - 1]
            return lastCategory === category
          })
          const keywords = nodesKeywords.filter((node) => {
            const lastCategoryCount = node.path.length - 1
            const lastCategory = node.path[lastCategoryCount - 1]
            return lastCategory === category
          })
          return { text: text[0].value, keyword: keywords[0].value[0] }
        })
      )
    const renderPaths = categoryPaths.map((categoryPath, index) => (

        {categoryPath.map((item, index) => {
          if (!index) {
            return !item.keyword ? (
              <FlatButton key={item.text} label={item.text} disabled={true} />
            ) : (
              <Link to={`/pictograms/search/${item.keyword}`}>
                <FlatButton key={item.text} label={item.text} />
              </Link>
            )
          } else {
            return item.keyword ? (

 /

                <Link to={`/pictograms/search/${item.keyword}`}>
                  <FlatButton key={item.text} label={item.text} />
                </Link>

            ) : (

 /

                <FlatButton label={item.text} disabled={true} />

            )
          }
        })}

    ))
    return (

{<FormattedMessage {...messages.taxonomy} />}

        <Divider />
        {showAAC && (

              <img
                style={{ width: '40px', height: '40px' }}
                src={`${IMAGES_URL}/core-vocabulary-icon.svg`}
              />

                <FormattedMessage {...messages.coreVocabularyIntro} />

            <Link
              to={`/pictograms/search/${intl.formatMessage(
                tagMessages['core vocabulary']
              )}`}
            >
              <RaisedButton
                label={<FormattedMessage {...messages.showCoreVocabulary} />}
                secondary={true}
                style={{ marginRight: '20px', minWidth: '200px' }}
              />
            </Link>

              <RaisedButton
                label={<FormattedMessage {...messages.knowMore} />}
                secondary={true}
                style={{ minWidth: '200px' }}
              />

        )}

{<FormattedMessage {...messages.pictoBelongs} />}

        {renderPaths.map((path) => (

{path}

        ))}

{<FormattedMessage {...messages.pictoLabels} />}

        {tags && (

            {tags.map((tag) => (
              <Chip key={tag} style={styles.chip}>
                {<FormattedMessage {...tagMessages[tag]} />}
              </Chip>
            ))}

        )}

    )
  }
}

export default injectIntl(PictogramCategories)
<|endoftext|>


<commit_before>import React, { Component, Fragment } from "react";
import ReactDOM from "react-dom";
import { ThemeProvider } from 'emotion-theming'
import sanitizeHtml from 'sanitize-html';
import theme from './textEditor/theme'
import themeDark from './textEditor/darkTheme'
import DraftRenderer from './textEditor/draftRenderer'
import DanteContainer from './textEditor/editorStyles'
import gravatar from "./shared/gravatar"
import Moment from 'react-moment';
import serialize from 'form-serialize'
import UnicornEditor from './textEditor'
import {isEmpty} from 'lodash'
import {
  EditorSection,
  CommentsWrapper,
  CommentsItem,
  Footer,
  ReadIndicator,
  MessageItem,
  UserAutoChatAvatar,
  NewConvoBtn,
  ConversationSummary,
  ConversationSummaryAvatar,
  ConversationSummaryBody,
  ConversationSummaryBodyMeta,
  ConversationSummaryBodyContent,
  ConversationSummaryBodyItems,
  Autor,
  Hint,
  ConversationsFooter,
  MessageSpinner,
  AppPackageBlockContainer,
  ConversationEventContainer,
} from './styles/styled'

export class Conversations extends Component {

  componentDidMount(){
    this.props.clearAndGetConversations()

    this.props.updateHeader(
      {
        translateY: 0 ,
        opacity: 1,
        height: '0'
      }
    )
  }

  // TODO: skip on xhr progress
  handleConversationsScroll = (e) => {
    let element = e.target

    //console.log(element.scrollHeight - element.scrollTop , element.clientHeight)
    if (element.scrollHeight - element.scrollTop === element.clientHeight) {
      if (this.props.conversationsMeta.next_page)
        this.props.getConversations({ append: true })
    }
  }

  sanitizeMessageSummary = (message)=>{
    if(!message)
      return

    const sanitized = sanitizeHtml(message)
    return sanitized.length > 100 ? `${sanitized.substring(0, 100)} ...` : sanitized
  }

  render(){
    const {t} = this.props

    return

        <CommentsWrapper
          isMobile={this.props.isMobile}>
          {
            this.props.conversations.map((o, i) => {

              const message = o.lastMessage

              return <CommentsItemComp
                key={`comments-item-comp-${o.key}`}
                message={message}
                o={o}
                index={i}
                t={t}
                displayConversation={this.props.displayConversation}
                sanitizeMessageSummary={this.sanitizeMessageSummary}
              />
            })
          }
        </CommentsWrapper>

        <ConversationsFooter>
          <Hint>
            {this.props.app.tagline}
          </Hint>

          {
            this.props.app.inboundSettings.enabled &&
            <NewConvoBtn
              in={this.props.transition}
              onClick={this.props.displayNewConversation}>
              {t("create_new_conversation")}
            </NewConvoBtn>
          }
        </ConversationsFooter>

  }
}

export class Conversation extends Component {

  componentDidMount(){
    this.props.updateHeader(
      {
        translateY: 0 ,
        opacity: 1,
        height: '0'
      }
    )
  }

  componentWillUnmount(){
    if(!this.props.inline_conversation)
      this.props.clearConversation()
  }

  // TODO: skip on xhr progress
  handleConversationScroll = (e) => {

    if(this.props.disablePagination) return

    let element = e.target
    //console.log(element.scrollTop)
    //console.log(element.scrollHeight - element.scrollTop, element.clientHeight) // on bottom
    if (element.scrollTop === 0) { // on top

      this.props.updateHeader(
        {
          translateY: 0 ,
          opacity: 1,
          height: 212
        }
      )

    //if (element.scrollTop <= 50) { // on almost top // todo skip on xhr loading
      if (this.props.conversation.messages.meta.next_page)
        this.props.setConversation(this.props.conversation.key)
    } else {
      this.props.updateHeader(
        {
          translateY: 0 ,
          opacity: 1,
          height: 0
        }
      )
    }
  }

  renderMessage = (o, i)=>{
    const userClass = o.appUser.kind === "agent" ? 'admin' : 'user'
    const isAgent = o.appUser.kind === "agent"
    const themeforMessage = o.privateNote || isAgent ? theme : themeDark
    const {t} = this.props

    return <MessageItemWrapper
            visible={this.props.visible}
            email={this.props.email}
            key={`conversation-item-${o.id}`}
            conversation={this.props.conversation}
            pushEvent={this.props.pushEvent}
            data={o}>

              <MessageItem
                className={userClass}
                messageSourceType={o.messageSource ? o.messageSource.type : ''}
              >

              {
                !this.props.isUserAutoMessage(o) && isAgent ?
                <ConversationSummaryAvatar>

                </ConversationSummaryAvatar> : null
              }

                {
                  this.props.isUserAutoMessage(o) ?
                    <UserAutoChatAvatar>

                      {o.appUser.name || o.appUser.email}
                    </UserAutoChatAvatar> : null
                }

                {/*render light theme on user or private note*/}

                <ThemeProvider
                  theme={ themeforMessage }>
                  <DanteContainer>
                    <DraftRenderer
                      key={i}
                      message={o}
                      domain={this.props.domain}
                      raw={JSON.parse(o.message.serializedContent)}
                    />

                      {
                        o.readAt ?
                          <Moment fromNow>
                            {o.readAt}
                          </Moment> : {t("not_seen")}
                      }

                  </DanteContainer>
                </ThemeProvider>

            </MessageItem>

          </MessageItemWrapper>
  }

  renderItemPackage = (o, i)=>{
    return  <AppPackageBlock
               key={i}
               message={o}
               conversation={this.props.conversation}
               submitAppUserData={this.props.submitAppUserData.bind(this)}
               clickHandler={this.appPackageClickHandler.bind(this)}
               appPackageSubmitHandler={this.appPackageSubmitHandler.bind(this)}
               t={this.props.t}
               {...o}
              />
  }

  renderEventBlock = (o, i)=>{
    const {data, action} = o.message
    return <ConversationEventContainer>

              {this.props.t(`conversations.events.${action}`, data)}

           </ConversationEventContainer>
  }

  appPackageBlockDisplay = (message)=>{
    this.props.displayAppBlockFrame(message)
  }

  appPackageClickHandler = (item, message)=>{
    // run app block display here! refactor
    if (message.message.blocks.type === "app_package") return this.appPackageBlockDisplay(message)
    this.props.pushEvent('trigger_step', {
      conversation_id: this.props.conversation.key,
      message_id: message.id,
      trigger: message.triggerId,
      step: item.nextStepUuid || item.next_step_uuid,
      reply: item
    })

  }

  appPackageSubmitHandler = (data, message)=>{
    this.props.pushEvent("receive_conversation_part",
      {
        conversation_id: this.props.conversation.key,
        message_id: message.id,
        step: this.props.stepId,
        trigger: this.props.TriggerId,
        submit: data
      })
  }

  renderTyping = ()=>{
    return <MessageItem>

              <MessageSpinner>

              </MessageSpinner>

                {this.props.agent_typing.author.name || 'agent'}
                is typing

           </MessageItem>
  }

  isInputEnabled =()=>{
    const messages = this.props.conversation.messages.collection
    if( messages.length === 0 ) return true

    const message = messages[0].message
    if(isEmpty(message.blocks)) return true
    return message.state === "replied"
  }

  render(){

    const {t} = this.props
    console.log("ENABLED???", this.isInputEnabled())
    return

 this.props.setOverflow(comp) }
        onScroll={this.handleConversationScroll}
        style={{ overflowY: 'auto', height: '100%' }}>

        <EditorSection>

          <CommentsWrapper
            isReverse={true}
            //ref={this.commentWrapperRef}
            isMobile={this.props.isMobile}>

            {
              this.props.agent_typing && this.renderTyping()
            }
            {
              this.props.conversation.messages && this.props.conversation.messages.collection.map((o, i) => {
                  if(o.message.blocks) return this.renderItemPackage(o, i)
                  if(o.message.action) return this.renderEventBlock(o, i)
                  return this.renderMessage(o, i)
              })
            }

          </CommentsWrapper>

            {
              !this.isInputEnabled() ? t("reply_above") :
              <UnicornEditor
                footerClassName={this.props.footerClassName }
                insertComment={this.props.insertComment}
              />
            }

        </EditorSection>

  }

}

class MessageItemWrapper extends Component {
  componentDidMount(){
    // mark as read on first render if not read & from admin
    this.sendEvent()
  }

  componentDidUpdate(prevProps, prevState){
    if(prevProps && prevProps.visible != this.props.visible && this.props.visible)
      this.sendEvent()
  }

  sendEvent = ()=>{
    if(this.props.visible && !this.props.data.volatile && !this.props.data.readAt && this.props.data.appUser.kind === "agent"){
      this.props.pushEvent("receive_conversation_part",
        Object.assign({}, {
          conversation_id: this.props.conversation.key,
          message_id: this.props.data.id,
          step: this.props.stepId,
          trigger: this.props.TriggerId
        }, {email: this.props.email})
      )
    }
  }

  render(){
    return <Fragment>
            {this.props.children}
           </Fragment>
  }
}

class AppPackageBlock extends Component {

  form = null

  state = {
    value: null
  }

  renderElements = ()=>{
    const isDisabled = this.props.message.state === "replied"
    if(isDisabled) return this.renderDisabledElement()
    return this.props.message.blocks.schema.map((o, i)=>
       this.renderElement(o, i)
    )
  }

  handleStepControlClick = (item)=>{
    this.props.clickHandler(item, this.props)
  }

  sendAppPackageSubmit = (e)=>{
    e.preventDefault()
    const data = serialize(e.currentTarget, { hash: true, empty: true })

    this.props.appPackageSubmitHandler(data, this.props)
  }

  renderEmptyItem = ()=>{
    if(this.props.message.blocks.type === "app_package"){
      return
{this.props.message.blocks.app_package} replied

    }else{
      return
mo

    }
  }

  renderDisabledElement = ()=>{
    const item = this.props.message.data

    if(!item) return this.renderEmptyItem()

    switch(item.element){
      case "button":
        if (this.props.message.blocks.type === "ask_option"){
          return
choosen: {item.label}

        }
      default:
        if (this.props.message.blocks.type === "data_retrieval"){
          return Object.keys(this.props.message.data).map((k)=>{
            return
{k}: {this.props.message.data[k]}

          })
        } else{

{JSON.stringify(this.props.message.data)}

        }
    }
  }

  renderElement = (item, index)=>{
    const element = item.element
    const isDisabled = this.props.message.state === "replied"
    const {t} = this.props
    switch(item.element){
    case "separator":
      return

    case "input":
      return

              {t("enter_your", {field: item.name })}
              <input
                disabled={isDisabled}
                type={item.type}
                name={item.name}
                placeholder={t("enter_your", {field: item.name })}
                //onKeyDown={(e)=>{ e.keyCode === 13 ?
                //  this.handleStepControlClick(item) : null
                //}}
              />
              <button disabled={isDisabled}
                      key={index}
                      style={{alignSelf: 'flex-end'}}
                      type={"submit"}>
                {t("submit")}
              </button>

    case "submit":
      return <button disabled={isDisabled}
                     key={index}
                     style={{alignSelf: 'flex-end'}}
                     type={"submit"}>
          {t("submit")}
        </button>
    case "button":
      return <button
        disabled={isDisabled}
        onClick={()=> this.handleStepControlClick(item)}
        key={index}
        type={"button"}>
        {item.label}
        </button>
    default:
      return null
    }
  }

  render(){
    return <AppPackageBlockContainer>
              {
                true ? //!this.state.done ?
                <form ref={o => this.form }
                  onSubmit={ this.sendAppPackageSubmit }>
                  {
                    this.renderElements()
                  }
                </form> :
aa

              }
          </AppPackageBlockContainer>

  }
}

export function CommentsItemComp(props){

  const {
    displayConversation,
    message,
    o,
    sanitizeMessageSummary,
    email,
    index,
    t
  } = props

  const [display, setDisplay] = React.useState(false)

  React.useEffect(() => {
    setTimeout(()=> setDisplay(true), 400 ) // + (index * 100))
  }, [])

  return <CommentsItem
                display={display}
                key={o.id}
                onClick={(e) => { displayConversation(e, o) }}>

                {
                  message ?
                    <ConversationSummary>

                      <ConversationSummaryAvatar>
                        {
                          o.assignee &&
                        }

                      </ConversationSummaryAvatar>

                      <ConversationSummaryBody>

                        <ConversationSummaryBodyMeta>
                          {
                            !message.readAt && message.appUser && message.appUser.email !== email ?
                              <ReadIndicator /> : null
                          }
                          <Autor>
                            {o.assignee && o.assignee.displayName}
                          </Autor>

                          <Moment fromNow style={{
                            float: 'right',
                            color: '#ccc',
                            width: '115px',
                            margin: '0px 10px',
                            fontSize: '.8em',
                            textTransform: 'unset',
                            textAlign: 'right',
                            fontWeight: '100'
                          }}>
                            {message.createdAt}
                          </Moment>

                        </ConversationSummaryBodyMeta>
                        {/* TODO: sanitize in backend */}
                        {/**/}
                        <ConversationSummaryBodyItems>
                          {
                            message.appUser && message.appUser.kind != "agent" ?

{t("you")}:
 : null
                          }
                          <ConversationSummaryBodyContent
                            dangerouslySetInnerHTML={
                              { __html: sanitizeMessageSummary(message.message.htmlContent) }
                            }
                          />
                        </ConversationSummaryBodyItems>

                      </ConversationSummaryBody>
                    </ConversationSummary> : null
                }
              </CommentsItem>
}<commit_msg>Update conversation.js<commit_after>import React, { Component, Fragment } from "react";
import ReactDOM from "react-dom";
import { ThemeProvider } from 'emotion-theming'
import sanitizeHtml from 'sanitize-html';
import theme from './textEditor/theme'
import themeDark from './textEditor/darkTheme'
import DraftRenderer from './textEditor/draftRenderer'
import DanteContainer from './textEditor/editorStyles'
import gravatar from "./shared/gravatar"
import Moment from 'react-moment';
import serialize from 'form-serialize'
import UnicornEditor from './textEditor'
import {isEmpty} from 'lodash'
import {
  EditorSection,
  CommentsWrapper,
  CommentsItem,
  Footer,
  ReadIndicator,
  MessageItem,
  UserAutoChatAvatar,
  NewConvoBtn,
  ConversationSummary,
  ConversationSummaryAvatar,
  ConversationSummaryBody,
  ConversationSummaryBodyMeta,
  ConversationSummaryBodyContent,
  ConversationSummaryBodyItems,
  Autor,
  Hint,
  ConversationsFooter,
  MessageSpinner,
  AppPackageBlockContainer,
  ConversationEventContainer,
} from './styles/styled'

export class Conversations extends Component {

  componentDidMount(){
    this.props.clearAndGetConversations()

    this.props.updateHeader(
      {
        translateY: 0 ,
        opacity: 1,
        height: '0'
      }
    )
  }

  // TODO: skip on xhr progress
  handleConversationsScroll = (e) => {
    let element = e.target

    //console.log(element.scrollHeight - element.scrollTop , element.clientHeight)
    if (element.scrollHeight - element.scrollTop === element.clientHeight) {
      if (this.props.conversationsMeta.next_page)
        this.props.getConversations({ append: true })
    }
  }

  sanitizeMessageSummary = (message)=>{
    if(!message)
      return

    const sanitized = sanitizeHtml(message)
    return sanitized.length > 100 ? `${sanitized.substring(0, 100)} ...` : sanitized
  }

  render(){
    const {t} = this.props

    return

        <CommentsWrapper
          isMobile={this.props.isMobile}>
          {
            this.props.conversations.map((o, i) => {

              const message = o.lastMessage

              return <CommentsItemComp
                key={`comments-item-comp-${o.key}`}
                message={message}
                o={o}
                index={i}
                t={t}
                displayConversation={this.props.displayConversation}
                sanitizeMessageSummary={this.sanitizeMessageSummary}
              />
            })
          }
        </CommentsWrapper>

        <ConversationsFooter>
          <Hint>
            {this.props.app.tagline}
          </Hint>

          {
            this.props.app.inboundSettings.enabled &&
            <NewConvoBtn
              in={this.props.transition}
              onClick={this.props.displayNewConversation}>
              {t("create_new_conversation")}
            </NewConvoBtn>
          }
        </ConversationsFooter>

  }
}

export class Conversation extends Component {

  componentDidMount(){
    this.props.updateHeader(
      {
        translateY: 0 ,
        opacity: 1,
        height: '0'
      }
    )
  }

  componentWillUnmount(){
    if(!this.props.inline_conversation)
      this.props.clearConversation()
  }

  // TODO: skip on xhr progress
  handleConversationScroll = (e) => {

    if(this.props.disablePagination) return

    let element = e.target
    //console.log(element.scrollTop)
    //console.log(element.scrollHeight - element.scrollTop, element.clientHeight) // on bottom
    if (element.scrollTop === 0) { // on top

      this.props.updateHeader(
        {
          translateY: 0 ,
          opacity: 1,
          height: 212
        }
      )

    //if (element.scrollTop <= 50) { // on almost top // todo skip on xhr loading
      if (this.props.conversation.messages.meta.next_page)
        this.props.setConversation(this.props.conversation.key)
    } else {
      this.props.updateHeader(
        {
          translateY: 0 ,
          opacity: 1,
          height: 0
        }
      )
    }
  }

  renderMessage = (o, i)=>{
    const userClass = o.appUser.kind === "agent" ? 'admin' : 'user'
    const isAgent = o.appUser.kind === "agent"
    const themeforMessage = o.privateNote || isAgent ? theme : themeDark
    const {t} = this.props

    return <MessageItemWrapper
            visible={this.props.visible}
            email={this.props.email}
            key={`conversation-item-${o.id}`}
            conversation={this.props.conversation}
            pushEvent={this.props.pushEvent}
            data={o}>

              <MessageItem
                className={userClass}
                messageSourceType={o.messageSource ? o.messageSource.type : ''}
              >

              {
                !this.props.isUserAutoMessage(o) && isAgent ?
                <ConversationSummaryAvatar>

                </ConversationSummaryAvatar> : null
              }

                {
                  this.props.isUserAutoMessage(o) ?
                    <UserAutoChatAvatar>

                      {o.appUser.name || o.appUser.email}
                    </UserAutoChatAvatar> : null
                }

                {/*render light theme on user or private note*/}

                <ThemeProvider
                  theme={ themeforMessage }>
                  <DanteContainer>
                    <DraftRenderer
                      key={i}
                      message={o}
                      domain={this.props.domain}
                      raw={JSON.parse(o.message.serializedContent)}
                    />

                      {
                        o.readAt ?
                          <Moment fromNow>
                            {o.readAt}
                          </Moment> : {t("not_seen")}
                      }

                  </DanteContainer>
                </ThemeProvider>

            </MessageItem>

          </MessageItemWrapper>
  }

  renderItemPackage = (o, i)=>{
    return  <AppPackageBlock
               key={i}
               message={o}
               conversation={this.props.conversation}
               submitAppUserData={this.props.submitAppUserData.bind(this)}
               clickHandler={this.appPackageClickHandler.bind(this)}
               appPackageSubmitHandler={this.appPackageSubmitHandler.bind(this)}
               t={this.props.t}
               {...o}
              />
  }

  renderEventBlock = (o, i)=>{
    const {data, action} = o.message
    return <ConversationEventContainer>

              {this.props.t(`conversations.events.${action}`, data)}

           </ConversationEventContainer>
  }

  appPackageBlockDisplay = (message)=>{
    this.props.displayAppBlockFrame(message)
  }

  appPackageClickHandler = (item, message)=>{
    // run app block display here! refactor
    if (message.message.blocks.type === "app_package") return this.appPackageBlockDisplay(message)
    this.props.pushEvent('trigger_step', {
      conversation_id: this.props.conversation.key,
      message_id: message.id,
      trigger: message.triggerId,
      step: item.nextStepUuid || item.next_step_uuid,
      reply: item
    })

  }

  appPackageSubmitHandler = (data, message)=>{
    this.props.pushEvent("receive_conversation_part",
      {
        conversation_id: this.props.conversation.key,
        message_id: message.id,
        step: this.props.stepId,
        trigger: this.props.TriggerId,
        submit: data
      })
  }

  renderTyping = ()=>{
    return <MessageItem>

              <MessageSpinner>

              </MessageSpinner>

                {this.props.agent_typing.author.name || 'agent'}
                is typing

           </MessageItem>
  }

  isInputEnabled =()=>{

    if(isEmpty(this.props.conversation.messages)) return true

    const messages = this.props.conversation.messages.collection
    if( messages.length === 0 ) return true

    const message = messages[0].message
    if(isEmpty(message.blocks)) return true
    return message.state === "replied"
  }

  render(){

    const {t} = this.props
    console.log("ENABLED???", this.isInputEnabled())
    return

 this.props.setOverflow(comp) }
        onScroll={this.handleConversationScroll}
        style={{ overflowY: 'auto', height: '100%' }}>

        <EditorSection>

          <CommentsWrapper
            isReverse={true}
            //ref={this.commentWrapperRef}
            isMobile={this.props.isMobile}>

            {
              this.props.agent_typing && this.renderTyping()
            }
            {
              this.props.conversation.messages && this.props.conversation.messages.collection.map((o, i) => {
                  if(o.message.blocks) return this.renderItemPackage(o, i)
                  if(o.message.action) return this.renderEventBlock(o, i)
                  return this.renderMessage(o, i)
              })
            }

          </CommentsWrapper>

            {
              !this.isInputEnabled() ? t("reply_above") :
              <UnicornEditor
                footerClassName={this.props.footerClassName }
                insertComment={this.props.insertComment}
              />
            }

        </EditorSection>

  }

}

class MessageItemWrapper extends Component {
  componentDidMount(){
    // mark as read on first render if not read & from admin
    this.sendEvent()
  }

  componentDidUpdate(prevProps, prevState){
    if(prevProps && prevProps.visible != this.props.visible && this.props.visible)
      this.sendEvent()
  }

  sendEvent = ()=>{
    if(this.props.visible && !this.props.data.volatile && !this.props.data.readAt && this.props.data.appUser.kind === "agent"){
      this.props.pushEvent("receive_conversation_part",
        Object.assign({}, {
          conversation_id: this.props.conversation.key,
          message_id: this.props.data.id,
          step: this.props.stepId,
          trigger: this.props.TriggerId
        }, {email: this.props.email})
      )
    }
  }

  render(){
    return <Fragment>
            {this.props.children}
           </Fragment>
  }
}

class AppPackageBlock extends Component {

  form = null

  state = {
    value: null
  }

  renderElements = ()=>{
    const isDisabled = this.props.message.state === "replied"
    if(isDisabled) return this.renderDisabledElement()
    return this.props.message.blocks.schema.map((o, i)=>
       this.renderElement(o, i)
    )
  }

  handleStepControlClick = (item)=>{
    this.props.clickHandler(item, this.props)
  }

  sendAppPackageSubmit = (e)=>{
    e.preventDefault()
    const data = serialize(e.currentTarget, { hash: true, empty: true })

    this.props.appPackageSubmitHandler(data, this.props)
  }

  renderEmptyItem = ()=>{
    if(this.props.message.blocks.type === "app_package"){
      return
{this.props.message.blocks.app_package} replied

    }else{
      return
mo

    }
  }

  renderDisabledElement = ()=>{
    const item = this.props.message.data

    if(!item) return this.renderEmptyItem()

    switch(item.element){
      case "button":
        if (this.props.message.blocks.type === "ask_option"){
          return
choosen: {item.label}

        }
      default:
        if (this.props.message.blocks.type === "data_retrieval"){
          return Object.keys(this.props.message.data).map((k)=>{
            return
{k}: {this.props.message.data[k]}

          })
        } else{

{JSON.stringify(this.props.message.data)}

        }
    }
  }

  renderElement = (item, index)=>{
    const element = item.element
    const isDisabled = this.props.message.state === "replied"
    const {t} = this.props
    switch(item.element){
    case "separator":
      return

    case "input":
      return

              {t("enter_your", {field: item.name })}
              <input
                disabled={isDisabled}
                type={item.type}
                name={item.name}
                placeholder={t("enter_your", {field: item.name })}
                //onKeyDown={(e)=>{ e.keyCode === 13 ?
                //  this.handleStepControlClick(item) : null
                //}}
              />
              <button disabled={isDisabled}
                      key={index}
                      style={{alignSelf: 'flex-end'}}
                      type={"submit"}>
                {t("submit")}
              </button>

    case "submit":
      return <button disabled={isDisabled}
                     key={index}
                     style={{alignSelf: 'flex-end'}}
                     type={"submit"}>
          {t("submit")}
        </button>
    case "button":
      return <button
        disabled={isDisabled}
        onClick={()=> this.handleStepControlClick(item)}
        key={index}
        type={"button"}>
        {item.label}
        </button>
    default:
      return null
    }
  }

  render(){
    return <AppPackageBlockContainer>
              {
                true ? //!this.state.done ?
                <form ref={o => this.form }
                  onSubmit={ this.sendAppPackageSubmit }>
                  {
                    this.renderElements()
                  }
                </form> :
aa

              }
          </AppPackageBlockContainer>

  }
}

export function CommentsItemComp(props){

  const {
    displayConversation,
    message,
    o,
    sanitizeMessageSummary,
    email,
    index,
    t
  } = props

  const [display, setDisplay] = React.useState(false)

  React.useEffect(() => {
    setTimeout(()=> setDisplay(true), 400 ) // + (index * 100))
  }, [])

  return <CommentsItem
                display={display}
                key={o.id}
                onClick={(e) => { displayConversation(e, o) }}>

                {
                  message ?
                    <ConversationSummary>

                      <ConversationSummaryAvatar>
                        {
                          o.assignee &&
                        }

                      </ConversationSummaryAvatar>

                      <ConversationSummaryBody>

                        <ConversationSummaryBodyMeta>
                          {
                            !message.readAt && message.appUser && message.appUser.email !== email ?
                              <ReadIndicator /> : null
                          }
                          <Autor>
                            {o.assignee && o.assignee.displayName}
                          </Autor>

                          <Moment fromNow style={{
                            float: 'right',
                            color: '#ccc',
                            width: '115px',
                            margin: '0px 10px',
                            fontSize: '.8em',
                            textTransform: 'unset',
                            textAlign: 'right',
                            fontWeight: '100'
                          }}>
                            {message.createdAt}
                          </Moment>

                        </ConversationSummaryBodyMeta>
                        {/* TODO: sanitize in backend */}
                        {/**/}
                        <ConversationSummaryBodyItems>
                          {
                            message.appUser && message.appUser.kind != "agent" ?

{t("you")}:
 : null
                          }
                          <ConversationSummaryBodyContent
                            dangerouslySetInnerHTML={
                              { __html: sanitizeMessageSummary(message.message.htmlContent) }
                            }
                          />
                        </ConversationSummaryBodyItems>

                      </ConversationSummaryBody>
                    </ConversationSummary> : null
                }
              </CommentsItem>
}<|endoftext|>


<commit_before>import { document } from 'global';
import { unregister, tag2, mount } from 'riot/riot';
import compiler from 'riot-compiler';
import { render } from './render-riot';

document.body = document.createElement('body');
const rootElement = document.body.appendChild(document.createElement('root'));

const context = {
  unregister,
  rootElement,
  compiler,
  tag2,
  mount,
};

beforeEach(() => {
  rootElement.innerHTML = '';
  unregister('root');
});

describe('render a riot element', () => {
  it('should not work with nothing', () => {
    expect(render(null, context)).toBe(false);

    expect(rootElement.innerHTML).toEqual('');
  });

  it('can work with some text', () => {
    expect(render({ tags: ['

some tests

'] }, context)).toBe(true);

    expect(rootElement.innerHTML).toEqual('

some tests

');
  });

  it('can work with raw code', () => {
    expect(render("riot.tag2('root', '
raw code
', '', '', () => {})", context)).toBe(
      true
    );

    expect(rootElement.innerHTML).toEqual('
raw code
');
  });

  it('can work with compiled code', () => {
    expect(render([], context)).toBe(true);
    // does only work in true mode, and not in jest mode
  });

  it('can nest several tags', () => {
    expect(
      render(
        {
          tags: [
            '<Tag1>
Inside tag1:

<Tag2><yield/></Tag2>

</Tag1>',
            '<Tag2>
Inside tag2:

<Tag3><yield/></Tag3>

</Tag2>',
            '<Tag3>
Inside tag3:

<Tag4><yield/></Tag4>

</Tag3>',
            '<Tag4>
Inside tag4:

<Tag5><yield/></Tag5>

</Tag4>',
            '<Tag5>
Inside tag5:

<yield/>

</Tag5>',
          ],
          template: '<Matriochka>
<Tag1>Content</Tag1>
</Matriochka>',
        },
        context
      )
    ).toBe(true);

    expect(rootElement.innerHTML).toMatchSnapshot();
  });

  it('can template some vars', () => {
    expect(
      render(
        {
          tags: [
            {
              content:
                "<SimpleTest>
simple test ({opts.test || 'without parameter'}). Oh, by the way ({opts.riotValue || '... well, nothing'})
</SimpleTest>",
              boundAs: 'mustBeUniquePlease',
            },
          ],
          template:
            '<SimpleTest test={ "with a parameter" } value={"value is mapped to riotValue"}></SimpleTest>',
        },
        context
      )
    ).toBe(true);

    expect(rootElement.innerHTML).toMatchSnapshot();
  });
});
<commit_msg>fixing unit tests<commit_after>import { document } from 'global';
import { unregister, tag2, mount } from 'riot/riot';
import compiler from 'riot-compiler';
import { render } from './render-riot';

const rootElement = document.createElement('div');
rootElement.id = 'root';
document.body = document.createElement('body');
document.body.appendChild(rootElement);

const context = {
  unregister,
  rootElement,
  compiler,
  tag2,
  mount,
};

beforeEach(() => {
  rootElement.innerHTML = '';
  unregister('root');
});

describe('render a riot element', () => {
  it('should not work with nothing', () => {
    expect(render(null, context)).toBe(false);

    expect(rootElement.innerHTML).toEqual('');
  });

  it('can work with some text', () => {
    expect(render({ tags: ['

some tests

'] }, context)).toBe(true);

    expect(rootElement.innerHTML).toEqual('

some tests

');
  });

  it('can work with raw code', () => {
    expect(render("riot.tag2('root', '
raw code
', '', '', () => {})", context)).toBe(
      true
    );

    expect(rootElement.innerHTML).toEqual('
raw code
');
  });

  it('can work with compiled code', () => {
    expect(render([{}], context)).toBe(true);
    // does only work in true mode, and not in jest mode
  });

  it('can nest several tags', () => {
    expect(
      render(
        {
          tags: [
            '<Tag1>
Inside tag1:

<Tag2><yield/></Tag2>

</Tag1>',
            '<Tag2>
Inside tag2:

<Tag3><yield/></Tag3>

</Tag2>',
            '<Tag3>
Inside tag3:

<Tag4><yield/></Tag4>

</Tag3>',
            '<Tag4>
Inside tag4:

<Tag5><yield/></Tag5>

</Tag4>',
            '<Tag5>
Inside tag5:

<yield/>

</Tag5>',
          ],
          template: '<Matriochka>
<Tag1>Content</Tag1>
</Matriochka>',
        },
        context
      )
    ).toBe(true);

    expect(rootElement.innerHTML).toMatchSnapshot();
  });

  it('can template some vars', () => {
    expect(
      render(
        {
          tags: [
            {
              content:
                "<SimpleTest>
simple test ({opts.test || 'without parameter'}). Oh, by the way ({opts.riotValue || '... well, nothing'})
</SimpleTest>",
              boundAs: 'mustBeUniquePlease',
            },
          ],
          template:
            '<SimpleTest test={ "with a parameter" } value={"value is mapped to riotValue"}></SimpleTest>',
        },
        context
      )
    ).toBe(true);

    expect(rootElement.innerHTML).toMatchSnapshot();
  });
});
<|endoftext|>


<commit_before>//@flow

import React from 'react';
import { Flex } from 'app/components/Layout';
import { Content } from 'app/components/Content';
import Button from 'app/components/Button';
import { type UserEntity } from 'app/reducers/users';
import { type ArticleEntity } from 'app/reducers/articles';
import LoadingIndicator from 'app/components/LoadingIndicator';
import styles from './ArticleEditor.css';
import { normalizeObjectPermissions } from 'app/components/Form/ObjectPermissions';
import {
  EditorField,
  TextInput,
  TextArea,
  SelectInput,
  CheckBox,
  ObjectPermissions,
  ImageUploadField,
  legoForm
} from 'app/components/Form';
import { Form, Fields, Field } from 'redux-form';

/**
 *
 */
export type Props = {
  article?: ArticleEntity,
  articleId: number,
  currentUser: UserEntity,
  isNew: boolean,
  handleSubmit: Object => void,
  submitArticle: Object => Promise<*>,
  deleteArticle: number => Promise<*>,
  push: string => void
};

const ArticleEditor = ({
  isNew,
  articleId,
  currentUser,
  submitArticle,
  handleSubmit,
  deleteArticle,
  push,
  article
}: Props) => {
  if (!isNew && (!article || !article.content)) {
    return <LoadingIndicator loading />;
  }

  const handleDeleteArticle = () => {
    deleteArticle(articleId).then(() => {
      push('/articles/');
    });
  };

  return (
    <Content>
      <Form onSubmit={handleSubmit}>
        <Field
          name="cover"
          label="Cover"
          component={ImageUploadField}
          aspectRatio={20 / 6}
          img={article && article.cover}
        />
        <Field
          label="Festet på forsiden"
          name="pinned"
          component={CheckBox.Field}
          fieldClassName={styles.metaField}
          className={styles.formField}
          normalize={v => !!v}
        />
        <Field
          placeholder="Title"
          name="title"
          label="Tittel"
          component={TextInput.Field}
          id="article-title"
        />
        <Field
          name="tags"
          label="Tags"
          filter={['tags.tag']}
          placeholder="Skriv inn tags"
          component={SelectInput.AutocompleteField}
          multi
          tags
          shouldKeyDownEventCreateNewOption={({
            keyCode
          }: {
            keyCode: number
          }) => keyCode === 32 || keyCode === 13}
        />

        <Fields
          names={[
            'requireAuth',
            'canViewGroups',
            'canEditUsers',
            'canEditGroups'
          ]}
          component={ObjectPermissions}
        />
        <Field
          placeholder="En kort beskrivelse av artikkelen"
          name="description"
          label="Beskrivelse"
          component={TextArea.Field}
          id="article-title"
        />
        <Field
          placeholder="Write your article here..."
          name="content"
          label="Content"
          component={EditorField.Field}
        />
        <Button className={styles.submitButton} type="submit">
          {isNew ? 'Create' : 'Save'}
        </Button>
      </Form>
      {!isNew && <Button onClick={handleDeleteArticle}>Delete</Button>}
    </Content>
  );
};

const onSubmit = (
  data,
  dispatch,
  { currentUser, isNew, articleId, submitArticle }: Props
) => {
  const body = {
    ...(isNew ? {} : { id: articleId }),
    ...(data.cover ? { cover: data.cover } : {}),
    ...normalizeObjectPermissions(data),
    title: data.title,
    author: currentUser.id,
    description: data.description,
    content: data.content,
    tags: (data.tags || []).map(tag => tag.value.toLowerCase()),
    pinned: data.pinned
  };

  return submitArticle(body);
};

export default legoForm({
  destroyOnUnmount: false,
  form: 'article',
  enableReinitialize: true,
  onSubmit
})(ArticleEditor);
<commit_msg>Fix lint<commit_after>//@flow

import React from 'react';
import { Content } from 'app/components/Content';
import Button from 'app/components/Button';
import { type UserEntity } from 'app/reducers/users';
import { type ArticleEntity } from 'app/reducers/articles';
import LoadingIndicator from 'app/components/LoadingIndicator';
import styles from './ArticleEditor.css';
import { normalizeObjectPermissions } from 'app/components/Form/ObjectPermissions';
import {
  EditorField,
  TextInput,
  TextArea,
  SelectInput,
  CheckBox,
  ObjectPermissions,
  ImageUploadField,
  legoForm
} from 'app/components/Form';
import { Form, Fields, Field } from 'redux-form';

/**
 *
 */
export type Props = {
  article?: ArticleEntity,
  articleId: number,
  currentUser: UserEntity,
  isNew: boolean,
  handleSubmit: Object => void,
  submitArticle: Object => Promise<*>,
  deleteArticle: number => Promise<*>,
  push: string => void
};

const ArticleEditor = ({
  isNew,
  articleId,
  currentUser,
  submitArticle,
  handleSubmit,
  deleteArticle,
  push,
  article
}: Props) => {
  if (!isNew && (!article || !article.content)) {
    return <LoadingIndicator loading />;
  }

  const handleDeleteArticle = () => {
    deleteArticle(articleId).then(() => {
      push('/articles/');
    });
  };

  return (
    <Content>
      <Form onSubmit={handleSubmit}>
        <Field
          name="cover"
          label="Cover"
          component={ImageUploadField}
          aspectRatio={20 / 6}
          img={article && article.cover}
        />
        <Field
          label="Festet på forsiden"
          name="pinned"
          component={CheckBox.Field}
          fieldClassName={styles.metaField}
          className={styles.formField}
          normalize={v => !!v}
        />
        <Field
          placeholder="Title"
          name="title"
          label="Tittel"
          component={TextInput.Field}
          id="article-title"
        />
        <Field
          name="tags"
          label="Tags"
          filter={['tags.tag']}
          placeholder="Skriv inn tags"
          component={SelectInput.AutocompleteField}
          multi
          tags
          shouldKeyDownEventCreateNewOption={({
            keyCode
          }: {
            keyCode: number
          }) => keyCode === 32 || keyCode === 13}
        />

        <Fields
          names={[
            'requireAuth',
            'canViewGroups',
            'canEditUsers',
            'canEditGroups'
          ]}
          component={ObjectPermissions}
        />
        <Field
          placeholder="En kort beskrivelse av artikkelen"
          name="description"
          label="Beskrivelse"
          component={TextArea.Field}
          id="article-title"
        />
        <Field
          placeholder="Write your article here..."
          name="content"
          label="Content"
          component={EditorField.Field}
        />
        <Button className={styles.submitButton} type="submit">
          {isNew ? 'Create' : 'Save'}
        </Button>
      </Form>
      {!isNew && <Button onClick={handleDeleteArticle}>Delete</Button>}
    </Content>
  );
};

const onSubmit = (
  data,
  dispatch,
  { currentUser, isNew, articleId, submitArticle }: Props
) => {
  const body = {
    ...(isNew ? {} : { id: articleId }),
    ...(data.cover ? { cover: data.cover } : {}),
    ...normalizeObjectPermissions(data),
    title: data.title,
    author: currentUser.id,
    description: data.description,
    content: data.content,
    tags: (data.tags || []).map(tag => tag.value.toLowerCase()),
    pinned: data.pinned
  };

  return submitArticle(body);
};

export default legoForm({
  destroyOnUnmount: false,
  form: 'article',
  enableReinitialize: true,
  onSubmit
})(ArticleEditor);
<|endoftext|>


<commit_before>/* global angular Firebase */
(function(){
  'use strict';

  angular.module('iron-pong')
    .controller('LeaderboardController',
      function($scope, $filter, $firebase, $firebaseArray, ngTableParams){

      var players = [
        {avatar_url: 'http://lorempixel.com/120/120/people/1', login: 'sjoyal',
        wins: '55', losses: '14', pct: '.757', games: '69'},
        {avatar_url: 'http://lorempixel.com/120/120/people/2', login: 'pcreglow',
        wins: '40', losses: '30', pct: '.568', games: '70'},
        {avatar_url: 'http://lorempixel.com/120/120/people/3', login: 'jorgehjr84',
        wins: '8', losses: '40', pct: '.213', games: '48'},
        {avatar_url: 'http://lorempixel.com/120/120/people/4', login: 'al-the-x',
        wins: '0', losses: '125', pct: '.000', games: '125'},
        {avatar_url: 'http://lorempixel.com/120/120/people/5', login: 'gatorpazz',
        wins: '0', losses: '0', pct: '.000', games: '0'},
        {avatar_url: 'http://lorempixel.com/120/120/people/6', login: 'jessyriordan',
        wins: '0', losses: '0', pct: '.000', games: '0'},
        {avatar_url: 'http://lorempixel.com/120/120/people/7', login: 'mstaehling',
        wins: '0', losses: '0', pct: '.000', games: '0'}
      ] // END players

      $scope.tableParams = new ngTableParams({
        sorting: {
          wins: 'asc'  // initial sorting
        }
      }, {
        getData: function($defer, params){
            $defer.resolve($filter('orderBy')(players, params.orderBy()));
        }
      });
    }); // END LeaderboardController

})();
<commit_msg>ng-table not sorting properly and I want to break my computer<commit_after>/* global angular Firebase */
(function(){
  'use strict';

  angular.module('iron-pong')
    .controller('LeaderboardController',
      function($scope, $filter, $firebase, $firebaseArray, ngTableParams){

      $scope.players = [
        {avatar_url: 'http://lorempixel.com/120/120/people/1', login: 'sjoyal',
        wins: '55', losses: '14', pct: '.757', games: '69'},
        {avatar_url: 'http://lorempixel.com/120/120/people/2', login: 'pcreglow',
        wins: '40', losses: '30', pct: '.568', games: '70'},
        {avatar_url: 'http://lorempixel.com/120/120/people/3', login: 'jorgehjr84',
        wins: '8', losses: '40', pct: '.213', games: '48'},
        {avatar_url: 'http://lorempixel.com/120/120/people/4', login: 'al-the-x',
        wins: '0', losses: '125', pct: '.000', games: '125'},
        {avatar_url: 'http://lorempixel.com/120/120/people/5', login: 'gatorpazz',
        wins: '0', losses: '0', pct: '.000', games: '0'},
        {avatar_url: 'http://lorempixel.com/120/120/people/6', login: 'jessyriordan',
        wins: '0', losses: '0', pct: '.000', games: '0'},
        {avatar_url: 'http://lorempixel.com/120/120/people/7', login: 'mstaehling',
        wins: '0', losses: '0', pct: '.000', games: '0'}
      ] // END players

      $scope.tableParams = new ngTableParams({
        sorting: {
          wins: 'desc'  // initial sorting
        }
      }, {
        getData: function($defer, params){
          var data = $scope.players;
            $defer.resolve($filter('orderBy')(data, params.orderBy()));
        }
      });
    }); // END LeaderboardController

})();
<|endoftext|>