<commit_before>JSONEditor.defaults.editors.select = JSONEditor.AbstractEditor.extend({
  setValue: function(value,initial) {
    value = this.typecast(value||'');

    // Sanitize value before setting it
    var sanitized = value;
    if(this.enum_values.indexOf(sanitized) < 0) {
      sanitized = this.enum_values[0];
    }

    if(this.value === sanitized) {
      return;
    }

    if(initial) this.is_dirty = false;
    else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true;

    this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)];
    if(this.select2) {
      if(this.select2v4)
        this.select2.val(this.input.value).trigger("change");
      else
        this.select2.select2('val',this.input.value);
    }
    this.value = sanitized;
    this.onChange();
    this.change();
  },
  register: function() {
    this._super();
    if(!this.input) return;
    this.input.setAttribute('name',this.formname);
  },
  unregister: function() {
    this._super();
    if(!this.input) return;
    this.input.removeAttribute('name');
  },
  getNumColumns: function() {
    if(!this.enum_options) return 3;
    var longest_text = this.getTitle().length;
    for(var i=0; i<this.enum_options.length; i++) {
      longest_text = Math.max(longest_text,this.enum_options[i].length+4);
    }
    return Math.min(12,Math.max(longest_text/7,2));
  },
  typecast: function(value) {
    if(this.schema.type === "boolean") {
      return !!value;
    }
    else if(this.schema.type === "number") {
      return 1*value;
    }
    else if(this.schema.type === "integer") {
      return Math.floor(value*1);
    }
    else {
      return ""+value;
    }
  },
  getValue: function() {
    if (!this.dependenciesFulfilled) {
      return undefined;
    }
    return this.typecast(this.value);
  },
  preBuild: function() {
    var self = this;
    this.input_type = 'select';
    this.enum_options = [];
    this.enum_values = [];
    this.enum_display = [];
    var i;

    // Enum options enumerated
    if(this.schema["enum"]) {
      var display = this.schema.options && this.schema.options.enum_titles || [];

      $each(this.schema["enum"],function(i,option) {
        self.enum_options[i] = ""+option;
        self.enum_display[i] = ""+(display[i] || option);
        self.enum_values[i] = self.typecast(option);
      });

      if(!this.isRequired()){
        self.enum_display.unshift(' ');
        self.enum_options.unshift('undefined');
        self.enum_values.unshift(undefined);
      }

    }
    // Boolean
    else if(this.schema.type === "boolean") {
      self.enum_display = this.schema.options && this.schema.options.enum_titles || ['true','false'];
      self.enum_options = ['1',''];
      self.enum_values = [true,false];

      if(!this.isRequired()){
        self.enum_display.unshift(' ');
        self.enum_options.unshift('undefined');
        self.enum_values.unshift(undefined);
      }

    }
    // Dynamic Enum
    else if(this.schema.enumSource) {
      this.enumSource = [];
      this.enum_display = [];
      this.enum_options = [];
      this.enum_values = [];

      // Shortcut declaration for using a single array
      if(!(Array.isArray(this.schema.enumSource))) {
        if(this.schema.enumValue) {
          this.enumSource = [
            {
              source: this.schema.enumSource,
              value: this.schema.enumValue
            }
          ];
        }
        else {
          this.enumSource = [
            {
              source: this.schema.enumSource
            }
          ];
        }
      }
      else {
        for(i=0; i<this.schema.enumSource.length; i++) {
          // Shorthand for watched variable
          if(typeof this.schema.enumSource[i] === "string") {
            this.enumSource[i] = {
              source: this.schema.enumSource[i]
            };
          }
          // Make a copy of the schema
          else if(!(Array.isArray(this.schema.enumSource[i]))) {
            this.enumSource[i] = $extend({},this.schema.enumSource[i]);
          }
          else {
            this.enumSource[i] = this.schema.enumSource[i];
          }
        }
      }

      // Now, enumSource is an array of sources
      // Walk through this array and fix up the values
      for(i=0; i<this.enumSource.length; i++) {
        if(this.enumSource[i].value) {
          this.enumSource[i].value = this.jsoneditor.compileTemplate(this.enumSource[i].value, this.template_engine);
        }
        if(this.enumSource[i].title) {
          this.enumSource[i].title = this.jsoneditor.compileTemplate(this.enumSource[i].title, this.template_engine);
        }
        if(this.enumSource[i].filter) {
          this.enumSource[i].filter = this.jsoneditor.compileTemplate(this.enumSource[i].filter, this.template_engine);
        }
      }
    }
    // Other, not supported
    else {
      throw "'select' editor requires the enum property to be set.";
    }
  },
  build: function() {
    var self = this;
    if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
    if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
    if(this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText);
    if(this.options.compact) this.container.classList.add('compact');

    this.input = this.theme.getSelectInput(this.enum_options);
    this.theme.setSelectOptions(this.input,this.enum_options,this.enum_display);

    if(this.schema.readOnly || this.schema.readonly) {
      this.always_disabled = true;
      this.input.disabled = true;
    }

    // Set custom attributes on input element. Parameter is array of protected keys. Empty array if none.
    this.setInputAttributes([]);

    this.input.addEventListener('change',function(e) {
      e.preventDefault();
      e.stopPropagation();
      self.onInputChange();
    });

    this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton);
    this.container.appendChild(this.control);

    this.value = this.enum_values[0];
  },
  onInputChange: function() {
    var val = this.typecast(this.input.value);

    var new_val;
    // Invalid option, use first option instead
    if(this.enum_values.indexOf(val) === -1) {
      new_val = this.enum_values[0];
    }
    else {
      new_val = this.enum_values[this.enum_values.indexOf(val)];
    }

    // If valid hasn't changed
    if(new_val === this.value) return;

    this.is_dirty = true;

    // Store new value and propogate change event
    this.value = new_val;
    this.onChange(true);
  },
  setupSelect2: function() {
    // If the Select2 library is loaded use it when we have lots of items
    if(window.jQuery && window.jQuery.fn && window.jQuery.fn.select2 && (this.enum_options.length > 2 || (this.enum_options.length && this.enumSource))) {
      var options = $extend({},JSONEditor.plugins.select2);
      if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options);
      this.select2 = window.jQuery(this.input).select2(options);
      this.select2v4 = this.select2.select2.hasOwnProperty("amd");

      var self = this;
      this.select2.on('select2-blur',function() {
        if(self.select2v4)
          self.input.value = self.select2.val();
        else
          self.input.value = self.select2.select2('val');

        self.onInputChange();
      });

      this.select2.on('change',function() {
        if(self.select2v4)
          self.input.value = self.select2.val();
        else
          self.input.value = self.select2.select2('val');

        self.onInputChange();
      });
    }
    else {
      this.select2 = null;
    }
  },
  postBuild: function() {
    this._super();
    this.theme.afterInputReady(this.input);
    this.setupSelect2();
  },
  onWatchedFieldChange: function() {
    var self = this, vars, j;

    // If this editor uses a dynamic select box
    if(this.enumSource) {
      vars = this.getWatchedFieldValues();
      var select_options = [];
      var select_titles = [];

      for(var i=0; i<this.enumSource.length; i++) {
        // Constant values
        if(Array.isArray(this.enumSource[i])) {
          select_options = select_options.concat(this.enumSource[i]);
          select_titles = select_titles.concat(this.enumSource[i]);
        }
        else {
          var items = [];
          // Static list of items
          if(Array.isArray(this.enumSource[i].source)) {
            items = this.enumSource[i].source;
          // A watched field
          } else {
            items = vars[this.enumSource[i].source];
          }

          if(items) {
            // Only use a predefined part of the array
            if(this.enumSource[i].slice) {
              items = Array.prototype.slice.apply(items,this.enumSource[i].slice);
            }
            // Filter the items
            if(this.enumSource[i].filter) {
              var new_items = [];
              for(j=0; j<items.length; j++) {
                if(this.enumSource[i].filter({i:j,item:items[j],watched:vars})) new_items.push(items[j]);
              }
              items = new_items;
            }

            var item_titles = [];
            var item_values = [];
            for(j=0; j<items.length; j++) {
              var item = items[j];

              // Rendered value
              if(this.enumSource[i].value) {
                item_values[j] = this.enumSource[i].value({
                  i: j,
                  item: item
                });
              }
              // Use value directly
              else {
                item_values[j] = items[j];
              }

              // Rendered title
              if(this.enumSource[i].title) {
                item_titles[j] = this.enumSource[i].title({
                  i: j,
                  item: item
                });
              }
              // Use value as the title also
              else {
                item_titles[j] = item_values[j];
              }
            }

            // TODO: sort

            select_options = select_options.concat(item_values);
            select_titles = select_titles.concat(item_titles);
          }
        }
      }

      var prev_value = this.value;

      this.theme.setSelectOptions(this.input, select_options, select_titles);
      this.enum_options = select_options;
      this.enum_display = select_titles;
      this.enum_values = select_options;

      if(this.select2) {
        this.select2.select2('destroy');
      }

      // If the previous value is still in the new select options, stick with it
      if(select_options.indexOf(prev_value) !== -1) {
        this.input.value = prev_value;
        this.value = prev_value;
      }
      // Otherwise, set the value to the first select option
      else {
        this.input.value = select_options[0];
        this.value = this.typecast(select_options[0] || "");
        if(this.parent) this.parent.onChildEditorChange(this);
        else this.jsoneditor.onChange();
        this.jsoneditor.notifyWatchers(this.path);
      }

      this.setupSelect2();
    }

    this._super();
  },
  enable: function() {
    if(!this.always_disabled) {
      this.input.disabled = false;
      if(this.select2) {
        if(this.select2v4)
          this.select2.prop("disabled",false);
        else
          this.select2.select2("enable",true);
      }
    }
    this._super();
  },
  disable: function(always_disabled) {
    if(always_disabled) this.always_disabled = true;
    this.input.disabled = true;
    if(this.select2) {
      if(this.select2v4)
        this.select2.prop("disabled",true);
      else
        this.select2.select2("enable",false);
    }
    this._super();
  },
  destroy: function() {
    if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
    if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
    if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
    if(this.select2) {
      this.select2.select2('destroy');
      this.select2 = null;
    }

    this._super();
  },
  showValidationErrors: function (errors) {
    var self = this;

    if (this.jsoneditor.options.show_errors === "always") {}
    else if(!this.is_dirty && this.previous_error_setting===this.jsoneditor.options.show_errors) return;

    this.previous_error_setting = this.jsoneditor.options.show_errors;

    var messages = [];
    $each(errors, function (i, error) {
      if (error.path === self.path) {
        messages.push(error.message);
      }
    });

    if (messages.length) {
      this.theme.addInputError(this.input, messages.join('. ') + '.');
    }
    else {
      this.theme.removeInputError(this.input);
    }
  }
});
<commit_msg>fix of #270<commit_after>JSONEditor.defaults.editors.select = JSONEditor.AbstractEditor.extend({
  setValue: function(value,initial) {
    value = this.typecast(value||'');

    // Sanitize value before setting it
    var sanitized = value;
    if(this.enum_values.indexOf(sanitized) < 0) {
      sanitized = this.enum_values[0];
    }

    if(this.value === sanitized) {
      return;
    }

    if(initial) this.is_dirty = false;
    else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true;

    this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)];
    if(this.select2) {
      if(this.select2v4)
        this.select2.val(this.input.value).trigger("change");
      else
        this.select2.select2('val',this.input.value);
    }
    this.value = sanitized;
    this.onChange();
    this.change();
  },
  register: function() {
    this._super();
    if(!this.input) return;
    this.input.setAttribute('name',this.formname);
  },
  unregister: function() {
    this._super();
    if(!this.input) return;
    this.input.removeAttribute('name');
  },
  getNumColumns: function() {
    if(!this.enum_options) return 3;
    var longest_text = this.getTitle().length;
    for(var i=0; i<this.enum_options.length; i++) {
      longest_text = Math.max(longest_text,this.enum_options[i].length+4);
    }
    return Math.min(12,Math.max(longest_text/7,2));
  },
  typecast: function(value) {
    if(this.schema.type === "boolean") {
      return !!value;
    }
    else if(this.schema.type === "number") {
      return 1*value;
    }
    else if(this.schema.type === "integer") {
      return Math.floor(value*1);
    }
    else {
      return ""+value;
    }
  },
  getValue: function() {
    if (!this.dependenciesFulfilled) {
      return undefined;
    }
    return this.typecast(this.value);
  },
  preBuild: function() {
    var self = this;
    this.input_type = 'select';
    this.enum_options = [];
    this.enum_values = [];
    this.enum_display = [];
    var i;

    // Enum options enumerated
    if(this.schema["enum"]) {
      var display = this.schema.options && this.schema.options.enum_titles || [];

      $each(this.schema["enum"],function(i,option) {
        self.enum_options[i] = ""+option;
        self.enum_display[i] = ""+(display[i] || option);
        self.enum_values[i] = self.typecast(option);
      });

      if(!this.isRequired()){
        self.enum_display.unshift(' ');
        self.enum_options.unshift('undefined');
        self.enum_values.unshift(undefined);
      }

    }
    // Boolean
    else if(this.schema.type === "boolean") {
      self.enum_display = this.schema.options && this.schema.options.enum_titles || ['true','false'];
      self.enum_options = ['1',''];
      self.enum_values = [true,false];

      if(!this.isRequired()){
        self.enum_display.unshift(' ');
        self.enum_options.unshift('undefined');
        self.enum_values.unshift(undefined);
      }

    }
    // Dynamic Enum
    else if(this.schema.enumSource) {
      this.enumSource = [];
      this.enum_display = [];
      this.enum_options = [];
      this.enum_values = [];

      // Shortcut declaration for using a single array
      if(!(Array.isArray(this.schema.enumSource))) {
        if(this.schema.enumValue) {
          this.enumSource = [
            {
              source: this.schema.enumSource,
              value: this.schema.enumValue
            }
          ];
        }
        else {
          this.enumSource = [
            {
              source: this.schema.enumSource
            }
          ];
        }
      }
      else {
        for(i=0; i<this.schema.enumSource.length; i++) {
          // Shorthand for watched variable
          if(typeof this.schema.enumSource[i] === "string") {
            this.enumSource[i] = {
              source: this.schema.enumSource[i]
            };
          }
          // Make a copy of the schema
          else if(!(Array.isArray(this.schema.enumSource[i]))) {
            this.enumSource[i] = $extend({},this.schema.enumSource[i]);
          }
          else {
            this.enumSource[i] = this.schema.enumSource[i];
          }
        }
      }

      // Now, enumSource is an array of sources
      // Walk through this array and fix up the values
      for(i=0; i<this.enumSource.length; i++) {
        if(this.enumSource[i].value) {
          this.enumSource[i].value = this.jsoneditor.compileTemplate(this.enumSource[i].value, this.template_engine);
        }
        if(this.enumSource[i].title) {
          this.enumSource[i].title = this.jsoneditor.compileTemplate(this.enumSource[i].title, this.template_engine);
        }
        if(this.enumSource[i].filter) {
          this.enumSource[i].filter = this.jsoneditor.compileTemplate(this.enumSource[i].filter, this.template_engine);
        }
      }
    }
    // Other, not supported
    else {
      throw "'select' editor requires the enum property to be set.";
    }
  },
  build: function() {
    var self = this;
    if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
    if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
    if(this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText);
    if(this.options.compact) this.container.classList.add('compact');

    this.input = this.theme.getSelectInput(this.enum_options);
    this.theme.setSelectOptions(this.input,this.enum_options,this.enum_display);

    if(this.schema.readOnly || this.schema.readonly) {
      this.always_disabled = true;
      this.input.disabled = true;
    }

    // Set custom attributes on input element. Parameter is array of protected keys. Empty array if none.
    this.setInputAttributes([]);

    this.input.addEventListener('change',function(e) {
      e.preventDefault();
      e.stopPropagation();
      self.onInputChange();
    });

    this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton);
    this.container.appendChild(this.control);

    this.value = this.enum_values[0];
  },
  onInputChange: function() {
    var val = this.typecast(this.input.value);

    var new_val;
    // Invalid option, use first option instead
    if(this.enum_values.indexOf(String(val)) === -1) {
      new_val = this.enum_values[0];
    }
    else {
      new_val = this.enum_values[this.enum_values.indexOf(String(val))];
    }

    // If valid hasn't changed
    if(new_val === this.value) return;

    this.is_dirty = true;

    // Store new value and propogate change event
    this.value = new_val;
    this.onChange(true);
  },
  setupSelect2: function() {
    // If the Select2 library is loaded use it when we have lots of items
    if(window.jQuery && window.jQuery.fn && window.jQuery.fn.select2 && (this.enum_options.length > 2 || (this.enum_options.length && this.enumSource))) {
      var options = $extend({},JSONEditor.plugins.select2);
      if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options);
      this.select2 = window.jQuery(this.input).select2(options);
      this.select2v4 = this.select2.select2.hasOwnProperty("amd");

      var self = this;
      this.select2.on('select2-blur',function() {
        if(self.select2v4)
          self.input.value = self.select2.val();
        else
          self.input.value = self.select2.select2('val');

        self.onInputChange();
      });

      this.select2.on('change',function() {
        if(self.select2v4)
          self.input.value = self.select2.val();
        else
          self.input.value = self.select2.select2('val');

        self.onInputChange();
      });
    }
    else {
      this.select2 = null;
    }
  },
  postBuild: function() {
    this._super();
    this.theme.afterInputReady(this.input);
    this.setupSelect2();
  },
  onWatchedFieldChange: function() {
    var self = this, vars, j;

    // If this editor uses a dynamic select box
    if(this.enumSource) {
      vars = this.getWatchedFieldValues();
      var select_options = [];
      var select_titles = [];

      for(var i=0; i<this.enumSource.length; i++) {
        // Constant values
        if(Array.isArray(this.enumSource[i])) {
          select_options = select_options.concat(this.enumSource[i]);
          select_titles = select_titles.concat(this.enumSource[i]);
        }
        else {
          var items = [];
          // Static list of items
          if(Array.isArray(this.enumSource[i].source)) {
            items = this.enumSource[i].source;
          // A watched field
          } else {
            items = vars[this.enumSource[i].source];
          }

          if(items) {
            // Only use a predefined part of the array
            if(this.enumSource[i].slice) {
              items = Array.prototype.slice.apply(items,this.enumSource[i].slice);
            }
            // Filter the items
            if(this.enumSource[i].filter) {
              var new_items = [];
              for(j=0; j<items.length; j++) {
                if(this.enumSource[i].filter({i:j,item:items[j],watched:vars})) new_items.push(items[j]);
              }
              items = new_items;
            }

            var item_titles = [];
            var item_values = [];
            for(j=0; j<items.length; j++) {
              var item = items[j];

              // Rendered value
              if(this.enumSource[i].value) {
                item_values[j] = this.enumSource[i].value({
                  i: j,
                  item: item
                });
              }
              // Use value directly
              else {
                item_values[j] = items[j];
              }

              // Rendered title
              if(this.enumSource[i].title) {
                item_titles[j] = this.enumSource[i].title({
                  i: j,
                  item: item
                });
              }
              // Use value as the title also
              else {
                item_titles[j] = item_values[j];
              }
            }

            // TODO: sort

            select_options = select_options.concat(item_values);
            select_titles = select_titles.concat(item_titles);
          }
        }
      }

      var prev_value = this.value;

      this.theme.setSelectOptions(this.input, select_options, select_titles);
      this.enum_options = select_options;
      this.enum_display = select_titles;
      this.enum_values = select_options;

      if(this.select2) {
        this.select2.select2('destroy');
      }

      // If the previous value is still in the new select options, stick with it
      if(select_options.indexOf(prev_value) !== -1) {
        this.input.value = prev_value;
        this.value = prev_value;
      }
      // Otherwise, set the value to the first select option
      else {
        this.input.value = select_options[0];
        this.value = this.typecast(select_options[0] || "");
        if(this.parent) this.parent.onChildEditorChange(this);
        else this.jsoneditor.onChange();
        this.jsoneditor.notifyWatchers(this.path);
      }

      this.setupSelect2();
    }

    this._super();
  },
  enable: function() {
    if(!this.always_disabled) {
      this.input.disabled = false;
      if(this.select2) {
        if(this.select2v4)
          this.select2.prop("disabled",false);
        else
          this.select2.select2("enable",true);
      }
    }
    this._super();
  },
  disable: function(always_disabled) {
    if(always_disabled) this.always_disabled = true;
    this.input.disabled = true;
    if(this.select2) {
      if(this.select2v4)
        this.select2.prop("disabled",true);
      else
        this.select2.select2("enable",false);
    }
    this._super();
  },
  destroy: function() {
    if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
    if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
    if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
    if(this.select2) {
      this.select2.select2('destroy');
      this.select2 = null;
    }

    this._super();
  },
  showValidationErrors: function (errors) {
    var self = this;

    if (this.jsoneditor.options.show_errors === "always") {}
    else if(!this.is_dirty && this.previous_error_setting===this.jsoneditor.options.show_errors) return;

    this.previous_error_setting = this.jsoneditor.options.show_errors;

    var messages = [];
    $each(errors, function (i, error) {
      if (error.path === self.path) {
        messages.push(error.message);
      }
    });

    if (messages.length) {
      this.theme.addInputError(this.input, messages.join('. ') + '.');
    }
    else {
      this.theme.removeInputError(this.input);
    }
  }
});
<|endoftext|>


<commit_before>var app = require('app')
var BrowserWindow = require('browser-window')
var ipcMain = require('electron').ipcMain

// Report crashes to our server.
require('crash-reporter').start()

if (process.env.NODE_ENV === 'development') {
  require('electron-debug')()
}

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
var mainWindow = null

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function () {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 1600, height: 800})

  require('./ipcadapter')(ipcMain, mainWindow.webContents)

  // and load the index.html of the app.
  if (process.env.NODE_ENV === 'development') {
    mainWindow.loadUrl('http://localhost:8080/hot-dev-app.html')
    mainWindow.openDevTools()
  } else {
    mainWindow.loadUrl('file://' + __dirname + '/../client/app.html')
  }

  // Emitted when the window is closed.
  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null
  })

  if (process.env.NODE_ENV === 'dev') {
    // require('electron-connect').client.create(mainWindow)
  }
})
<commit_msg>chore(electron): Smaller Window size<commit_after>var app = require('app')
var BrowserWindow = require('browser-window')
var ipcMain = require('electron').ipcMain

// Report crashes to our server.
require('crash-reporter').start()

if (process.env.NODE_ENV === 'development') {
  require('electron-debug')()
}

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
var mainWindow = null

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function () {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600})

  require('./ipcadapter')(ipcMain, mainWindow.webContents)

  // and load the index.html of the app.
  if (process.env.NODE_ENV === 'development') {
    mainWindow.loadURL('http://localhost:8080/hot-dev-app.html')
    mainWindow.openDevTools()
  } else {
    mainWindow.loadURL('file://' + __dirname + '/../client/app.html')
  }

  // Emitted when the window is closed.
  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null
  })

  if (process.env.NODE_ENV === 'dev') {
    // require('electron-connect').client.create(mainWindow)
  }
})
<|endoftext|>


<commit_before>;(function(){
    var choreIt = angular.module('choreIt');

    //Controller for authenticating users
    choreIt.controller('AuthCtrl', ['$state', 'auth', function($state, auth) {
        var self = this;
        self.user = {};

        //calls auth service to register user - if no error, redirect to chores state
        self.register = function() {
            auth.register(self.user).catch(function(error) {
                self.error = error;
                console.log(error);
            }).then(function() {
                if(!self.error) {
                    $state.go('chores');
                }
            })
        };

        //calls auth service to log in user - if no error, redirect to chores state
        self.logIn = function() {
            //don't save errors across multiple attempts
            self.error = null;

            auth.logIn(self.user).catch(function(error) {
                self.error = error;
                console.log(error.data.message);
            }).then(function() {
                if(!self.error) {
                    $state.go('chores');
                }
            })
        };
    }]);

})();<commit_msg>error message on duplicate username<commit_after>;(function(){
    var choreIt = angular.module('choreIt');

    //Controller for authenticating users
    choreIt.controller('AuthCtrl', ['$state', 'auth', function($state, auth) {
        var self = this;
        self.user = {};

        //calls auth service to register user - if no error, redirect to chores state
        self.register = function() {
            auth.register(self.user).catch(function(error) {
                self.error = error;

                //returns 500 when duplicate username, look into better mongodb error handling.
                if(self.error.status == 500) {
                    self.error.data = {message: 'That username is already taken.'};
                }
                console.log(error);
            }).then(function() {
                if(!self.error) {
                    $state.go('chores');
                }
            })
        };

        //calls auth service to log in user - if no error, redirect to chores state
        self.logIn = function() {
            //don't save errors across multiple attempts
            self.error = null;

            auth.logIn(self.user).catch(function(error) {
                self.error = error;
                console.log(error.data.message);
            }).then(function() {
                if(!self.error) {
                    $state.go('chores');
                }
            })
        };
    }]);

})();<|endoftext|>


<commit_before>
// 过滤空格
app.filter('noSpace', function() {
    return function(input) {
        if (input) {
            return input.replace(/\s+/g, '');
        }
    };
});

//截取字符串
app.filter('strOmit', function() {
    return function(str, num, type, omit) {
        if (!str) {
            return;
        }
        var selfLen = str.length;
        var omitted = omit || '';
        var data = '';
        var tempLen = 0;
        if (type) {
            for (var i = 0; tempLen <= num; i++) {
                if (str.charCodeAt(i) > 255) {
                    tempLen += 2;
                } else {
                    tempLen += 1;
                }
            }
            if (tempLen > num) {
                i--;
            }
            data = str.slice(0, i);
        } else {
            data = str.slice(0, num);
        }
        if (selfLen > num || selfLen > i) {
            data += omitted;
        }
        return data;
    };
});

//空格转换为nbsp
app.filter('toNBSP', function() {
    return function(input) {
        var val = input.replace(/ /g, ' ');
        return val;
    };
});

//搜索结果着色
app.filter('resultsKey', function() {
    return function(input, key) {
        var reg = new RegExp(key);
        var val = input.replace(reg, `<span class="key">${key}</span>`);
        return val;
    };
});

app.filter('htmlTagsRemove', function() {
    return function(str) {
        str = str.replace(/<\/?[^>]*>/g, ''); //去除HTML tag
        str = str.replace(/[ | ]*\n/g, '\n'); //去除行尾空白
        str = str.replace(/\n[\s| | ]*\r/g, '\n'); //去除多余空行
        str = str.replace(/ /ig, ''); //去掉
        return str;
    }
})

app.filter('htmlDecode', function() {
    return function(str) {
        var s = "";
        if (str.length == 0) return "";
        s = str.replace(/&/g, "&");
        s = s.replace(/</g, "<");
        s = s.replace(/>/g, ">");
        s = s.replace(/ /g, " ");
        s = s.replace(/'/g, "\'");
        s = s.replace(/"/g, "\"");
        return s;
    }
});

// 过滤字符串NULL
app.filter('nullToSpace', function() {
    return function(input) {
        let str = input === 'NULL' ? '' : input;
        return str;
    };
});

// 百分比
app.filter('percentage', ['$filter', function ($filter) {
    return function (input, decimals) {
        return $filter('number')(toRMBFen(input), decimals) + '%';
    };
}]);

// 价格区间显示
app.filter('showPriceSummary', ['$filter', function ($filter) {
    return function (input, isSku) {
        var minPrice = null, maxPrice = null;

        if (hasTrue(isSku)) {
            minPrice = toRMBYuan(input.minSkuPrice);
            maxPrice = toRMBYuan(input.maxSkuPrice);
        } else {
            minPrice = toRMBYuan(input.minPrice);
            maxPrice = toRMBYuan(input.maxPrice);
        }

        if (minPrice === maxPrice) {
            return '￥' + minPrice;
        } else {
            return '￥' + minPrice + '-' + maxPrice;
        }
    };
}]);

// 显示价格
app.filter('showPrice', ['$filter', function ($filter) {
    return function (input) {
        return $filter('currency')(toRMBYuan(input));
    };
}]);
<commit_msg>1.修改￥为¥实体符；<commit_after>
// 过滤空格
app.filter('noSpace', function() {
    return function(input) {
        if (input) {
            return input.replace(/\s+/g, '');
        }
    };
});

//截取字符串
app.filter('strOmit', function() {
    return function(str, num, type, omit) {
        if (!str) {
            return;
        }
        var selfLen = str.length;
        var omitted = omit || '';
        var data = '';
        var tempLen = 0;
        if (type) {
            for (var i = 0; tempLen <= num; i++) {
                if (str.charCodeAt(i) > 255) {
                    tempLen += 2;
                } else {
                    tempLen += 1;
                }
            }
            if (tempLen > num) {
                i--;
            }
            data = str.slice(0, i);
        } else {
            data = str.slice(0, num);
        }
        if (selfLen > num || selfLen > i) {
            data += omitted;
        }
        return data;
    };
});

//空格转换为nbsp
app.filter('toNBSP', function() {
    return function(input) {
        var val = input.replace(/ /g, ' ');
        return val;
    };
});

//搜索结果着色
app.filter('resultsKey', function() {
    return function(input, key) {
        var reg = new RegExp(key);
        var val = input.replace(reg, `<span class="key">${key}</span>`);
        return val;
    };
});

app.filter('htmlTagsRemove', function() {
    return function(str) {
        str = str.replace(/<\/?[^>]*>/g, ''); //去除HTML tag
        str = str.replace(/[ | ]*\n/g, '\n'); //去除行尾空白
        str = str.replace(/\n[\s| | ]*\r/g, '\n'); //去除多余空行
        str = str.replace(/ /ig, ''); //去掉
        return str;
    }
})

app.filter('htmlDecode', function() {
    return function(str) {
        var s = "";
        if (str.length == 0) return "";
        s = str.replace(/&/g, "&");
        s = s.replace(/</g, "<");
        s = s.replace(/>/g, ">");
        s = s.replace(/ /g, " ");
        s = s.replace(/'/g, "\'");
        s = s.replace(/"/g, "\"");
        return s;
    }
});

// 过滤字符串NULL
app.filter('nullToSpace', function() {
    return function(input) {
        let str = input === 'NULL' ? '' : input;
        return str;
    };
});

// 百分比
app.filter('percentage', ['$filter', function ($filter) {
    return function (input, decimals) {
        return $filter('number')(toRMBFen(input), decimals) + '%';
    };
}]);

// 价格区间显示
app.filter('showPriceSummary', ['$filter', function ($filter) {
    return function (input, isSku) {
        var minPrice = null, maxPrice = null;

        if (hasTrue(isSku)) {
            minPrice = toRMBYuan(input.minSkuPrice);
            maxPrice = toRMBYuan(input.maxSkuPrice);
        } else {
            minPrice = toRMBYuan(input.minPrice);
            maxPrice = toRMBYuan(input.maxPrice);
        }

        if (minPrice === maxPrice) {
            return '¥' + minPrice;
        } else {
            return '¥' + minPrice + '-' + maxPrice;
        }
    };
}]);

// 显示价格
app.filter('showPrice', ['$filter', function ($filter) {
    return function (input) {
        return $filter('currency')(toRMBYuan(input));
    };
}]);
<|endoftext|>


<commit_before>/*
 * Filter.js
 * version: 1.5.1 (22/4/2013)
 *
 * Licensed under the MIT:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright 2013 Jiren Patel[ joshsoftware.com ]
 *
 * Dependency:
 *  jQuery(v1.8 >=)
 */

(function(window) {

  'use strict';

  var FilterJS = function(data, container, view, options) {
    return new _FilterJS(data, container, view, options);
  };

  FilterJS.VERSION = '1.5.1';

  $.fn.filterjs = function(data, view, options) {
    var $this = $(this);
    if ($this.data('fjs')) return;
    $this.data('fjs', new _FilterJS(data, $this, view, options));
  };

  window.FilterJS = FilterJS;

  var _FilterJS = function(data, container, view, options) {
    var property_count = 0;

    this.data = data;
    this.view = view;
    this.container = container;
    this.options = options || {};
    this.categories_map = {}
    this.record_ids = [];

    if (this.data.constructor != Array) this.data = [this.data];

    for (name in this.data[0]){
      this.root = name;
      property_count += 1;
    }

    if (property_count == 1){
      this.getRecord = function(i, d){ return d[i][this.root]; }
    }else{
      this.getRecord = function(i, d){ return d[i]; }
      this.root = 'fjs';
    }

    this.id_field = this.options.id_field || 'id';
    this.render(this.data);
    this.parseOptions();
    this.buildCategoryMap(this.data);
    this.bindEvents();

    this.options.callbacks = this.options.callbacks || {};
    this.execCallBack('after_init', this.record_ids);
    this.execCallBack('after_add', this.data);
    this.options.filter_types = this.options.filter_types || {};

    if (!this.options.filter_types['range'])
      this.options.filter_types['range'] = this.rangeFilter;

    this.options.streaming = this.options.streaming || {};
    if (this.options.streaming.data_url){
      this.options.streaming.stream_after = (this.options.streaming.stream_after || 2)*1000;
      this.options.streaming.batch_size = this.options.streaming.batch_size || false;
      this.streamData(this.options.streaming.stream_after);
    }

    if(this.options.filter_on_init == undefined || this.options.filter_on_init == true){
      this.options.filter_on_init = true;
      this.filter();
    }

    return this;
  };

  _FilterJS.prototype = {

    //Render Html using JSON data
    render: function(data, offset) {
      var $container = $(this.container), record, el;

      if (!data) return;

      for (var i = 0, l = data.length; i < l; i++){
        record = this.getRecord(i, data);
        el = $(this.view(record));
        el.attr({id: this.root + '_' + record[this.id_field], 'data-fjs': true});
        el = $container.append(el);
      }
    },

    //Bind Events to filter html elements
    bindEvents: function() {
      var self = this, s = this.options.selectors, i = 0, l = s.length;

      for (i; i < l; i++){
        this.bindSelectorEvent(s[i], self);
      }

      if (this.options.search){
        $(this.options.search.input).on('keyup', function(e){
          self.filter();
        });
      }
    },

    bindSelectorEvent: function(selector, context) {
      $(selector.element).on(selector.events, function(e) {
        context.filter();
      });
    },

    //Unbind fileter events
    clear: function() {
      var s = this.options.selectors, i = 0, l = s.length;

      for (i; i < l; i++)
        $(s[i].element).off(s[i].events);

      if (this.options.search) $(this.options.search.input).off('keyup');

      this.category_map = null;
      this.record_ids = null;
    },

    //Find elements accroding to selection criteria.
    filter: function(){
      var result, s, selected_vals, records, selected_none = false, i = 0, l = this.options.selectors.length;

      //Check if criteria option is specified
      if(l) {
        for (i; i < l; i++){
          s = this.options.selectors[i];
          selected_vals = $(s.element).filter(s.select).map(function() {
            return $(this).val();
          });

          if (selected_vals.length) {
            records = this.findObjects(selected_vals, this.categories_map[s.name], this.options.filter_types[s.type]);

            result = $.grep((result || this.record_ids), function(v) {
              return (records.indexOf(v) != -1);
            });
          }else{
            selected_none = true;
          }
        }

        if (selected_none && this.options.and_filter_on) result = [];
      }
      else{
        result = this.record_ids;
      }

      if (this.options.search) result = this.search(this.options.search, result);

      this.hideShow(result);
      this.execCallBack('after_filter', result);
    },

    //Compare and collect objects
    findObjects: function(category_vals, category_map, filter_type_func) {
      var r = [], ids, category_val, i = 0, l = category_vals.length;

      for (i; i < l; i++){
        category_val = category_vals[i];

        if (filter_type_func){
          ids = $.map(category_map, function(n,v){
            if (filter_type_func(category_val, v)) return n;
          });
        } else {
          ids = category_map.constructor == Array ? category_map : category_map[category_val];
        }

        if (ids) r = r.concat(ids);
      }

      return r;
    },

    //Make eval expresssion  to collect object from the json data.
    buildEvalString: function(field_map) {
      var fields = field_map.split('.ARRAY.'), eval_str, i = 1, l = fields.length;

      eval_str = fields[0];

      for (i; i < l; i++) {
        eval_str += ".filter_collect('" + fields[i] + "')";
      }

      return eval_str;
    },

    addFilterCriteria: function(name, criteria, ids_or_mapping) {
      this.categories_map[name] = {};

      var selector = this.parseSelectorOptions({name: name}, [criteria]);
      ids_or_mapping = ids_or_mapping || $(selector.element).data('ids') || [];

      this.options.selectors.push(selector);
      this.categories_map[name] = ids_or_mapping;

      this.bindSelectorEvent(selector, this);
    },

    //Create map accroding to selection criteria.
    parseOptions: function() {
      var filter_criteria = this.options.filter_criteria, selector, criteria, ele, ele_type;
      this.options.selectors = [];

      for (name in filter_criteria) {

        criteria = filter_criteria[name];
        selector = this.parseSelectorOptions({name: name}, criteria);

        this.options.selectors.push(selector);

        criteria.push(this.buildEvalString(criteria[1]));
        this.categories_map[name] = {};
      }
    },

    parseSelectorOptions: function(selector, criteria) {
      selector.element = criteria[0].split(/.EVENT.|.SELECT.|.TYPE./)[0];
      selector.events = (criteria[0].match(/.EVENT.(\S*)/) || [])[1];
      selector.select = (criteria[0].match(/.SELECT.(\S*)/) || [])[1];
      selector.type = (criteria[0].match(/.TYPE.(\S*)/) || [])[1];

      var ele = $(selector.element),
          ele_type = ele.attr('type');

      if (!selector.select){
        if (ele.get(0).tagName == 'INPUT'){
          if (ele_type == 'checkbox' || ele_type == 'radio'){
            selector.select = ':checked';
          }else if (ele_type == 'hidden'){
            selector.select = ':input';
          }
        }else if (ele.get(0).tagName == 'SELECT'){
           selector.select = 'select';
        }
      }

      if (!selector.events){
        if (ele_type == 'checkbox' ||ele_type == 'radio'){
          selector.events = 'click';
        }else if (ele_type == 'hidden' || ele.get(0).tagName == 'SELECT'){
          selector.events = 'change';
        }
      }

      return selector;
    },

    buildCategoryMap: function(data) {
      var filter_criteria = this.options.filter_criteria, record, categories, obj, x;

      for (var i = 0, l = data.length; i < l; i++){
        record = this.getRecord(i, data);
        this.record_ids.push(record[this.id_field]);

        for (name in filter_criteria) {
          categories = eval('record.' + filter_criteria[name][2]);
          obj = this.categories_map[name];

          if (categories && categories.constructor == Array) {
            for (var j = 0, lj = categories.length; j < lj; j++){
              x = categories[j];
              obj[x] ? obj[x].push(record[this.id_field]) : obj[x] = [record[this.id_field]];
            }
          } else {
            obj[categories] ? obj[categories].push(record[this.id_field]) : obj[categories] = [record[this.id_field]];
          }
        }
      }
    },

    hideShow: function(ids) {
      var e_id = '#' + this.root + '_', i = 0, l = ids.length;

      $(this.container + ' > *[data-fjs]').hide();

      for (i; i < l; i++)
        $(e_id + ids[i]).show();
    },

    search: function (search_config, filter_result) {
  		var val = $.trim($(search_config.input).val());
	  	var search_in = search_config.search_in;
		  var min_length = $.isNumeric(search_config.min_length) ? search_config.min_length : 1;

		  if (val.length < min_length) return filter_result;

		  var id_prefix = '#' + this.root + '_';
		  val = val.toUpperCase();

		  return $.map(filter_result, function (id) {
			  var $ele = $(id_prefix + id);

			  if (search_in) $ele = $ele.find(search_in);

	  		if ($ele.text().toUpperCase().indexOf(val) >= 0) return id;
		  });
	  },

    execCallBack: function(type, result){
      if(this.options.callbacks[type])
        this.options.callbacks[type].call(this, result)
    },

    rangeFilter: function(category_value, v){
      var range = category_value.split('-');

      if (range.length == 2){
        if (range[0] == 'below') range[0] = -Infinity;
        if (range[1] == 'above') range[1] = Infinity;
        if (Number(v) >= range[0] && Number(v) <= range[1]){
          return true;
        }
      }
    },

    //Collect Records by id array
    getRecordsByIds: function(ids){
      var records = [], r, i = 0, l = this.data.length;

      for (i; i < l; i++){
        r = this.getRecord(i, this.data);
        if (ids.indexOf(r[this.id_field]) != -1) records.push(r)
      }

      return records;
    },

    addData: function(data){
      if (data == undefined || data.length == 0 ) return;

      var i = 0, l = data.length, r, uniq_data = [], e_id = '#' + this.root + '_';

      this.execCallBack('before_add', data)

      //for (i, l; i < l; i++){
      //  r = this.getRecord(i, data);
      //  if ($(e_id + r.id).length == 0) uniq_data.push(data[i]);
      //}

      this.data = this.data.concat(data);
      this.render(data);
      this.buildCategoryMap(data);
      this.execCallBack('after_add', data)
      this.filter();
    },

    setStreamingTimer: function(){
      var self = this,
          timer_func = this.options.streaming.batch_size ? setInterval : setTimeout;

      return timer_func(function(){
        self.streamData();
      }, this.options.streaming.stream_after);
    },

    clearStreamingTimer: function(){
      if (this.timer) clearTimeout(this.timer);
    },

    fetchData: function(){
      var self = this,
          params = this.options.params || {},
          opts = this.options.streaming;

      params['offset'] = this.data.length;

      if (opts.batch_size) params['limit'] = opts.batch_size;
      if (this.options.search) params['q'] = $.trim($(this.options.search.input).val());

      $.getJSON(opts.data_url, params).done(function(data){

        if (params.limit != null && (!data || !data.length)){
          self.stopStreaming();
        }else{
          self.setStreamInterval();
          self.addData(data);
        }

      }).fail(function(e){
        self.stopStreaming();
      });
    },

    setStreamInterval: function(){
      var self = this;
      if(self.options.streaming.stop_streaming == true) return;

      self.timer = setTimeout(function(){
        self.fetchData();
      }, self.options.streaming.stream_after);
    },

    stopStreaming: function(){
      this.options.streaming.stop_streaming = true;
      if (this.timer) clearTimeout(this.timer);
    },

    resumeStreaming: function(){
      this.options.streaming.stop_streaming = false;
      this.streamData(this.options.streaming.stream_after);
    },

    streamData: function(time){
      this.setStreamInterval();
      if(!this.options.streaming.batch_size) this.stopStreaming();
    }

}

})(this);

/**
 * Recursive method to collect object from json object.
 * i.e. test =  [ {"deal": {"id": 1 }}, {"deal": {"id": 2}}]
 *  - to collect id from the json data
 *    test.filter_collect('deal').filter_collect('id')
 *    this will return [1,2]
 */
Array.prototype.filter_collect = function(field, arr) {
  var arr = arr || [];
  for (var i = 0, l = this.length; i < l; i++){
    var obj = this[i];
    if (obj.constructor == Array){
      obj.filter_collect(field, arr);
    }
    else {
      arr.push(obj[field]);
    }
  }

  return arr;
};

//In IE indexOf method not define.
if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function(obj, start) {
    for (var i = (start || 0), j = this.length; i < j; i++) {
      if (this[i] === obj) { return i; }
    }
    return -1;
  }
}
<commit_msg>Fix for text input change<commit_after>/*
 * Filter.js
 * version: 1.5.2 (12/5/2014)
 *
 * Licensed under the MIT:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright 2013 Jiren Patel[ joshsoftware.com ]
 *
 * Dependency:
 *  jQuery(v1.8 >=)
 */

(function(window) {

  'use strict';

  var FilterJS = function(data, container, view, options) {
    return new _FilterJS(data, container, view, options);
  };

  FilterJS.VERSION = '1.5.1';

  $.fn.filterjs = function(data, view, options) {
    var $this = $(this);
    if ($this.data('fjs')) return;
    $this.data('fjs', new _FilterJS(data, $this, view, options));
  };

  window.FilterJS = FilterJS;

  var _FilterJS = function(data, container, view, options) {
    var property_count = 0;

    this.data = data;
    this.view = view;
    this.container = container;
    this.options = options || {};
    this.categories_map = {}
    this.record_ids = [];

    if (this.data.constructor != Array) this.data = [this.data];

    for (name in this.data[0]){
      this.root = name;
      property_count += 1;
    }

    if (property_count == 1){
      this.getRecord = function(i, d){ return d[i][this.root]; }
    }else{
      this.getRecord = function(i, d){ return d[i]; }
      this.root = 'fjs';
    }

    this.id_field = this.options.id_field || 'id';
    this.render(this.data);
    this.parseOptions();
    this.buildCategoryMap(this.data);
    this.bindEvents();

    this.options.callbacks = this.options.callbacks || {};
    this.execCallBack('after_init', this.record_ids);
    this.execCallBack('after_add', this.data);
    this.options.filter_types = this.options.filter_types || {};

    if (!this.options.filter_types['range'])
      this.options.filter_types['range'] = this.rangeFilter;

    this.options.streaming = this.options.streaming || {};
    if (this.options.streaming.data_url){
      this.options.streaming.stream_after = (this.options.streaming.stream_after || 2)*1000;
      this.options.streaming.batch_size = this.options.streaming.batch_size || false;
      this.streamData(this.options.streaming.stream_after);
    }

    if(this.options.filter_on_init == undefined || this.options.filter_on_init == true){
      this.options.filter_on_init = true;
      this.filter();
    }

    return this;
  };

  _FilterJS.prototype = {

    //Render Html using JSON data
    render: function(data, offset) {
      var $container = $(this.container), record, el;

      if (!data) return;

      for (var i = 0, l = data.length; i < l; i++){
        record = this.getRecord(i, data);
        el = $(this.view(record));
        el.attr({id: this.root + '_' + record[this.id_field], 'data-fjs': true});
        el = $container.append(el);
      }
    },

    //Bind Events to filter html elements
    bindEvents: function() {
      var self = this, s = this.options.selectors, i = 0, l = s.length;

      for (i; i < l; i++){
        this.bindSelectorEvent(s[i], self);
      }

      if (this.options.search){
        $(this.options.search.input).on('keyup', function(e){
          self.filter();
        });
      }
    },

    bindSelectorEvent: function(selector, context) {
      $(selector.element).on(selector.events, function(e) {
        context.filter();
      });
    },

    //Unbind fileter events
    clear: function() {
      var s = this.options.selectors, i = 0, l = s.length;

      for (i; i < l; i++)
        $(s[i].element).off(s[i].events);

      if (this.options.search) $(this.options.search.input).off('keyup');

      this.category_map = null;
      this.record_ids = null;
    },

    //Find elements accroding to selection criteria.
    filter: function(){
      var result, s, selected_vals, records, selected_none = false, i = 0, l = this.options.selectors.length;

      //Check if criteria option is specified
      if(l) {
        for (i; i < l; i++){
          s = this.options.selectors[i];
          selected_vals = $(s.element).filter(s.select).map(function() {
            return $(this).val();
          });

          if (selected_vals.length) {
            records = this.findObjects(selected_vals, this.categories_map[s.name], this.options.filter_types[s.type]);

            result = $.grep((result || this.record_ids), function(v) {
              return (records.indexOf(v) != -1);
            });
          }else{
            selected_none = true;
          }
        }

        if (selected_none && this.options.and_filter_on) result = [];
      }
      else{
        result = this.record_ids;
      }

      if (this.options.search) result = this.search(this.options.search, result);

      this.hideShow(result);
      this.execCallBack('after_filter', result);
    },

    //Compare and collect objects
    findObjects: function(category_vals, category_map, filter_type_func) {
      var r = [], ids, category_val, i = 0, l = category_vals.length;

      for (i; i < l; i++){
        category_val = category_vals[i];

        if (filter_type_func){
          ids = $.map(category_map, function(n,v){
            if (filter_type_func(category_val, v)) return n;
          });
        } else {
          ids = category_map.constructor == Array ? category_map : category_map[category_val];
        }

        if (ids) r = r.concat(ids);
      }

      return r;
    },

    //Make eval expresssion  to collect object from the json data.
    buildEvalString: function(field_map) {
      var fields = field_map.split('.ARRAY.'), eval_str, i = 1, l = fields.length;

      eval_str = fields[0];

      for (i; i < l; i++) {
        eval_str += ".filter_collect('" + fields[i] + "')";
      }

      return eval_str;
    },

    addFilterCriteria: function(name, criteria, ids_or_mapping) {
      this.categories_map[name] = {};

      var selector = this.parseSelectorOptions({name: name}, [criteria]);
      ids_or_mapping = ids_or_mapping || $(selector.element).data('ids') || [];

      this.options.selectors.push(selector);
      this.categories_map[name] = ids_or_mapping;

      this.bindSelectorEvent(selector, this);
    },

    //Create map accroding to selection criteria.
    parseOptions: function() {
      var filter_criteria = this.options.filter_criteria, selector, criteria, ele, ele_type;
      this.options.selectors = [];

      for (name in filter_criteria) {

        criteria = filter_criteria[name];
        selector = this.parseSelectorOptions({name: name}, criteria);

        this.options.selectors.push(selector);

        criteria.push(this.buildEvalString(criteria[1]));
        this.categories_map[name] = {};
      }
    },

    parseSelectorOptions: function(selector, criteria) {
      selector.element = criteria[0].split(/.EVENT.|.SELECT.|.TYPE./)[0];
      selector.events = (criteria[0].match(/.EVENT.(\S*)/) || [])[1];
      selector.select = (criteria[0].match(/.SELECT.(\S*)/) || [])[1];
      selector.type = (criteria[0].match(/.TYPE.(\S*)/) || [])[1];

      var ele = $(selector.element),
          ele_type = ele.attr('type');

      if (!selector.select){
        if (ele.get(0).tagName == 'INPUT'){
          if (ele_type == 'checkbox' || ele_type == 'radio'){
            selector.select = ':checked';
          }else if (ele_type == 'hidden' || ele_type == 'text'){
            selector.select = ':input';
          }
        }else if (ele.get(0).tagName == 'SELECT'){
           selector.select = 'select';
        }
      }

      if (!selector.events){
        if (ele_type == 'checkbox' ||ele_type == 'radio'){
          selector.events = 'click';
        }else if (ele_type == 'hidden' || ele.get(0).tagName == 'SELECT'){
          selector.events = 'change';
        }
      }

      return selector;
    },

    buildCategoryMap: function(data) {
      var filter_criteria = this.options.filter_criteria, record, categories, obj, x;

      for (var i = 0, l = data.length; i < l; i++){
        record = this.getRecord(i, data);
        this.record_ids.push(record[this.id_field]);

        for (name in filter_criteria) {
          categories = eval('record.' + filter_criteria[name][2]);
          obj = this.categories_map[name];

          if (categories && categories.constructor == Array) {
            for (var j = 0, lj = categories.length; j < lj; j++){
              x = categories[j];
              obj[x] ? obj[x].push(record[this.id_field]) : obj[x] = [record[this.id_field]];
            }
          } else {
            obj[categories] ? obj[categories].push(record[this.id_field]) : obj[categories] = [record[this.id_field]];
          }
        }
      }
    },

    hideShow: function(ids) {
      var e_id = '#' + this.root + '_', i = 0, l = ids.length;

      $(this.container + ' > *[data-fjs]').hide();

      for (i; i < l; i++)
        $(e_id + ids[i]).show();
    },

    search: function (search_config, filter_result) {
  		var val = $.trim($(search_config.input).val());
	  	var search_in = search_config.search_in;
		  var min_length = $.isNumeric(search_config.min_length) ? search_config.min_length : 1;

		  if (val.length < min_length) return filter_result;

		  var id_prefix = '#' + this.root + '_';
		  val = val.toUpperCase();

		  return $.map(filter_result, function (id) {
			  var $ele = $(id_prefix + id);

			  if (search_in) $ele = $ele.find(search_in);

	  		if ($ele.text().toUpperCase().indexOf(val) >= 0) return id;
		  });
	  },

    execCallBack: function(type, result){
      if(this.options.callbacks[type])
        this.options.callbacks[type].call(this, result)
    },

    rangeFilter: function(category_value, v){
      var range = category_value.split('-');

      if (range.length == 2){
        if (range[0] == 'below') range[0] = -Infinity;
        if (range[1] == 'above') range[1] = Infinity;
        if (Number(v) >= range[0] && Number(v) <= range[1]){
          return true;
        }
      }
    },

    //Collect Records by id array
    getRecordsByIds: function(ids){
      var records = [], r, i = 0, l = this.data.length;

      for (i; i < l; i++){
        r = this.getRecord(i, this.data);
        if (ids.indexOf(r[this.id_field]) != -1) records.push(r)
      }

      return records;
    },

    addData: function(data){
      if (data == undefined || data.length == 0 ) return;

      var i = 0, l = data.length, r, uniq_data = [], e_id = '#' + this.root + '_';

      this.execCallBack('before_add', data)

      //for (i, l; i < l; i++){
      //  r = this.getRecord(i, data);
      //  if ($(e_id + r.id).length == 0) uniq_data.push(data[i]);
      //}

      this.data = this.data.concat(data);
      this.render(data);
      this.buildCategoryMap(data);
      this.execCallBack('after_add', data)
      this.filter();
    },

    setStreamingTimer: function(){
      var self = this,
          timer_func = this.options.streaming.batch_size ? setInterval : setTimeout;

      return timer_func(function(){
        self.streamData();
      }, this.options.streaming.stream_after);
    },

    clearStreamingTimer: function(){
      if (this.timer) clearTimeout(this.timer);
    },

    fetchData: function(){
      var self = this,
          params = this.options.params || {},
          opts = this.options.streaming;

      params['offset'] = this.data.length;

      if (opts.batch_size) params['limit'] = opts.batch_size;
      if (this.options.search) params['q'] = $.trim($(this.options.search.input).val());

      $.getJSON(opts.data_url, params).done(function(data){

        if (params.limit != null && (!data || !data.length)){
          self.stopStreaming();
        }else{
          self.setStreamInterval();
          self.addData(data);
        }

      }).fail(function(e){
        self.stopStreaming();
      });
    },

    setStreamInterval: function(){
      var self = this;
      if(self.options.streaming.stop_streaming == true) return;

      self.timer = setTimeout(function(){
        self.fetchData();
      }, self.options.streaming.stream_after);
    },

    stopStreaming: function(){
      this.options.streaming.stop_streaming = true;
      if (this.timer) clearTimeout(this.timer);
    },

    resumeStreaming: function(){
      this.options.streaming.stop_streaming = false;
      this.streamData(this.options.streaming.stream_after);
    },

    streamData: function(time){
      this.setStreamInterval();
      if(!this.options.streaming.batch_size) this.stopStreaming();
    }

}

})(this);

/**
 * Recursive method to collect object from json object.
 * i.e. test =  [ {"deal": {"id": 1 }}, {"deal": {"id": 2}}]
 *  - to collect id from the json data
 *    test.filter_collect('deal').filter_collect('id')
 *    this will return [1,2]
 */
Array.prototype.filter_collect = function(field, arr) {
  var arr = arr || [];
  for (var i = 0, l = this.length; i < l; i++){
    var obj = this[i];
    if (obj.constructor == Array){
      obj.filter_collect(field, arr);
    }
    else {
      arr.push(obj[field]);
    }
  }

  return arr;
};

//In IE indexOf method not define.
if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function(obj, start) {
    for (var i = (start || 0), j = this.length; i < j; i++) {
      if (this[i] === obj) { return i; }
    }
    return -1;
  }
}
<|endoftext|>


<commit_before>//TODO add other views
//TODO add other fact types (periscope / beginner debunking
//TODO make the view match the state for the buttons etc.
//TODO add table row headers

// The expected number of bits of information from real science is
// -[1/7*lg(1/7) * 7]
// = 2.807 bits

// The expected number of bits of information from experimenting randomly on an adventurer is
// -[1/7*lg(1/7) + 2/7*lg(2/7) + 1/7*lg(1/7) + 3/7*lg(3/7)]
// = 1.842 bits

import React from 'react';
// import logo from './logo.svg';
import './App.css';

import _ from 'lodash'
import update from 'react-addons-update'
import PureRenderMixin from 'react-addons-pure-render-mixin';

import {Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn} from 'material-ui/Table';
import RaisedButton from 'material-ui/RaisedButton';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

const style = {
  margin: 12,
};

// This is what an Alchemical looks like:
// [1, 1, -1]

// Here are all of the Alchemicals:
var alchemicals = [
  [-1, +1, -1],
  [+1, -1, +1],
  [+1, -1, -1],
  [-1, +1, +1],
  [-1, -1, +1],
  [+1, +1, -1],
  [-1, -1, -1],
  [+1, +1, +1],
]

var ingredients = [
  "Mushroom",
  "Fern",
  "Toad",
  "Birdclaw",
  "Flower",
  "Mandrake",
  "Scorion",
  "Feather",
]

var potions = {
  "Red+":   [+1, 0, 0],
  "Red-":   [-1, 0, 0],
  "Green+": [0, +1, 0],
  "Green-": [0, -1, 0],
  "Blue+":  [0, 0, +1],
  "Blue-":  [0, 0, -1],
  "Soup":   [0, 0, 0],
}

var Fact = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return

      {this.props.index}: {JSON.stringify(this.props.item)}
      <RaisedButton onClick={this.props.deleteFact} value={this.props.index} style={style}>Delete</RaisedButton>

  }
});

var FactList = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return
{this.props.items.map((fact, factIndex) => <Fact key={factIndex} item={fact} index={factIndex} deleteFact={this.props.deleteFact}/>)}
;
  }
});

var Ingredient = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return
<input type="radio" name="ingredient" onChange={this.props.callback}/>{this.props.name}

  }
})

var Potion = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return
<input type="checkbox" name="potion" onChange={this.props.callback}/>{this.props.name}

  }
})

var SheetCell = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return <TableRowColumn>{this.percentage()}</TableRowColumn>
  },
  percentage: function() {
    var worlds = _.filter(this.props.worlds, _.bind(function(world) {
      return _.isEqual(world[this.props.ingredientIndex], this.props.alchemical)
    }, this))
    return Math.round(100 * worlds.length / this.props.worlds.length, 0)
  }
})

var SheetRow = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return <TableRow>{ingredients.map((ingredient, index) => <SheetCell ingredientIndex={index} alchemical={this.props.alchemical} key={index} worlds={this.props.worlds}/>)}</TableRow>
  }
})

var AlchemistsSolverApp = React.createClass({
  mixins: [PureRenderMixin],

  getInitialState: function() {
    return {
      factlist: [],
      currentFact: this.makeNewFact(),
      worlds: permutator(alchemicals),
    }
  },
  makeNewFact: function() {
    return {
      ingredients: [0, 1],
      possibleResults: [false, false, false, false, false, false, false]
    }
  },
  handleSubmit: function(e) {
    e.preventDefault();
    this.setState({
      factlist: this.state.factlist.concat([this.state.currentFact]),
      worlds: _.filter(this.state.worlds, _.curry(check)(this.state.currentFact)),
    });
  },
  ingredientChange: function(ingredientIndex, ingredient) {
    var newIngredients = [];
    newIngredients[ingredientIndex] = ingredient;
    var newState = update(this.state,
      {currentFact: {ingredients: {$merge: newIngredients}}})
    this.setState(newState)
  },
  potionChange: function(potionIndex) {
    var newPossibleResults = [];
    newPossibleResults[potionIndex] = !this.state.currentFact.possibleResults[potionIndex];
    var newState = update(this.state,
      {currentFact: {possibleResults: {$merge: newPossibleResults}}})
    this.setState(newState)
  },
  render: function() {
    var self = this;
    return (
      <MuiThemeProvider>

Alchemists Solver

          <FactList items={this.state.factlist} deleteFact={this.deleteFact}/>

          <form action="" style={{display: "inline-block"}}>
            {ingredients.map((name, index) => <Ingredient name={name} ingredientNumber={0} key={index} callback={function() {self.ingredientChange(0, index)}} />)}
          </form>

          <form action="" style={{display: "inline-block"}}>
            {ingredients.map((name, index) => <Ingredient name={name} ingredientNumber={1} key={index} callback={function() {self.ingredientChange(1, index)}} />)}
          </form>

          <form action="" style={{display: "inline-block"}}>
            {_.keys(potions).map((name, index) => <Potion name={name} key={index} callback={function() {self.potionChange(index)}} />)}
          </form>

          <RaisedButton label="Add Fact" primary={true} style={style} onClick={this.handleSubmit}/>

{this.computeStuff()}

            <TableHeader displaySelectAll={false} adjustForCheckbox={false}>
              <TableRow>
                {ingredients.map((name, index) => <TableHeaderColumn key={index}>{name}</TableHeaderColumn>)}
              </TableRow>
            </TableHeader>
            <TableBody>
              {alchemicals.map((alchemical, index) => <SheetRow alchemical={alchemical} key={index} worlds={this.state.worlds}/>)}
            </TableBody>

      </MuiThemeProvider>
    );
  },
  deleteFact: function(e) {
    var newFactList = removeAtIndex(this.state.factlist, parseInt(e.target.value, 10))
    var worlds = permutator(alchemicals)
    for (var factIndex in newFactList) {
      var fact = newFactList[factIndex]
      console.log(fact)
      worlds = _.filter(worlds, _.curry(check)(fact))
    }
    this.setState({factlist: newFactList, worlds: worlds})
  },
  computeStuff: function() {
    return this.state.worlds.length
  }
});

// non-mutating
function removeAtIndex(arr, index) {
  return _.filter(arr, function(val, idx) {return idx !== index})
}

var ColorMapping = {
  0: "red",
  1: "green",
  2: "blue",
};

// var SignMapping = {
//   -1: "minus",
//   0: "neutral",
//   1: "plus",
// };

// Alchemical -> Alchemical -> Potion
function mix(alchemicalA, alchemicalB) {
  var mean = _.zipWith(alchemicalA, alchemicalB, function(a,b){return (a+b)/2})
  for (var index = 0; index < 3; index++) {
    if (mean[(index+1) % 3] !== 0)
      mean[index] = 0
  }

  return mean
}

function check(fact, world) {
  var alchemicalA = world[fact.ingredients[0]]
  var alchemicalB = world[fact.ingredients[1]]
  var result = mix(alchemicalA, alchemicalB)
  var potionIndex = _.findIndex(_.values(potions), _.curry(_.isEqual)(result))
  return fact.possibleResults[potionIndex]
}

// http://stackoverflow.com/a/20871714/6036628
function permutator(inputArr) {
  var results = [];

  function permute(arr, memo) {
    var cur, memo = memo || [];

    for (var i = 0; i < arr.length; i++) {
      cur = arr.splice(i, 1);
      if (arr.length === 0) {
        results.push(memo.concat(cur));
      }
      permute(arr.slice(), memo.concat(cur));
      arr.splice(i, 0, cur[0]);
    }

    return results;
  }

  return permute(inputArr);
}

// ===== Unit tests =====

// Here is what a World looks like:
var exampleWorld = [
  [+1, -1, -1],
  [-1, +1, -1],
  [+1, -1, +1],
  [-1, +1, +1],
  [+1, +1, +1],
  [-1, -1, +1],
  [+1, +1, -1],
  [-1, -1, -1],
]

// This is what an Ingredient looks like:
var exampleIngredientA = 4
var exampleIngredientB = 5

// This is what a Potion looks like:
var examplePotionA = [0, 0, 1] // this is a blue+
var examplePotionB = [0, 0, 0] // this is soup

// This is what a Fact looks like:
// {
//   ingredients: [5, 7]
//   possibleResults: [true, false, false, false, false, false, true]
// }
// combining ingredient 5 and 7 make either a red+ or a soup
var exampleFact = {
  ingredients: [exampleIngredientA, exampleIngredientB],
  possibleResults: [examplePotionA, examplePotionB]
}

console.log(check(exampleFact, exampleWorld))

export default AlchemistsSolverApp;
<commit_msg>Adds row headers<commit_after>// === UI stuff ===
//TODO visualize the alchemicals in the row headers of the table
//TODO add pictures for the ingredients in the column headers of the table
//TODO add pictures for the ingredients in the radio buttons
//TODO add pictures of the potions for the check boxes
//TODO add refresh indicators http://www.material-ui.com/#/components/refresh-indicator

// === Other stuff ===
//TODO add other views
//TODO add other fact types (periscope / beginner debunking
//TODO make the view match the state for the buttons etc.

// The expected number of bits of information from real science is
// -[1/7*lg(1/7) * 7]
// = 2.807 bits

// The expected number of bits of information from experimenting randomly on an adventurer is
// -[1/7*lg(1/7) + 2/7*lg(2/7) + 1/7*lg(1/7) + 3/7*lg(3/7)]
// = 1.842 bits

import React from 'react';
// import logo from './logo.svg';
import './App.css';

import _ from 'lodash'
import update from 'react-addons-update'
import PureRenderMixin from 'react-addons-pure-render-mixin';

import {Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn} from 'material-ui/Table';
import RaisedButton from 'material-ui/RaisedButton';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

const style = {
  margin: 12,
};

// This is what an Alchemical looks like:
// [1, 1, -1]

// Here are all of the Alchemicals:
var alchemicals = [
  [-1, +1, -1],
  [+1, -1, +1],
  [+1, -1, -1],
  [-1, +1, +1],
  [-1, -1, +1],
  [+1, +1, -1],
  [-1, -1, -1],
  [+1, +1, +1],
]

var ingredients = [
  "Mushroom",
  "Fern",
  "Toad",
  "Birdclaw",
  "Flower",
  "Mandrake",
  "Scorion",
  "Feather",
]

var potions = {
  "Red+":   [+1, 0, 0],
  "Red-":   [-1, 0, 0],
  "Green+": [0, +1, 0],
  "Green-": [0, -1, 0],
  "Blue+":  [0, 0, +1],
  "Blue-":  [0, 0, -1],
  "Soup":   [0, 0, 0],
}

var Fact = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return

      {this.props.index}: {JSON.stringify(this.props.item)}
      <RaisedButton onClick={this.props.deleteFact} value={this.props.index} style={style}>Delete</RaisedButton>

  }
});

var FactList = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return
{this.props.items.map((fact, factIndex) => <Fact key={factIndex} item={fact} index={factIndex} deleteFact={this.props.deleteFact}/>)}
;
  }
});

var Ingredient = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return
<input type="radio" name="ingredient" onChange={this.props.callback}/>{this.props.name}

  }
})

var Potion = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return
<input type="checkbox" name="potion" onChange={this.props.callback}/>{this.props.name}

  }
})

var SheetCell = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return <TableRowColumn>{this.percentage()}</TableRowColumn>
  },
  percentage: function() {
    var worlds = _.filter(this.props.worlds, _.bind(function(world) {
      return _.isEqual(world[this.props.ingredientIndex], this.props.alchemical)
    }, this))
    return Math.round(100 * worlds.length / this.props.worlds.length, 0)
  }
})

var SheetRow = React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return (
      <TableRow>
        <TableRowColumn>{this.props.alchemical}</TableRowColumn>
        {ingredients.map((ingredient, index) => <SheetCell ingredientIndex={index} alchemical={this.props.alchemical} key={index} worlds={this.props.worlds}/>)}
      </TableRow>
    )
  }
})

var AlchemistsSolverApp = React.createClass({
  mixins: [PureRenderMixin],

  getInitialState: function() {
    return {
      factlist: [],
      currentFact: this.makeNewFact(),
      worlds: permutator(alchemicals),
    }
  },
  makeNewFact: function() {
    return {
      ingredients: [0, 1],
      possibleResults: [false, false, false, false, false, false, false]
    }
  },
  handleSubmit: function(e) {
    e.preventDefault();
    this.setState({
      factlist: this.state.factlist.concat([this.state.currentFact]),
      worlds: _.filter(this.state.worlds, _.curry(check)(this.state.currentFact)),
    });
  },
  ingredientChange: function(ingredientIndex, ingredient) {
    var newIngredients = [];
    newIngredients[ingredientIndex] = ingredient;
    var newState = update(this.state,
      {currentFact: {ingredients: {$merge: newIngredients}}})
    this.setState(newState)
  },
  potionChange: function(potionIndex) {
    var newPossibleResults = [];
    newPossibleResults[potionIndex] = !this.state.currentFact.possibleResults[potionIndex];
    var newState = update(this.state,
      {currentFact: {possibleResults: {$merge: newPossibleResults}}})
    this.setState(newState)
  },
  render: function() {
    var self = this;
    return (
      <MuiThemeProvider>

Alchemists Solver

          <FactList items={this.state.factlist} deleteFact={this.deleteFact}/>

          <form action="" style={{display: "inline-block"}}>
            {ingredients.map((name, index) => <Ingredient name={name} ingredientNumber={0} key={index} callback={function() {self.ingredientChange(0, index)}} />)}
          </form>

          <form action="" style={{display: "inline-block"}}>
            {ingredients.map((name, index) => <Ingredient name={name} ingredientNumber={1} key={index} callback={function() {self.ingredientChange(1, index)}} />)}
          </form>

          <form action="" style={{display: "inline-block"}}>
            {_.keys(potions).map((name, index) => <Potion name={name} key={index} callback={function() {self.potionChange(index)}} />)}
          </form>

          <RaisedButton label="Add Fact" primary={true} style={style} onClick={this.handleSubmit}/>

Remaining worlds: {this.computeStuff()}

            <TableHeader displaySelectAll={false} adjustForCheckbox={false}>
              <TableRow>
                <TableHeaderColumn/>
                {ingredients.map((name, index) => <TableHeaderColumn key={index}>{name}</TableHeaderColumn>)}
              </TableRow>
            </TableHeader>
            <TableBody>
              {alchemicals.map((alchemical, index) => <SheetRow alchemical={alchemical} key={index} worlds={this.state.worlds}/>)}
            </TableBody>

      </MuiThemeProvider>
    );
  },
  deleteFact: function(e) {
    var newFactList = removeAtIndex(this.state.factlist, parseInt(e.target.value, 10))
    var worlds = permutator(alchemicals)
    for (var factIndex in newFactList) {
      var fact = newFactList[factIndex]
      console.log(fact)
      worlds = _.filter(worlds, _.curry(check)(fact))
    }
    this.setState({factlist: newFactList, worlds: worlds})
  },
  computeStuff: function() {
    return this.state.worlds.length
  }
});

// non-mutating
function removeAtIndex(arr, index) {
  return _.filter(arr, function(val, idx) {return idx !== index})
}

var ColorMapping = {
  0: "red",
  1: "green",
  2: "blue",
};

// var SignMapping = {
//   -1: "minus",
//   0: "neutral",
//   1: "plus",
// };

// Alchemical -> Alchemical -> Potion
function mix(alchemicalA, alchemicalB) {
  var mean = _.zipWith(alchemicalA, alchemicalB, function(a,b){return (a+b)/2})
  for (var index = 0; index < 3; index++) {
    if (mean[(index+1) % 3] !== 0)
      mean[index] = 0
  }

  return mean
}

function check(fact, world) {
  var alchemicalA = world[fact.ingredients[0]]
  var alchemicalB = world[fact.ingredients[1]]
  var result = mix(alchemicalA, alchemicalB)
  var potionIndex = _.findIndex(_.values(potions), _.curry(_.isEqual)(result))
  return fact.possibleResults[potionIndex]
}

// http://stackoverflow.com/a/20871714/6036628
function permutator(inputArr) {
  var results = [];

  function permute(arr, memo) {
    var cur, memo = memo || [];

    for (var i = 0; i < arr.length; i++) {
      cur = arr.splice(i, 1);
      if (arr.length === 0) {
        results.push(memo.concat(cur));
      }
      permute(arr.slice(), memo.concat(cur));
      arr.splice(i, 0, cur[0]);
    }

    return results;
  }

  return permute(inputArr);
}

// ===== Unit tests =====

// Here is what a World looks like:
var exampleWorld = [
  [+1, -1, -1],
  [-1, +1, -1],
  [+1, -1, +1],
  [-1, +1, +1],
  [+1, +1, +1],
  [-1, -1, +1],
  [+1, +1, -1],
  [-1, -1, -1],
]

// This is what an Ingredient looks like:
var exampleIngredientA = 4
var exampleIngredientB = 5

// This is what a Potion looks like:
var examplePotionA = [0, 0, 1] // this is a blue+
var examplePotionB = [0, 0, 0] // this is soup

// This is what a Fact looks like:
// {
//   ingredients: [5, 7]
//   possibleResults: [true, false, false, false, false, false, true]
// }
// combining ingredient 5 and 7 make either a red+ or a soup
var exampleFact = {
  ingredients: [exampleIngredientA, exampleIngredientB],
  possibleResults: [examplePotionA, examplePotionB]
}

console.log(check(exampleFact, exampleWorld))

export default AlchemistsSolverApp;
<|endoftext|>


<commit_before>/************************************************************
 * Problem: https://leetcode.com/problems/two-sum/
   Given an array of integers, return indices of the two numbers such that they add up to a specific target.
   You may assume that each input would have exactly one solution.
   The return format had been changed to zero-based indices.

 * Example:
   Given nums = [2, 7, 11, 15], target = 9,
   Because nums[0] + nums[1] = 2 + 7 = 9,
   return [0, 1].

 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}

 * Analysis: requirement is mainly on getting 'key' from valid 'value',
   which makes Hash to be an efficient data structure to use.

************************************************************/

var twoSum = {};
/*
 * Solution 1: brutal force loop
 *
 * "N" is nums.length.
 * Time complexity: O(N^2)
 * Space complexity: O(1)
 */
twoSum.brutal = function(nums, target) {
    if (!(Array.isArray(nums) && nums.length > 1) || typeof target !== 'number') {
        return;
    }

    var i, j, len = nums.length,
        compensate;

    for (i = 0; i < len; i++) {
        compensate = target - nums[i];

        for (j = i + 1; j < len; j++) {
            if (nums[j] === compensate) {
                return [i, j];
            }
        }
    }
    return;
};

/*
 * Solution 2: Sort then search
 * Sorted list will be efficiently performed operations like binary search, etc.
 * It can be useful for large size collections.
 *
 * "N" is nums.length.
 * Time complexity:  O(?)
 * Space complexity: O(?) depend on browser impl of Array:sort
 */
twoSum.sortedSearch = function(nums, target) {
    if (!(Array.isArray(nums) && nums.length > 1) || typeof target !== 'number') {
        return;
    }

    // sort nums for quicker search
    // js array sort() uses pretty efficient sort algorithms, could have O(N) to O(NlogN) time complexity depend on browser
    // some browser such as Mozilla uses mergeSort, which will add extra space complexity
    // (http://stackoverflow.com/questions/234683/javascript-array-sort-implementation)
    var sortedNums = nums.sort(),
        i, lo, hi, mid, compensate;

    for (i = 0; i < sortedNums.length; i++) {
        lo = i;
        hi = sortedNums.length - 1;
        compensate = target - sortedNums[lo];

        // binary search the compensate target
        while (lo <= hi) {
            // Key is in a[lo..hi] or not present.
            mid = lo + (hi - lo) / 2;

            if (compensate < sortedNums[mid]) {
                hi = mid - 1;
            } else if (compensate > sortedNums[mid]) {
                lo = mid + 1;
            } else {
                return [lo, mid];
            }
        }
    }
    return;
};

/*
 * Solution 3: Hash
 * @Key: nums.value
 * @Value: nums.key
 *
 * "N" is nums.length.
 * Time complexity:  O(N)
 * Space complexity: O(N)
 */
twoSum.hash = function(nums, target) {
    if (!(Array.isArray(nums) && nums.length > 1) || typeof target !== 'number') {
        return;
    }

    var i, hash = {}, key,
        len = nums.length,
        compensate;

    for (i = 0; i < len; i++) {
        compensate = target - nums[i];

        key = hash[compensate];
        if (typeof key === 'number' && key !== i) {
            return [key, i];
        }
        hash[nums[i]] = i;
    }
    return;
};

/************************************************************
 * Lessons:
   1. Consider introduce appriorate helper data structures to solve problems.

   2. for list:array based problem, we can consider sort it first and utilize some of its treats like binary search.

   3. Key, Value are just relative and can be exchanged when needed.
   For js, with the ES6 Map/WeakMap that allows all types to be 'key', this trick can be applied easier.

************************************************************/<commit_msg>Rename<commit_after>/************************************************************
 * Problem: https://leetcode.com/problems/two-sum/
   Given an array of integers, return indices of the two numbers such that they add up to a specific target.
   You may assume that each input would have exactly one solution.
   The return format had been changed to zero-based indices.

 * Example:
   Given nums = [2, 7, 11, 15], target = 9,
   Because nums[0] + nums[1] = 2 + 7 = 9,
   return [0, 1].

 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}

 * Analysis: requirement is mainly on getting 'key' from valid 'value',
   which makes Hash to be an efficient data structure to use.

************************************************************/

var twoSum = {};
/*
 * Solution 1: brutal force loop
 *
 * "N" is nums.length.
 * Time complexity: O(N^2)
 * Space complexity: O(1)
 */
twoSum.brutal = function(nums, target) {
    if (!(Array.isArray(nums) && nums.length > 1) || typeof target !== 'number') {
        return;
    }

    var i, j, len = nums.length,
        compensate;

    for (i = 0; i < len; i++) {
        compensate = target - nums[i];

        for (j = i + 1; j < len; j++) {
            if (nums[j] === compensate) {
                return [i, j];
            }
        }
    }
    return;
};

/*
 * Solution 2: Sort then search
 * Sorted list will be efficiently performed operations like binary search, etc.
 * It can be useful for large size collections.
 *
 * "N" is nums.length.
 * Time complexity:  O(?)
 * Space complexity: O(?) depend on browser impl of Array:sort
 */
twoSum.sortThenSearch = function(nums, target) {
    if (!(Array.isArray(nums) && nums.length > 1) || typeof target !== 'number') {
        return;
    }

    // sort nums for quicker search
    // js array sort() uses pretty efficient sort algorithms, could have O(N) to O(NlogN) time complexity depend on browser
    // some browser such as Mozilla uses mergeSort, which will add extra space complexity
    // (http://stackoverflow.com/questions/234683/javascript-array-sort-implementation)
    var sortedNums = nums.sort(),
        i, lo, hi, mid, compensate;

    for (i = 0; i < sortedNums.length; i++) {
        lo = i;
        hi = sortedNums.length - 1;
        compensate = target - sortedNums[lo];

        // binary search the compensate target
        while (lo <= hi) {
            // Key is in a[lo..hi] or not present.
            mid = lo + (hi - lo) / 2;

            if (compensate < sortedNums[mid]) {
                hi = mid - 1;
            } else if (compensate > sortedNums[mid]) {
                lo = mid + 1;
            } else {
                return [lo, mid];
            }
        }
    }
    return;
};

/*
 * Solution 3: Hash
 * @Key: nums.value
 * @Value: nums.key
 *
 * "N" is nums.length.
 * Time complexity:  O(N)
 * Space complexity: O(N)
 */
twoSum.hash = function(nums, target) {
    if (!(Array.isArray(nums) && nums.length > 1) || typeof target !== 'number') {
        return;
    }

    var i, hash = {}, key,
        len = nums.length,
        compensate;

    for (i = 0; i < len; i++) {
        compensate = target - nums[i];

        key = hash[compensate];
        if (typeof key === 'number' && key !== i) {
            return [key, i];
        }
        hash[nums[i]] = i;
    }
    return;
};

/************************************************************
 * Lessons:
   1. Consider introduce appriorate helper data structures to solve problems.

   2. for list:array based problem, we can consider sort it first and utilize some of its treats like binary search.

   3. Key, Value are just relative and can be exchanged when needed.
   For js, with the ES6 Map/WeakMap that allows all types to be 'key', this trick can be applied easier.

************************************************************/<|endoftext|>


<commit_before>function map(resolve, morphism) {
	return function(value) {
		var result = morphism(value);

		if (result && result.isMonad) {
			result.bind(resolve);
		} else {
			resolve(result);
		}
	};
}

function Future(producer) {
	var pending = [];

	producer(function(value) {
		pending.forEach(function(cb) {
			cb(value);
		});
	});

	return {
		isMonad: true,
		bind: function(right, left) {
			return Future(function(resolve) {
				pending.push(map(resolve, morphism));
			});
		}
	};
}

function asyncResolve(resolve) {
	setTimeout(function() {
		resolve(13);
	}, 1000);
}

Future(asyncResolve).bind(function(value) {
	return value * 3;
}).bind(function(value) {
	console.log(value);
});

var b = Future(asyncResolve);

b.bind(function(value) {
	console.log('1', value);
});

b.bind(function(value) {
	console.log('2', value);
});

Future(asyncResolve).bind(function(value) {
	return Future(function(resolve) {
		setTimeout(function() {
			resolve(value * 2);
		}, 2000);
	});
}).bind(function(a) {
	console.log(a);
})<commit_msg>incorrect fn<commit_after>function map(resolve, morphism) {
	return function(value) {
		var result = morphism(value);

		if (result && result.isMonad) {
			result.bind(resolve);
		} else {
			resolve(result);
		}
	};
}

function Future(producer) {
	var pending = [];

	producer(function(value) {
		pending.forEach(function(cb) {
			cb(value);
		});
	});

	return {
		isMonad: true,
		bind: function(right, left) {
			return Future(function(resolve) {
				pending.push(map(resolve, right));
			});
		}
	};
}

function asyncResolve(resolve) {
	setTimeout(function() {
		resolve(13);
	}, 1000);
}

Future(asyncResolve).bind(function(value) {
	return value * 3;
}).bind(function(value) {
	console.log(value);
});

var b = Future(asyncResolve);

b.bind(function(value) {
	console.log('1', value);
});

b.bind(function(value) {
	console.log('2', value);
});

Future(asyncResolve).bind(function(value) {
	return Future(function(resolve) {
		setTimeout(function() {
			resolve(value * 2);
		}, 2000);
	});
}).bind(function(a) {
	console.log(a);
})<|endoftext|>