<commit_before>/**
 * @file PhantomJS host adapter
 * @since 0.1.5
 */
/*#ifndef(UMD)*/
"use strict";
/*global _GPF_HOST*/ // Host types
/*global _gpfBootImplByHost*/ // Boot host specific implementation per host
/*global _gpfExit:true*/ // Exit function
/*global _gpfWebDocument:true*/ // Browser document object
/*global _gpfWebWindow:true*/ // Browser window object
/*#endif*/

/*jshint phantom: true*/
/*jshint browser: true*/
/*eslint-env phantomjs, browser*/

_gpfBootImplByHost[_GPF_HOST.PHANTOMJS] = function () {

    /* istanbul ignore next */ // exit.1
    _gpfExit = function (code) {
        phantom.exit(code);
    };

    _gpfWebWindow = window;
    _gpfWebDocument = document;

};
<commit_msg>Introduces gpf.fs.read for all hosts (#168)<commit_after>/**
 * @file PhantomJS host adapter
 * @since 0.1.5
 */
/*#ifndef(UMD)*/
"use strict";
/*global _GPF_HOST*/ // Host types
/*global _gpfBootImplByHost*/ // Boot host specific implementation per host
/*global _gpfExit:true*/ // Exit function
/*global _gpfNodeFs:true*/ // Node/PhantomJS require("fs")
/*global _gpfWebDocument:true*/ // Browser document object
/*global _gpfWebWindow:true*/ // Browser window object
/*#endif*/

/*jshint phantom: true*/
/*jshint browser: true*/
/*eslint-env phantomjs, browser*/

_gpfBootImplByHost[_GPF_HOST.PHANTOMJS] = function () {

    /* istanbul ignore next */ // exit.1
    _gpfExit = function (code) {
        phantom.exit(code);
    };

    _gpfWebWindow = window;
    _gpfWebDocument = document;
    _gpfNodeFs = require("fs");

};
<|endoftext|>


<commit_before>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Mickael Jeanroy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

(function(window, document, undefined) {

  if (!window.addEventListener) {
  	// Polyfill event
    var Event = function(e, element) {
      if (!(this instanceof Event)) {
        return new Event(e, element);
      }

      for (var i in e) {
        this[i] = e[i];
      }

      this.currentTarget =  element;
      this.target = e.srcElement || element;

      this.returnValue = true;
      this.cancelBubble = false;
      this.timeStamp = new Date().getTime();
    };

    Event.prototype = {
      // If this method is called, the default action of the event
      // will not be triggered.
      preventDefault: function() {
        this.returnValue = false;
      },

      // Prevents the event from bubbling up the DOM tree, preventing any parent
      // handlers from being notified of the event.
      stopPropagation: function() {
        this.cancelBubble = true;
      },

      // Keeps the rest of the handlers from being executed and
      // prevents the event from bubbling up the DOM tree.
      stopImmediatePropagation: function() {
        this.cancelBubble = true;
      }
    };

    var findIndex = function(array, callback) {
      for (var i = 0, size = array.length; i < size; ++i) {
        if (callback.call(null, array[i], i)) {
          return i;
        }
      }

      return -1;
    };

    var listenerIterator = function(type, listener) {
      return function(o) {
        return o.type === type && o.listener === listener;
      };
    };

    // Add event listener to element
    var addEventListener = function(type, listener) {
      var element = this;
      var listeners = element.$$listeners = element.$$listeners || [];
      var iterator = listenerIterator(type, listener);

      // Do not attach twice...
      var index = findIndex(listeners, iterator);
      var callback;
      if (index === -1) {
        callback = function(e) {
          var handleEvent = listener;
          var ctx = element;
          if (typeof listener !== 'function') {
            handleEvent = typeof listener.handleEvent === 'function' ? listener.handleEvent : function() {};
            ctx = listener;
          }

          handleEvent.call(ctx, new Event(e || window.event, element));
        };

        listeners.push({
          type: type,
          listener: listener,
          callback: callback
        });

        element.attachEvent('on' + type, callback);
      }

      // Free memory
      element  = listeners = callback = iterator = null;
    };

    var removeEventListener = function(type, listener) {
      var element = this;
      var listeners = element.$$listeners || [];
      var iterator = listenerIterator(type, listener);

      var o;
      var index = findIndex(listeners, iterator);
      if (index !== -1) {
        o = listeners[index];
        element.detachEvent('on' + type, o.callback);
        listeners.splice(index, 1);
      }

      // Free memory
      element  = listeners = o = iterator = null;
    };

    var apply = function(array) {
      for (var i = 0, size = array.length; i < size; ++i) {
        var o = array[i];
        o.addEventListener = addEventListener;
        o.removeEventListener = removeEventListener;
      }
    };

    // Attach methods
    if ('Element' in window) {
      // IE8
      apply([document, window, Element.prototype]);
    }
    else {
      // IE7
      apply([document, window]);

      var head = document.getElementsByTagName('head')[0];
      var style = document.createElement('style');
      head.insertBefore(style, head.firstChild);
      style.styleSheet.cssText = '*{-ms-event-prototype:expression(!this.addEventListener&&(this.addEventListener=addEventListener)&&(this.removeEventListener=removeEventListener))}';
    }
  }

})(window, document, void 0);<commit_msg>fix: do not forget to return listener value<commit_after>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Mickael Jeanroy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

(function(window, document, undefined) {

  if (!window.addEventListener) {
  	// Polyfill event
    var Event = function(e, element) {
      if (!(this instanceof Event)) {
        return new Event(e, element);
      }

      for (var i in e) {
        this[i] = e[i];
      }

      this.currentTarget =  element;
      this.target = e.srcElement || element;

      this.returnValue = true;
      this.cancelBubble = false;
      this.timeStamp = new Date().getTime();
    };

    Event.prototype = {
      // If this method is called, the default action of the event
      // will not be triggered.
      preventDefault: function() {
        this.returnValue = false;
      },

      // Prevents the event from bubbling up the DOM tree, preventing any parent
      // handlers from being notified of the event.
      stopPropagation: function() {
        this.cancelBubble = true;
      },

      // Keeps the rest of the handlers from being executed and
      // prevents the event from bubbling up the DOM tree.
      stopImmediatePropagation: function() {
        this.cancelBubble = true;
      }
    };

    var findIndex = function(array, callback) {
      for (var i = 0, size = array.length; i < size; ++i) {
        if (callback.call(null, array[i], i)) {
          return i;
        }
      }

      return -1;
    };

    var listenerIterator = function(type, listener) {
      return function(o) {
        return o.type === type && o.listener === listener;
      };
    };

    // Add event listener to element
    var addEventListener = function(type, listener) {
      var element = this;
      var listeners = element.$$listeners = element.$$listeners || [];
      var iterator = listenerIterator(type, listener);

      // Do not attach twice...
      var index = findIndex(listeners, iterator);
      var callback;
      if (index === -1) {
        callback = function(e) {
          var handleEvent = listener;
          var ctx = element;
          if (typeof listener !== 'function') {
            handleEvent = typeof listener.handleEvent === 'function' ? listener.handleEvent : function() {};
            ctx = listener;
          }

          return handleEvent.call(ctx, new Event(e || window.event, element));
        };

        listeners.push({
          type: type,
          listener: listener,
          callback: callback
        });

        element.attachEvent('on' + type, callback);
      }

      // Free memory
      element  = listeners = callback = iterator = null;
    };

    var removeEventListener = function(type, listener) {
      var element = this;
      var listeners = element.$$listeners || [];
      var iterator = listenerIterator(type, listener);

      var o;
      var index = findIndex(listeners, iterator);
      if (index !== -1) {
        o = listeners[index];
        element.detachEvent('on' + type, o.callback);
        listeners.splice(index, 1);
      }

      // Free memory
      element  = listeners = o = iterator = null;
    };

    var apply = function(array) {
      for (var i = 0, size = array.length; i < size; ++i) {
        var o = array[i];
        o.addEventListener = addEventListener;
        o.removeEventListener = removeEventListener;
      }
    };

    // Attach methods
    if ('Element' in window) {
      // IE8
      apply([document, window, Element.prototype]);
    }
    else {
      // IE7
      apply([document, window]);

      var head = document.getElementsByTagName('head')[0];
      var style = document.createElement('style');
      head.insertBefore(style, head.firstChild);
      style.styleSheet.cssText = '*{-ms-event-prototype:expression(!this.addEventListener&&(this.addEventListener=addEventListener)&&(this.removeEventListener=removeEventListener))}';
    }
  }

})(window, document, void 0);<|endoftext|>


<commit_before>"use strict";
const ipcRenderer = require('electron').ipcRenderer;
const webFrame = require('web-frame');
const menu = require('./menu.js');

const lock = (object, key, value) => Object.defineProperty(object, key, {
  get: () => value,
  set: () => {
  }
});

webFrame.setZoomLevelLimits(1, 1);

//lock(window, 'console', window.console);

let angular = window.angular = {};
let angularBootstrapReal;
Object.defineProperty(angular, 'bootstrap', {
  get: () => angularBootstrapReal ? function (element, moduleNames) {
    const moduleName = 'webwxApp';
    if (moduleNames.indexOf(moduleName) >= 0) {
      let constants;
      angular.injector(['ng', 'Services']).invoke(['confFactory', (confFactory) => (constants = confFactory)]);
      angular.module(moduleName).config([
        '$httpProvider',
        ($httpProvider) => {
          $httpProvider.defaults.transformResponse.push((value) => {
            if (typeof value === 'object' && value !== null && value.AddMsgList instanceof Array) {
              value.AddMsgList.forEach((msg) => {
                switch (msg.MsgType) {
                  case constants.MSGTYPE_EMOTICON:
                    const rec = msg.Content.match(/<msg><emoji.+cdnurl\s*=\s*"(.+?)".+thumburl\s*=\s*"(.*?)"/);
                    if (rec !== null) {
                      let actualContent = msg.Content;
                      lock(msg, 'MMActualContent', actualContent);
                      lock(msg, 'MMDigest', '[Emoticon resolved by Electronic WeChat]');
                      lock(msg, 'MsgType', constants.MSGTYPE_EMOTICON);
                    }
                    break;
                  case constants.MSGTYPE_RECALLED:
                    lock(msg, 'MsgType', constants.MSGTYPE_SYS);
                    lock(msg, 'MMActualContent', '阻止了一次撤回');
                    lock(msg, 'MMDigest', '阻止了一次撤回');
                    break;
                }
              });
            }
            return value;
          });
        }
      ]);
    }
    return angularBootstrapReal.apply(angular, arguments);
  } : angularBootstrapReal,
  set: (real) => (angularBootstrapReal = real)
});

window.injectBundle = {};
injectBundle.getBadgeJS = () => {
  $(".chat_list.scroll-content").bind('DOMSubtreeModified', () => {
    var count = 0;
    $(".icon.web_wechat_reddot_middle").each(function () {
      count += parseInt(this.textContent);
    });
    if (count > 0) {
      ipcRenderer.send("badge-changed", count.toString());
    } else {
      ipcRenderer.send("badge-changed", "");
    }
  })
};

injectBundle.getProfileNameJS = () => {
  let updateName = ()=> {
    let name = $('.display_name').text();
    ipcRenderer.send("profile-name-changed", name);
  };
  $('.display_name').ready(updateName).change(updateName);
};

menu.create();
<commit_msg>Fix emoticon<commit_after>"use strict";
const ipcRenderer = require('electron').ipcRenderer;
const webFrame = require('web-frame');
const menu = require('./menu.js');

const lock = (object, key, value) => Object.defineProperty(object, key, {
  get: () => value,
  set: () => {
  }
});

webFrame.setZoomLevelLimits(1, 1);

//lock(window, 'console', window.console);

let angular = window.angular = {};
let angularBootstrapReal;
Object.defineProperty(angular, 'bootstrap', {
  get: () => angularBootstrapReal ? function (element, moduleNames) {
    const moduleName = 'webwxApp';
    if (moduleNames.indexOf(moduleName) >= 0) {
      let constants;
      angular.injector(['ng', 'Services']).invoke(['confFactory', (confFactory) => (constants = confFactory)]);
      angular.module(moduleName).config([
        '$httpProvider',
        ($httpProvider) => {
          $httpProvider.defaults.transformResponse.push((value) => {
            if (typeof value === 'object' && value !== null && value.AddMsgList instanceof Array) {
              value.AddMsgList.forEach((msg) => {
                switch (msg.MsgType) {
                  case constants.MSGTYPE_EMOTICON:
                    lock(msg, 'MMDigest', '[Emoticon]');
                    lock(msg, 'MsgType', constants.MSGTYPE_EMOTICON);
                    break;
                  case constants.MSGTYPE_RECALLED:
                    lock(msg, 'MsgType', constants.MSGTYPE_SYS);
                    lock(msg, 'MMActualContent', '阻止了一次撤回');
                    lock(msg, 'MMDigest', '阻止了一次撤回');
                    break;
                }
              });
            }
            return value;
          });
        }
      ]);
    }
    return angularBootstrapReal.apply(angular, arguments);
  } : angularBootstrapReal,
  set: (real) => (angularBootstrapReal = real)
});

window.injectBundle = {};
injectBundle.getBadgeJS = () => {
  $(".chat_list.scroll-content").bind('DOMSubtreeModified', () => {
    var count = 0;
    $(".icon.web_wechat_reddot_middle").each(function () {
      count += parseInt(this.textContent);
    });
    if (count > 0) {
      ipcRenderer.send("badge-changed", count.toString());
    } else {
      ipcRenderer.send("badge-changed", "");
    }
  })
};

injectBundle.getProfileNameJS = () => {
  let updateName = ()=> {
    let name = $('.display_name').text();
    ipcRenderer.send("profile-name-changed", name);
  };
  $('.display_name').ready(updateName).change(updateName);
};

menu.create();
<|endoftext|>


<commit_before>/**
 * Index Class
 *
 * Creates an Index Class
 * @class
 */
class Index {
  /**
   *
   *
   * @constructor
   */
  constructor() {
    this.index = {};
    this.files = {};
  }

  /**
   * checkJSON
   *
   * Checks whether the file is a JSON file
   *
   * @param {object} responseFile - Content of the file
   * @returns {bool} | {object} A false boolean if file is not valid
   *
   * or a JSON object if valid file
   */
  static checkJSON(responseFile) {
    if (responseFile.length > 0
      && responseFile[0].title && responseFile[0].text) {
      return responseFile;
    }
    return false;
  }

  /**
   * tidyText
   *
   * Removes punctuation and whitespace
   *
   * @param {string} values - String of words in file as a value
   * @returns {array} An array of words with no whitespace or punctuation
   */
  static tidyText(values) {
    const words = values.toLowerCase()
      .replace(/\W+/g, ' ')
      .trim()
      .split(' ');
    return this.arrangeWords(words);
  }

  /**
   * arrangeWords
   *
   * bring out an array the contain properly arranged words
   * @param {array} words words in each file
   * @returns {array} returns an array of arranged words
   *
   */
  static arrangeWords(words) {
    return words.filter((item, position) => words.indexOf(item) === position);
  }

  /**
   * createIndex
   *
   * Creates the index from the file passed
   *
   * @param {string} fileName - Name of the file
   * @param {object} file - Content of the file
   * @returns {object} An object of created indices
   */
  createIndex(fileName, file) {
    file = Index.checkJSON(file);
    if (!file) {
      return false;
    }

    const wordsIndex = {};
    file.forEach((document, index) => {
      const fileString = Index.tidyText(document.text);
      fileString.forEach((word) => {
        if (wordsIndex[word]) {
          wordsIndex[word].push(index);
        } else {
          wordsIndex[word] = [index];
        }
      });
    });
    if (!this.index[fileName]) {
      this.index[fileName] = wordsIndex;
    }
    return this.index;
  }

  /**
   * getIndex
   *
   * Returns the index
   *
   * @param {string} fileName - Name to the file
   * @returns {object} An object of indices
   */
  getIndex(fileName) {
    return this.index[fileName];
  }

  /**
   * searchIndex
   * Search for terms in a file
   * @param {String} terms word(s) to be searched in the index
   * @param {String} filename file to search for words
   * @return {Object} words and their index
   */
  static searchWords(...terms) {
    let toSearch = '';

    for (let word = 0; word < terms.length; word += 1) {
      if (Array.isArray(...[word])) {
        terms[word].join(' ');
      }
      toSearch += `${terms[word]} `;
    }
    return toSearch.split(',').join(' ');
  }

  /**
   * searchIndex
   *
   * Checks whether searched terms are in the created index
   *
   * @param {string} fileName
   * @param {string} terms
   * @returns {object} An object of the results
   */
  searchIndex(fileName, ...terms) {
    const results = {};
    let searchTerms = [];
    const toSearch = Index.searchWords(...terms);
    searchTerms = Index.tidyText(toSearch.toLowerCase());

    if (!fileName) {
      Array.prototype.forEach.call(this.index, (file) => {
        results[file] = Index.searchResult(searchTerms, this.index[file]);
      });
    } else {
      const searchFile = this.index[fileName];
      results[fileName] = Index.searchResult(searchTerms, searchFile);
    }
    return results;
  }

  /**
   * searchResult
   *
   * Returns the search results
   *
   * @param {array} searchTerms
   * @param {object} file
   * @returns {object} results
   */
  static searchResult(searchTerms, file) {
    const results = {};
    searchTerms.forEach((term, index) => {
      if (file[term]) {
        results[searchTerms[index]] = file[term];
      }
    });
    return results;
  }
}
<commit_msg>Making failed test to pass<commit_after>/**
 * Index Class
 *
 * Creates an Index Class
 * @class
 */
class Index {
  /**
   *
   *
   * @constructor
   */
  constructor() {
    this.index = {};
    this.files = {};
  }

  /**
   * checkJSON
   *
   * Checks whether the file is a JSON file
   *
   * @param {object} responseFile - Content of the file
   * @returns {bool} | {object} A false boolean if file is not valid
   *
   * or a JSON object if valid file
   */
  static checkJSON(responseFile) {
    if (responseFile.length > 0
      && responseFile[0].title && responseFile[0].text) {
      return responseFile;
    }
    return false;
  }

  /**
   * tidyText
   *
   * Removes punctuation and whitespace
   *
   * @param {string} values - String of words in file as a value
   * @returns {array} An array of words with no whitespace or punctuation
   */
  static tidyText(values) {
    const words = values.toLowerCase()
      .replace(/\W+/g, ' ')
      .trim()
      .split(' ');
    return this.arrangeWords(words);
  }

  /**
   * arrangeWords
   *
   * bring out an array the contain properly arranged words
   * @param {array} words words in each file
   * @returns {array} returns an array of arranged words
   *
   */
  static arrangeWords(words) {
    return words.filter((item, position) => words.indexOf(item) === position);
  }

  /**
   * createIndex
   *
   * Creates the index from the file passed
   *
   * @param {string} fileName - Name of the file
   * @param {object} file - Content of the file
   * @returns {object} An object of created indices
   */
  createIndex(fileName, file) {
    file = Index.checkJSON(file);
    if (!file) {
      return false;
    }

    const wordsIndex = {};
    file.forEach((document, index) => {
      const fileString = Index.tidyText(document.text);
      fileString.forEach((word) => {
        if (wordsIndex[word]) {
          wordsIndex[word].push(index);
        } else {
          wordsIndex[word] = [index];
        }
      });
    });
    if (!this.index[fileName]) {
      this.index[fileName] = wordsIndex;
    }
    return this.index;
  }

  /**
   * getIndex
   *
   * Returns the index
   *
   * @param {string} fileName - Name to the file
   * @returns {object} An object of indices
   */
  getIndex(fileName) {
    return this.index[fileName];
  }

  /**
   * searchIndex
   * Search for terms in a file
   * @param {String} terms word(s) to be searched in the index
   * @param {String} filename file to search for words
   * @return {Object} words and their index
   */
  static searchWords(...terms) {
    let toSearch = '';

    for (let word = 0; word < terms.length; word += 1) {
      if (Array.isArray(...[word])) {
        terms[word].join(' ');
      }
      toSearch += `${terms[word]} `;
    }
    return toSearch.split(',').join(' ');
  }

  /**
   * searchIndex
   *
   * Checks whether searched terms are in the created index
   *
   * @param {string} fileName
   * @param {string} terms
   * @returns {object} An object of the results
   */
  searchIndex(fileName, ...terms) {
    const results = {};
    let searchTerms = [];
    const toSearch = Index.searchWords(...terms);
    searchTerms = Index.tidyText(toSearch.toLowerCase());

    if (!fileName) {
      for (const file in this.index) {
        results[file] = Index.searchResult(searchTerms, this.index[file]);
      }
    } else {
      const searchFile = this.index[fileName];
      results[fileName] = Index.searchResult(searchTerms, searchFile);
    }
    return results;
  }

  /**
   * searchResult
   *
   * Returns the search results
   *
   * @param {array} searchTerms
   * @param {object} file
   * @returns {object} results
   */
  static searchResult(searchTerms, file) {
    const results = {};
    searchTerms.forEach((term, index) => {
      if (file[term]) {
        results[searchTerms[index]] = file[term];
      }
    });
    return results;
  }
}
<|endoftext|>


<commit_before>/**
 * Creates a new Index.
 * @class
 */
class Index {
  /**
   * class constructor
   * @constructor
   */
  constructor() {
    this.files = {};
  }

  /** Tokenize
  * Splits string to a sorted array of words
  *
  * @param {String} sentence
  * @returns {Array} containing unique words
  */
  static tokenize(sentence) {
    const wordString = this.removePunctuation(sentence);
    const wordList = wordString.toLowerCase().split(' ').sort();
    return this.filterWords(wordList);
  }

  /** Remove Punctuation
  * Removes non-alphanumeric characters from string
  *
  * @param {String} sentence
  * @returns {String} containing only alphanumeric characters
  */
  static removePunctuation(sentence) {
    return sentence.replace(/[^\w+\s+]/gi, ' ');
  }

  /** Filter Words
  *
  * Removes duplicate array elements and empty arrays
  *
  * @param {Array} words
  * @returns {Array} containing unique words
  */
  static filterWords(words) {
    return words.filter((item, index, list) => list
      .indexOf(item) === index && item.length > 0);
  }

  /** Get Book Text
    *
    * Concatenates text of all books
    *
    * @param {Object} books
    * @returns {String} containing text of all books
    */
  static getBookText(books) {
    return books.map(book => book.text).join(' ');
  }

  /** Create Index
  *
  * Creates Indices for files
  *
  * @param {String} fileName
  * @param {Object} books
  * @returns {null} stores index of file
  */
  createIndex(fileName, books) {
    const fileIndex = {};
    const bookText = Index.getBookText(books);
    const wordList = Index.tokenize(bookText);

  // loops through each book to check for existence of word(s)
    wordList.forEach((word) => {
      books.forEach((book, index) => {
        const regex = new RegExp(`\\b${word}\\b`, 'i');
        if (regex.test(book.text)) {
          if (fileIndex[word]) {
            fileIndex[word].push(index);
          } else {
            fileIndex[word] = [index];
          }
        }
      });
    });
    this.files[fileName].index = fileIndex;
  }

  /** Search Index
  *
  *  Searches index of file(s) for given terms
  *
  * @param {String} terms
  * @param {String} filePath
  * @returns {Object} SearchResult
  */
  searchIndex(terms, filePath) {
    const searchResult = {};
    terms = terms.toString();
    if (Index.removePunctuation(terms).match('^\\s*$')) {
      return false;
    }
    const wordList = Index.tokenize(terms);
    const fileKeys = this.files[filePath] ?
    [filePath] : Object.keys(this.files);
    fileKeys.forEach((filename) => {
      if (this.files[filename].index) {
        const fileIndex = this.files[filename].index;
        searchResult[filename] = {};
        wordList.forEach((word) => {
          searchResult[filename][word] = fileIndex[word] ? fileIndex[word] : [];
        });
      }
    });
    return searchResult;
  }

  /** Get index
  *
  * Returns index of file(s)
  *
  * @param {String} fileName
  * @returns {Object} containing index of file
  */
  getIndex(fileName) {
    return this.files[fileName].index;
  }

  /** Is Valid JSON
     *
     * Checks if contents of file is a correct JSON object
     *
     * @param {Object} uploadedFiles
     * @returns {Boolean} file contents if valid, returns false if invalid
     */
  static isValidJson(uploadedFiles) {
    try {
      const file = JSON.parse(uploadedFiles);
      file.forEach((item) => {
        if (!Object.prototype.hasOwnProperty.call(item, 'title')
          && !Object.prototype.hasOwnProperty.call(item, 'text')) {
          throw Error;
        }
      });
      return file;
    } catch (error) {
      return false;
    }
  }

  /** Add files
  *
  * Adds new file(s) to class
  *
  * @param {Object} fileContents
  * @param {Object} fileName
  * @returns {null} sets Class files
  */
  addFiles(fileContents, fileName) {
    const refinedName = fileName.replace(/\.json/g, '').replace(/\s+/g, '');
    if (fileContents) {
      this.files[refinedName] = {};
      this.files[refinedName].name = fileName;
      if (Array.isArray(fileContents) && fileContents.length !== 0) {
          // check if content is an array of objects
        this.files[refinedName].books = fileContents;
      } else {
          // do this if content has just one object
        this.files[refinedName].books = [fileContents];
      }
    }
  }
}
module.exports = Index;
<commit_msg>Correct addiles jsdoc parameter<commit_after>/**
 * Creates a new Index.
 * @class
 */
class Index {
  /**
   * class constructor
   * @constructor
   */
  constructor() {
    this.files = {};
  }

  /** Tokenize
  * Splits string to a sorted array of words
  *
  * @param {String} sentence
  * @returns {Array} containing unique words
  */
  static tokenize(sentence) {
    const wordString = this.removePunctuation(sentence);
    const wordList = wordString.toLowerCase().split(' ').sort();
    return this.filterWords(wordList);
  }

  /** Remove Punctuation
  * Removes non-alphanumeric characters from string
  *
  * @param {String} sentence
  * @returns {String} containing only alphanumeric characters
  */
  static removePunctuation(sentence) {
    return sentence.replace(/[^\w+\s+]/gi, ' ');
  }

  /** Filter Words
  *
  * Removes duplicate array elements and empty arrays
  *
  * @param {Array} words
  * @returns {Array} containing unique words
  */
  static filterWords(words) {
    return words.filter((item, index, list) => list
      .indexOf(item) === index && item.length > 0);
  }

  /** Get Book Text
    *
    * Concatenates text of all books
    *
    * @param {Object} books
    * @returns {String} containing text of all books
    */
  static getBookText(books) {
    return books.map(book => book.text).join(' ');
  }

  /** Create Index
  *
  * Creates Indices for files
  *
  * @param {String} fileName
  * @param {Object} books
  * @returns {null} stores index of file
  */
  createIndex(fileName, books) {
    const fileIndex = {};
    const bookText = Index.getBookText(books);
    const wordList = Index.tokenize(bookText);

  // loops through each book to check for existence of word(s)
    wordList.forEach((word) => {
      books.forEach((book, index) => {
        const regex = new RegExp(`\\b${word}\\b`, 'i');
        if (regex.test(book.text)) {
          if (fileIndex[word]) {
            fileIndex[word].push(index);
          } else {
            fileIndex[word] = [index];
          }
        }
      });
    });
    this.files[fileName].index = fileIndex;
  }

  /** Search Index
  *
  *  Searches index of file(s) for given terms
  *
  * @param {String} terms
  * @param {String} filePath
  * @returns {Object} SearchResult
  */
  searchIndex(terms, filePath) {
    const searchResult = {};
    terms = terms.toString();
    if (Index.removePunctuation(terms).match('^\\s*$')) {
      return false;
    }
    const wordList = Index.tokenize(terms);
    const fileKeys = this.files[filePath] ?
    [filePath] : Object.keys(this.files);
    fileKeys.forEach((filename) => {
      if (this.files[filename].index) {
        const fileIndex = this.files[filename].index;
        searchResult[filename] = {};
        wordList.forEach((word) => {
          searchResult[filename][word] = fileIndex[word] ? fileIndex[word] : [];
        });
      }
    });
    return searchResult;
  }

  /** Get index
  *
  * Returns index of file(s)
  *
  * @param {String} fileName
  * @returns {Object} containing index of file
  */
  getIndex(fileName) {
    return this.files[fileName].index;
  }

  /** Is Valid JSON
     *
     * Checks if contents of file is a correct JSON object
     *
     * @param {Object} uploadedFiles
     * @returns {Boolean} file contents if valid, returns false if invalid
     */
  static isValidJson(uploadedFiles) {
    try {
      const file = JSON.parse(uploadedFiles);
      file.forEach((item) => {
        if (!Object.prototype.hasOwnProperty.call(item, 'title')
          && !Object.prototype.hasOwnProperty.call(item, 'text')) {
          throw Error;
        }
      });
      return file;
    } catch (error) {
      return false;
    }
  }

  /** Add files
  *
  * Adds new file(s) to class
  *
  * @param {Object} fileContents
  * @param {String} fileName
  * @returns {null} sets Class files
  */
  addFiles(fileContents, fileName) {
    const refinedName = fileName.replace(/\.json/g, '').replace(/\s+/g, '');
    if (fileContents) {
      this.files[refinedName] = {};
      this.files[refinedName].name = fileName;
      if (Array.isArray(fileContents) && fileContents.length !== 0) {
          // check if content is an array of books
        this.files[refinedName].books = fileContents;
      } else {
        this.files[refinedName].books = [fileContents];
      }
    }
  }
}
module.exports = Index;
<|endoftext|>


<commit_before>var Helper = require('./inverted-helper.js');
var fs = require('fs');
var _ = require('lodash');

/**
* @class Index
*
* Creates an index object given the filepath,
* which consists of all the unique words in the file and a corresponding
* list of the documents in which they can be found within the file.
*/
function Index() {
  'use strict';
  var _this = this;

  this.key = '';
  this.indexObject = {};
  this.helperMethods = new Helper();

  /**
  * @method createIndex
  *
  * Creates the index object for a given filepath.
  *
  * @param {String} filepath
  * @return {Void}
  */
  this.createIndex = function(filepath) {
    if(filepath) {
      // Gets filename from filepath to serve as key in indexObject.
      _this.key = _this.helperMethods.getFilename(filepath);
      _this.indexObject[_this.key] = {};
      // Calls readJsonFile to asynchronously read JSON file.
      _this.readJsonFile(filepath, function(res) {
        _this.transformObjects(res);
      });
    } else {
      throw new Error('Cannot createIndex of ' + filepath);
    }
  };

  /**
  * @method readJsonFile
  *
  * Asynchronously reads JSON file.
  *
  * @param {String} filepath
  * @param {Function} callback
  * @return {void}
  */
  this.readJsonFile = function(filepath, callback) {
    var object;

    fs.readFile(filepath, 'utf8', function(err, data) {
      try{
        object = JSON.parse(data);
      }
      catch(error){
        throw new Error("Expected an array of JSON objects");
      }
      if(!_this.helperMethods.isValidArray(object)) {
        throw new Error('JSON file cannot be empty');
      }
      callback(object);
    });
  };

  /**
  * @method transformObjects
  *
  * Loops through an array of objects to populate the index object.
  *
  * @param {Array} arrayOfObj
  * @return {Void}
  */
  this.transformObjects = function(arrayOfObj) {
    var title,
        text;
    var uniqueArray = [];
    var arraylength = arrayOfObj.length;

    for(var objectIndex = 0; objectIndex < arraylength; objectIndex++) {
      // Removes punctuations and chages both title and text to lower case.
      // Splits title and text to array.
      title = _this.helperMethods.normalize(arrayOfObj[objectIndex].title).split(' ');
      text = _this.helperMethods.normalize(arrayOfObj[objectIndex].text).split(' ');
      // Creates an array of tokens and removes multiple occurrence of words.
      uniqueArray.push(_this.helperMethods.uniqueFilter(title,text));
    }
    // Populates the index object.
    uniqueArray.forEach(_this.makeIndexObject);
  };

  /**
  * @method makeIndexObject
  *
  * Loops through an array of tokens and populates the index object.
  *
  * @param {Array} array
  * @param {Number} location
  * @return {Void}
  */
  this.makeIndexObject = function(array, location) {
    // Loop through array of tokens
    for(var currentIndex = 0; currentIndex < array.length; currentIndex++) {
      if(_this.indexObject[_this.key].hasOwnProperty(array[currentIndex])) {
      // Push this location into the corresponding array for this token.
      _this.indexObject[_this.key][array[currentIndex]].push(location);
      } else {
        // Creates an array containing this location to correspond to the token
        _this.indexObject[_this.key][array[currentIndex]] = [location];
      }
    }
  };

  /**
  * @method getIndex
  *
  * Returns the index object for a file or term.
  *
  * @param Optional {String} filepath
  * @param Optional {String} term
  * @return {Object / Array} indexObj
  */
  this.getIndex = function(filepath, term) {
    var key;

    if(!term && !filepath) {
      return _this.indexObject;
    }
    // Get file name from a given filepath.
    key = _this.helperMethods.getFilename(filepath);
    if(!term) {
      return _this.indexObject[key];
    }

    return _this.indexObject[key][term] || [];
  };
}

module.exports = Index;
<commit_msg>add locateterm function<commit_after>var Helper = require('./inverted-helper.js');
var fs = require('fs');
var _ = require('lodash');

/**
* @class Index
*
* Creates an index object given the filepath,
* which consists of all the unique words in the file and a corresponding
* list of the documents in which they can be found within the file.
*/
function Index() {
  'use strict';
  var _this = this;

  this.key = '';
  this.indexObject = {};
  this.helperMethods = new Helper();

  /**
  * @method createIndex
  *
  * Creates the index object for a given filepath.
  *
  * @param {String} filepath
  * @return {Void}
  */
  this.createIndex = function(filepath) {
    if(filepath) {
      // Gets filename from filepath to serve as key in indexObject.
      _this.key = _this.helperMethods.getFilename(filepath);
      _this.indexObject[_this.key] = {};
      // Calls readJsonFile to asynchronously read JSON file.
      _this.readJsonFile(filepath, function(res) {
        _this.transformObjects(res);
      });
    } else {
      throw new Error('Cannot createIndex of ' + filepath);
    }
  };

  /**
  * @method readJsonFile
  *
  * Asynchronously reads JSON file.
  *
  * @param {String} filepath
  * @param {Function} callback
  * @return {void}
  */
  this.readJsonFile = function(filepath, callback) {
    var object;

    fs.readFile(filepath, 'utf8', function(err, data) {
      try{
        object = JSON.parse(data);
      }
      catch(error){
        throw new Error("Expected an array of JSON objects");
      }
      if(!_this.helperMethods.isValidArray(object)) {
        throw new Error('JSON file cannot be empty');
      }
      callback(object);
    });
  };

  /**
  * @method transformObjects
  *
  * Loops through an array of objects to populate the index object.
  *
  * @param {Array} arrayOfObj
  * @return {Void}
  */
  this.transformObjects = function(arrayOfObj) {
    var title,
        text;
    var uniqueArray = [];
    var arraylength = arrayOfObj.length;

    for(var objectIndex = 0; objectIndex < arraylength; objectIndex++) {
      // Removes punctuations and chages both title and text to lower case.
      // Splits title and text to array.
      title = _this.helperMethods.normalize(arrayOfObj[objectIndex].title).split(' ');
      text = _this.helperMethods.normalize(arrayOfObj[objectIndex].text).split(' ');
      // Creates an array of tokens and removes multiple occurrence of words.
      uniqueArray.push(_this.helperMethods.uniqueFilter(title,text));
    }
    // Populates the index object.
    uniqueArray.forEach(_this.makeIndexObject);
  };

  /**
  * @method makeIndexObject
  *
  * Loops through an array of tokens and populates the index object.
  *
  * @param {Array} array
  * @param {Number} location
  * @return {Void}
  */
  this.makeIndexObject = function(array, location) {
    // Loop through array of tokens
    for(var currentIndex = 0; currentIndex < array.length; currentIndex++) {
      if(_this.indexObject[_this.key].hasOwnProperty(array[currentIndex])) {
      // Push this location into the corresponding array for this token.
      _this.indexObject[_this.key][array[currentIndex]].push(location);
      } else {
        // Creates an array containing this location to correspond to the token
        _this.indexObject[_this.key][array[currentIndex]] = [location];
      }
    }
  };

  /**
  * @method getIndex
  *
  * Returns the index object for a file or term.
  *
  * @param Optional {String} filepath
  * @param Optional {String} term
  * @return {Object / Array} indexObj
  */
  this.getIndex = function(filepath, term) {
    var key;

    if(!term && !filepath) {
      return _this.indexObject;
    }
    // Get file name from a given filepath.
    key = _this.helperMethods.getFilename(filepath);
    if(!term) {
      return _this.indexObject[key];
    }

    return _this.indexObject[key][term] || [];
  };

  /**
  * @method locateTerm
  *
  * Finds a term in the index object.
  *
  * @param {String} term
  * @return {Array} indexObj[_this.key][terms]
  */
  this.locateTerm = function(terms) {
    return _this.indexObject[_this.key][terms] || [];
  };

}

module.exports = Index;
<|endoftext|>


<commit_before>/**
 * Index Class
 *
 * Creates an Index Class
 * @class
 */
class Index {
  /**
   *
   *
   * @constructor
   */
  constructor() {
    this.index = {};
    this.files = {};
  }

  /**
   * checkJSON
   *
   * Checks whether the file is a JSON file
   *
   * @param {object} responseFile - Content of the file
   * @returns {bool} | {object} A false boolean if file is not valid
   *
   * or a JSON object if valid file
   */
  static checkJSON(responseFile) {
    if (responseFile.length > 0
      && responseFile[0].title && responseFile[0].text) {
      return responseFile;
    }
    return false;
  }

  /**
   * tidyText
   *
   * Removes punctuation and whitespace
   *
   * @param {string} words - Words in file
   * @returns {array} An array of words with no whitespace or punctuation
   */
  static tidyText(words) {
    return words.replace(/\W+/g, ' ')
      .trim().split(' ').filter(text =>
        text
      );
  }

  /**
   * createIndex
   *
   * Creates the index from the file passed
   *
   * @param {string} fileName - Name of the file
   * @param {object} file - Content of the file
   * @returns {object} An object of created indices
   */
  createIndex(fileName, file) {
    file = Index.checkJSON(file);
    if (!file) {
      return false;
    }

    const fileWords = [];
    const wordsIndex = {};
    file.forEach((document) => {
      const fileString = (document.text).toLowerCase();
      fileWords.push(Index.tidyText(fileString));
    });

    Array.prototype.forEach.call(fileWords, (indexNo) => {
      const position = parseInt(indexNo, 10);
      fileWords[indexNo].forEach((word) => {
        if (wordsIndex[word]) {
          if (wordsIndex[word].indexOf(position) === -1) {
            wordsIndex[word].push(position);
          }
        } else {
          wordsIndex[word] = [position];
        }
      });
    });
    if (!this.index[fileName]) {
      this.index[fileName] = wordsIndex;
    }
    this.files[fileName] = file.length;
    return this.index;
  }

  /**
   * getIndex
   *
   * Returns the index
   *
   * @param {string} fileName - Name to the file
   * @returns {object} An object of indices
   */
  getIndex(fileName) {
    return this.index[fileName];
  }

  /**
   * searchIndex
   * Search for terms in a file
   * @param {String} terms word(s) to be searched in the index
   * @param {String} filename file to search for words
   * @return {Object} words and their index
   */
  static searchWords(...terms) {
    let toSearch = '';

    for (let word = 0; word < terms.length; word += 1) {
      if (Array.isArray(...[word])) {
        terms[word].join(' ');
      }
      toSearch += `${terms[word]} `;
    }
    return toSearch.split(',').join(' ');
  }

  /**
   * searchIndex
   *
   * Checks whether searched terms are in the created index
   *
   * @param {string} fileName
   * @param {string} terms
   * @returns {object} An object of the results
   */
  searchIndex(fileName, ...terms) {
    const results = {};
    let searchTerms = [];
    const pass = new Date();
    const now = pass.getTime() / 1000;
    const toSearch = Index.searchWords(...terms);

    searchTerms = Index.tidyText(toSearch.toLowerCase());
    if (!fileName) {
      Array.prototype.forEach.call(this.index, (file) => {
        results[file] = Index.searchResult(searchTerms, this.index[file]);
      });
    } else {
      const searchFile = this.index[fileName];
      results[fileName] = Index.searchResult(searchTerms, searchFile);
    }

    if (((pass.getTime() / 1000) - now) > 0) {
      throw new Error('Search took too long.');
    }
    return results;
  }

  /**
   * searchResult
   *
   * Returns the search results
   *
   * @param {array} searchTerms
   * @param {object} file
   * @returns {object} results
   */
  static searchResult(searchTerms, file) {
    const results = {};

    searchTerms.forEach((term, index) => {
      if (file[term]) {
        results[searchTerms[index]] = file[term];
      }
    });
    return results;
  }
}
<commit_msg>Adding test into file<commit_after>/**
 * Index Class
 *
 * Creates an Index Class
 * @class
 */
class Index {
  /**
   *
   *
   * @constructor
   */
  constructor() {
    this.index = {};
    this.files = {};
  }

  /**
   * checkJSON
   *
   * Checks whether the file is a JSON file
   *
   * @param {object} responseFile - Content of the file
   * @returns {bool} | {object} A false boolean if file is not valid
   *
   * or a JSON object if valid file
   */
  static checkJSON(responseFile) {
    if (responseFile.length > 0
      && responseFile[0].title && responseFile[0].text) {
      return responseFile;
    }
    return false;
  }

  /**
   * tidyText
   *
   * Removes punctuation and whitespace
   *
   * @param {string} words - Words in file
   * @returns {array} An array of words with no whitespace or punctuation
   */
  static tidyText(words) {
    return words.replace(/\W+/g, ' ')
      .trim().split(' ').filter(text =>
        text
      );
  }

  /**
   * createIndex
   *
   * Creates the index from the file passed
   *
   * @param {string} fileName - Name of the file
   * @param {object} file - Content of the file
   * @returns {object} An object of created indices
   */
  createIndex(fileName, file) {
    file = Index.checkJSON(file);
    if (!file) {
      return false;
    }

    const fileWords = [];
    const wordsIndex = {};
    file.forEach((document) => {
      const fileString = (document.text).toLowerCase();
      fileWords.push(Index.tidyText(fileString));
    });

    for (const indexNo in fileWords) {
      const position = parseInt(indexNo, 10);
      fileWords[indexNo].forEach((word) => {
        if (wordsIndex[word]) {
          if (wordsIndex[word].indexOf(position) === -1) {
            wordsIndex[word].push(position);
          }
        } else {
          wordsIndex[word] = [position];
        }
      });
    }
    if (!this.index[fileName]) {
      this.index[fileName] = wordsIndex;
    }
    this.files[fileName] = file.length;
    return this.index;
  }

  /**
   * getIndex
   *
   * Returns the index
   *
   * @param {string} fileName - Name to the file
   * @returns {object} An object of indices
   */
  getIndex(fileName) {
    return this.index[fileName];
  }

  /**
   * searchIndex
   * Search for terms in a file
   * @param {String} terms word(s) to be searched in the index
   * @param {String} filename file to search for words
   * @return {Object} words and their index
   */
  static searchWords(...terms) {
    let toSearch = '';

    for (let word = 0; word < terms.length; word += 1) {
      if (Array.isArray(...[word])) {
        terms[word].join(' ');
      }
      toSearch += `${terms[word]} `;
    }
    return toSearch.split(',').join(' ');
  }

  /**
   * searchIndex
   *
   * Checks whether searched terms are in the created index
   *
   * @param {string} fileName
   * @param {string} terms
   * @returns {object} An object of the results
   */
  searchIndex(fileName, ...terms) {
    const results = {};
    let searchTerms = [];
    const pass = new Date();
    const now = pass.getTime() / 1000;
    const toSearch = Index.searchWords(...terms);

    searchTerms = Index.tidyText(toSearch.toLowerCase());
    if (!fileName) {
      Array.prototype.forEach.call(this.index, (file) => {
        results[file] = Index.searchResult(searchTerms, this.index[file]);
      });
    } else {
      const searchFile = this.index[fileName];
      results[fileName] = Index.searchResult(searchTerms, searchFile);
    }

    if (((pass.getTime() / 1000) - now) > 0) {
      throw new Error('Search took too long.');
    }
    return results;
  }

  /**
   * searchResult
   *
   * Returns the search results
   *
   * @param {array} searchTerms
   * @param {object} file
   * @returns {object} results
   */
  static searchResult(searchTerms, file) {
    const results = {};

    searchTerms.forEach((term, index) => {
      if (file[term]) {
        results[searchTerms[index]] = file[term];
      }
    });
    return results;
  }
}
<|endoftext|>


<commit_before>/**
 * @module IO
 * @submodule Table
 * @requires core
 */

'use strict';

var p5 = require('../core/core');

/**
 *  A TableRow object represents a single row of data values,
 *  stored in columns, from a table.
 *
 *  A Table Row contains both an ordered array, and an unordered
 *  JSON object.
 *
 *  @class p5.TableRow
 *  @constructor
 *  @param {String} [str]       optional: populate the row with a
 *                              string of values, separated by the
 *                              separator
 *  @param {String} [separator] comma separated values (csv) by default
 */
p5.TableRow = function (str, separator) {
  var arr = [];
  var obj = {};
  if (str){
    separator = separator || ',';
    arr = str.split(separator);
  }
  for (var i = 0; i < arr.length; i++){
    var key = i;
    var val = arr[i];
    obj[key] = val;
  }
  this.arr = arr;
  this.obj = obj;
  this.table = null;
};

/**
 *  Stores a value in the TableRow's specified column.
 *  The column may be specified by either its ID or title.
 *
 *  @method  set
 *  @param {String|Number} column Column ID (Number)
 *                                or Title (String)
 *  @param {String|Number} value  The value to be stored
 */
p5.TableRow.prototype.set = function(column, value) {
  // if typeof column is string, use .obj
  if (typeof(column) === 'string'){
    var cPos = this.table.columns.indexOf(column); // index of columnID
    if (cPos >= 0) {
      this.obj[column] = value;
      this.arr[cPos] = value;
    }
    else {
      throw 'This table has no column named "' + column +'"';
    }
  }

  // if typeof column is number, use .arr
  else {
    if (column < this.table.columns.length) {
      this.arr[column] = value;
      var cTitle = this.table.columns[column];
      this.obj[cTitle] = value;
    }
    else {
      throw 'Column #' + column + ' is out of the range of this table';
    }
  }
};

/**
 *  Stores a Float value in the TableRow's specified column.
 *  The column may be specified by either its ID or title.
 *
 *  @method  setNum
 *  @param {String|Number} column Column ID (Number)
 *                                or Title (String)
 *  @param {Number} value  The value to be stored
 *                                as a Float
 */
p5.TableRow.prototype.setNum = function(column, value){
  var floatVal = parseFloat(value, 10);
  this.set(column, floatVal);
};

/**
 *  Stores a String value in the TableRow's specified column.
 *  The column may be specified by either its ID or title.
 *
 *  @method  setString
 *  @param {String|Number} column Column ID (Number)
 *                                or Title (String)
 *  @param {String} value  The value to be stored
 *                                as a String
 */
p5.TableRow.prototype.setString = function(column, value){
  var stringVal = value.toString();
  this.set(column, stringVal);
};

/**
 *  Retrieves a value from the TableRow's specified column.
 *  The column may be specified by either its ID or title.
 *
 *  @method  get
 *  @param  {String|Number} column columnName (string) or
 *                                   ID (number)
 *  @return {String|Number}
 */
p5.TableRow.prototype.get = function(column) {
  if (typeof(column) === 'string'){
    return this.obj[column];
  } else {
    return this.arr[column];
  }
};

/**
 *  Retrieves a Float value from the TableRow's specified
 *  column. The column may be specified by either its ID or
 *  title.
 *
 *  @method  getNum
 *  @param  {String|Number} column columnName (string) or
 *                                   ID (number)
 *  @return {Number}  Float Floating point number
 */
p5.TableRow.prototype.getNum = function(column) {
  var ret;
  if (typeof(column) === 'string'){
    ret = parseFloat(this.obj[column], 10);
  } else {
    ret = parseFloat(this.arr[column], 10);
  }

  if (ret.toString() === 'NaN') {
  }
  return ret;
};

/**
 *  Retrieves an String value from the TableRow's specified
 *  column. The column may be specified by either its ID or
 *  title.
 *
 *  @method  getString
 *  @param  {String|Number} column columnName (string) or
 *                                   ID (number)
 *  @return {String}  String
 */
p5.TableRow.prototype.getString = function(column) {
  if (typeof(column) === 'string'){
    return this.obj[column].toString();
  } else {
    return this.arr[column].toString();
  }
};

module.exports = p5.TableRow;
<commit_msg>#1888 empty cell getNum error handling<commit_after>/**
 * @module IO
 * @submodule Table
 * @requires core
 */

'use strict';

var p5 = require('../core/core');

/**
 *  A TableRow object represents a single row of data values,
 *  stored in columns, from a table.
 *
 *  A Table Row contains both an ordered array, and an unordered
 *  JSON object.
 *
 *  @class p5.TableRow
 *  @constructor
 *  @param {String} [str]       optional: populate the row with a
 *                              string of values, separated by the
 *                              separator
 *  @param {String} [separator] comma separated values (csv) by default
 */
p5.TableRow = function (str, separator) {
  var arr = [];
  var obj = {};
  if (str){
    separator = separator || ',';
    arr = str.split(separator);
  }
  for (var i = 0; i < arr.length; i++){
    var key = i;
    var val = arr[i];
    obj[key] = val;
  }
  this.arr = arr;
  this.obj = obj;
  this.table = null;
};

/**
 *  Stores a value in the TableRow's specified column.
 *  The column may be specified by either its ID or title.
 *
 *  @method  set
 *  @param {String|Number} column Column ID (Number)
 *                                or Title (String)
 *  @param {String|Number} value  The value to be stored
 */
p5.TableRow.prototype.set = function(column, value) {
  // if typeof column is string, use .obj
  if (typeof(column) === 'string'){
    var cPos = this.table.columns.indexOf(column); // index of columnID
    if (cPos >= 0) {
      this.obj[column] = value;
      this.arr[cPos] = value;
    }
    else {
      throw 'This table has no column named "' + column +'"';
    }
  }

  // if typeof column is number, use .arr
  else {
    if (column < this.table.columns.length) {
      this.arr[column] = value;
      var cTitle = this.table.columns[column];
      this.obj[cTitle] = value;
    }
    else {
      throw 'Column #' + column + ' is out of the range of this table';
    }
  }
};

/**
 *  Stores a Float value in the TableRow's specified column.
 *  The column may be specified by either its ID or title.
 *
 *  @method  setNum
 *  @param {String|Number} column Column ID (Number)
 *                                or Title (String)
 *  @param {Number} value  The value to be stored
 *                                as a Float
 */
p5.TableRow.prototype.setNum = function(column, value){
  var floatVal = parseFloat(value, 10);
  this.set(column, floatVal);
};

/**
 *  Stores a String value in the TableRow's specified column.
 *  The column may be specified by either its ID or title.
 *
 *  @method  setString
 *  @param {String|Number} column Column ID (Number)
 *                                or Title (String)
 *  @param {String} value  The value to be stored
 *                                as a String
 */
p5.TableRow.prototype.setString = function(column, value){
  var stringVal = value.toString();
  this.set(column, stringVal);
};

/**
 *  Retrieves a value from the TableRow's specified column.
 *  The column may be specified by either its ID or title.
 *
 *  @method  get
 *  @param  {String|Number} column columnName (string) or
 *                                   ID (number)
 *  @return {String|Number}
 */
p5.TableRow.prototype.get = function(column) {
  if (typeof(column) === 'string'){
    return this.obj[column];
  } else {
    return this.arr[column];
  }
};

/**
 *  Retrieves a Float value from the TableRow's specified
 *  column. The column may be specified by either its ID or
 *  title.
 *
 *  @method  getNum
 *  @param  {String|Number} column columnName (string) or
 *                                   ID (number)
 *  @return {Number}  Float Floating point number
 */
p5.TableRow.prototype.getNum = function(column) {
  var ret;
  if (typeof(column) === 'string'){
    ret = parseFloat(this.obj[column], 10);
  } else {
    ret = parseFloat(this.arr[column], 10);
  }

  if (ret.toString() === 'NaN') {
    console.error('Error: ' + this.obj[column]+ ' is NaN (Not a Number)');
  }
  return ret;
};

/**
 *  Retrieves an String value from the TableRow's specified
 *  column. The column may be specified by either its ID or
 *  title.
 *
 *  @method  getString
 *  @param  {String|Number} column columnName (string) or
 *                                   ID (number)
 *  @return {String}  String
 */
p5.TableRow.prototype.getString = function(column) {
  if (typeof(column) === 'string'){
    return this.obj[column].toString();
  } else {
    return this.arr[column].toString();
  }
};

module.exports = p5.TableRow;
<|endoftext|>


<commit_before>(function (env) {
    "use strict";

    env.ddg_spice_people_in_space = function(api_result) {

        if (!api_result || api_result.number === undefined) {
          return Spice.failed('people_in_space');
        }

        if (api_result.number > 0) {
            DDG.require("moment.js", function() {
                var codes = {"canada":"ca","china":"cn", "denmark":"dk", "france":"fr", "germany":"de", "italy":"it", "japan":"jp", "netherlands":"nl", "russia":"ru", "spain":"sp", "sweden":"se", "uk":"uk", "usa":"us"};

                Spice.add({
                    id: "people_in_space",
                    name: "Answer",
                    data: api_result.people,
                    meta: {
                        itemType: (api_result.number === 1 ? "Person in Space" : "People in Space"),
                        sourceName: "People in Space",
                        sourceUrl: "http://www.howmanypeopleareinspacerightnow.com/"
                    },
                    normalize: function(item) {
                        return {
                            daysElapsed: moment().diff(item.launchdate, 'days'),
                            icon: DDG.settings.region.getLargeIconURL(codes[item.country.toLowerCase()]),
                            twitter: item.twitter.replace(/https?:\/\/twitter.com\//,'')
                        };
                    },
                    templates: {
                        item: 'base_item',
                        item_detail: Spice.people_in_space.detail,
                        options: {
                            content: Spice.people_in_space.content,
                        }
                    }
                });
         });
        } else {
            Spice.add({
                id: "people_in_space",
                name: "Answer",
                data: api_result,
                meta: {
                    sourceName: "People in Space",
                    sourceUrl: "http://www.howmanypeopleareinspacerightnow.com/"
                },
                normalize: function(item) {
                    return {
                        title: "There are no people in space right now.",
                    };
                },
                templates: {
                    group: "text",
                    options:{
                        moreAt: true
                    }
                }
            });
        }
    };
}(this));<commit_msg>PeopleInSpace: Format launchdate using moment.js<commit_after>(function (env) {
    "use strict";

    env.ddg_spice_people_in_space = function(api_result) {

        if (!api_result || api_result.number === undefined) {
          return Spice.failed('people_in_space');
        }

        if (api_result.number > 0) {
            DDG.require("moment.js", function() {
                var codes = {"canada":"ca","china":"cn", "denmark":"dk", "france":"fr", "germany":"de", "italy":"it", "japan":"jp", "netherlands":"nl", "russia":"ru", "spain":"sp", "sweden":"se", "uk":"uk", "usa":"us"};

                Spice.add({
                    id: "people_in_space",
                    name: "Answer",
                    data: api_result.people,
                    meta: {
                        itemType: (api_result.number === 1 ? "Person in Space" : "People in Space"),
                        sourceName: "People in Space",
                        sourceUrl: "http://www.howmanypeopleareinspacerightnow.com/"
                    },
                    normalize: function(item) {
                        return {
                            daysElapsed: moment().diff(item.launchdate, 'days'),
                            launchdate: moment(item.launchdate).format("MMM DD, YYYY"),
                            icon: DDG.settings.region.getLargeIconURL(codes[item.country.toLowerCase()]),
                            twitter: item.twitter.replace(/https?:\/\/twitter.com\//,'')
                        };
                    },
                    templates: {
                        item: 'base_item',
                        item_detail: Spice.people_in_space.detail,
                        options: {
                            content: Spice.people_in_space.content,
                        }
                    }
                });
         });
        } else {
            Spice.add({
                id: "people_in_space",
                name: "Answer",
                data: api_result,
                meta: {
                    sourceName: "People in Space",
                    sourceUrl: "http://www.howmanypeopleareinspacerightnow.com/"
                },
                normalize: function(item) {
                    return {
                        title: "There are no people in space right now.",
                    };
                },
                templates: {
                    group: "text",
                    options:{
                        moreAt: true
                    }
                }
            });
        }
    };
}(this));<|endoftext|>


<commit_before>(function (env) {
    "use strict";
    env.ddg_spice_twitch_featured = function(api_result){

        if (!api_result || !api_result.featured || api_result.featured.length === 0 ||api_result.error) {
            return Spice.failed('twitch_featured');
        }

        Spice.add({
            minItemsForModeSwitch: 3,
            id: "twitch_featured",
            name: "Twitch Featured Streams",
            data: api_result.featured,
            meta: {
                sourceName: "twitch.tv",
                sourceUrl: "http://www.twitch.tv/",
                itemType: "Twitch Featured"
            },
            templates: {
                group: 'base',
                detail: false,
                item_detail: false,
                item: 'videos_item',
                moreAt: true,
            },
            normalize: function(featuredStream) {
                var title = "Untitled Broadcast";
                if(featuredStream.stream.channel.status != null && featuredStream.stream.channel.display_name != null) {
                    title = featuredStream.stream.channel.display_name + ": " + featuredStream.stream.channel.status;
                }

                return {
                    url: featuredStream.stream.channel.url,
                    viewCount: featuredStream.stream.viewers,
                    images: featuredStream.stream.preview,
                    title: title,
                    duration: featuredStream.stream.game
                };
            }
        });
    };
}(this));
<commit_msg>Update twitch_featured.js<commit_after>(function (env) {
    "use strict";
    env.ddg_spice_twitch_featured = function(api_result){

        if (!api_result || !api_result.featured || api_result.featured.length === 0 ||api_result.error) {
            return Spice.failed('twitch_featured');
        }

        Spice.add({
            minItemsForModeSwitch: 3,
            id: "twitch_featured",
            name: "Twitch Featured Streams",
            data: api_result.featured,
            meta: {
                sourceName: "twitch.tv",
                sourceUrl: "http://www.twitch.tv/",
                itemType: "Featured Streams"
            },
            templates: {
                group: 'base',
                detail: false,
                item_detail: false,
                item: 'videos_item',
                moreAt: true,
            },
            normalize: function(item) {
                var title = "Untitled Broadcast";
                if(item.stream.channel.status != null && item.stream.channel.display_name != null) {
                    title = item.stream.channel.display_name + ": " + item.stream.channel.status;
                }

                return {
                    url: item.stream.channel.url,
                    viewCount: item.stream.viewers,
                    images: item.stream.preview,
                    title: title,
                    duration: item.stream.game
                };
            }
        });
    };
}(this));
<|endoftext|>


<commit_before>'use strict';
/* global d3:false */

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');

    if(this.graphInfo._context.displayModeBar === 'hover') {
        this.element.className = 'modebar modebar--hover';
    } else {
        this.element.className = 'modebar';
    }

    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config()[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    if (this.graphInfo._context.displaylogo) {
        this.element.appendChild(this.getLogo());
    }

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'modebar-group';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var _this = this,
        button = document.createElement('a');

    button.setAttribute('rel', 'tooltip');
    button.className = 'modebar-btn';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('data-title', config.title);
    button.setAttribute('data-gravity', config.gravity || 'n');
    button.addEventListener('click', function () {
            config.click.apply(_this, arguments);
        });

    button.appendChild(this.createIcon(this.Plotly.Icons[config.icon]));

    return button;
};

/**
 * Add an icon to a button
 * @Param {object} thisIcon
 * @Param {number} thisIcon.width
 * @Param {string} thisIcon.path
 * @Return {HTMLelement}
 */
ModeBar.prototype.createIcon = function (thisIcon) {
    var iconDef = this.Plotly.Icons,
        iconHeight = iconDef.ascent - iconDef.descent,
        svgNS = 'http://www.w3.org/2000/svg',
        icon = document.createElementNS(svgNS, 'svg'),
        path = document.createElementNS(svgNS, 'path');

    icon.setAttribute('height', '1em');
    icon.setAttribute('width', (thisIcon.width / iconHeight)+'em');
    icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' '));

    path.setAttribute('d', thisIcon.path);
    path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + iconDef.ascent + ')');
    icon.appendChild(path);

    return icon;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        d3.select(button).classed('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * @return {HTMLDivElement} The logo image wrapped in a group
 */
ModeBar.prototype.getLogo = function(){
    var group = this.createGroup(),
        a = document.createElement('a');

    a.href = 'https://plot.ly/';
    a.target = '_blank';
    a.setAttribute('data-title', 'Produced with Plotly');
    a.className = 'modebar-btn plotlyjsicon modebar-btn--logo';

    a.appendChild(this.createIcon(this.Plotly.Icons.plotlylogo));

    group.appendChild(a);
    return group;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCartesian = function(ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        graphInfo = this.graphInfo,
        fullLayout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        aobj = {};

    if(astr === 'zoom') {
        var xr = fullLayout.xaxis.range,
            yr = fullLayout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y'
    if (astr==='hovermode' && (val==='x' || val==='y')) {
        val = fullLayout._isHoriz ? 'y' : 'x';
        button.setAttribute('data-val', val);
    }

    aobj[astr] = val;

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                fullLayout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
            Plotly.Fx.supplyLayoutDefaults(graphInfo.layout, fullLayout,
                graphInfo._fullData);
        }
    });
};

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 */
ModeBar.prototype.handleHover3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneLayouts = Plotly.Lib.getSceneLayouts(fullLayout),
        layoutUpdate = {};

    // 3D has only 1 hover mode; toggle it
    var val = fullLayout[attr]!=='closest' ? 'closest' : false;
    layoutUpdate[attr] = val;

    // Apply to all scenes
    for (var i = 0;  i < sceneLayouts.length; ++i) {
        var sceneLayout = sceneLayouts[i],
            scene = sceneLayout._scene;

        scene.spikeEnable = !scene.spikeEnable;
        scene.container.focus();
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });
};

/**
 * Reconfigure keyboard bindings for webgl3D camera control on drag
 * @Param {object} ev event object
 */
ModeBar.prototype.handleDrag3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneLayouts = Plotly.Lib.getSceneLayouts(fullLayout),
        layoutUpdate = {};

    // set dragmode to given value
    layoutUpdate[attr] = val;

    // Update the webgl3D key binding of all scenes
    for (var i = 0;  i < sceneLayouts.length; ++i) {
        var sceneLayout = sceneLayouts[i],
            scene = sceneLayout._scene;

        if ('camera' in scene) {
            scene.camera.keyBindingMode = val;
            scene.container.focus();
        }
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });
};

/**
 * Reset the position of the webgl3D camera
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCamera3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        Plotly = this.Plotly,
        fullLayout = this.graphInfo._fullLayout,
        sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d');

    var i, sceneLayout, scene, cameraposition;

    for (i = 0;  i < sceneIds.length; i++) {
        sceneLayout = fullLayout[sceneIds[i]];
        scene = sceneLayout._scene;

        if (attr === 'resetDefault') scene.setCameraToDefault();
        else if (attr === 'resetLastSave') {
            cameraposition = sceneLayout.cameraposition;
            if (cameraposition) scene.setCameraPosition(cameraposition);
            else scene.setCameraToDefault();
        }
    }

    /* TODO have a sceneLastTouched in _fullLayout to only
     * update the camera of the scene last touched by the user
     */
};

ModeBar.prototype.cleanup = function(){
    this.element.innerHTML = '';
    var modebarParent = this.element.parentNode;
    if (modebarParent) modebarParent.removeChild(this.element);
};

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = function config() {
    return {
        zoom2d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'zoombox',
            click: this.handleCartesian
        },
        pan2d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'pan',
            click: this.handleCartesian
        },
        zoomIn2d: {
            title: 'Zoom in',
            attr: 'zoom',
            val: 'in',
            icon: 'zoom_plus',
            click: this.handleCartesian
        },
        zoomOut2d: {
            title: 'Zoom out',
            attr: 'zoom',
            val: 'out',
            icon: 'zoom_minus',
            click: this.handleCartesian
        },
        autoScale2d: {
            title: 'Autoscale',
            attr: 'allaxes.autorange',
            val: '',
            icon: 'autoscale',
            click: this.handleCartesian
        },
        hoverClosest2d: {
            title: 'Show closest data on hover',
            attr: 'hovermode',
            val: 'closest',
            icon: 'tooltip_basic',
            gravity: 'ne',
            click: this.handleCartesian
        },
        hoverCompare2d: {
            title: 'Compare data on hover',
            attr: 'hovermode',
            val: this.graphInfo._fullLayout._isHoriz ? 'y' : 'x',
            icon: 'tooltip_compare',
            gravity: 'ne',
            click: this.handleCartesian
        },
        zoom3d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'zoombox',
            click: this.handleDrag3d
        },
        pan3d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'pan',
            click: this.handleDrag3d
        },
        rotate3d: {
            title: 'Rotate',
            attr: 'dragmode',
            val: 'rotate',
            icon: 'undo',
            click: this.handleDrag3d
        },
        resetCameraDefault3d: {
            title: 'Reset camera to default',
            attr: 'resetDefault',
            val: false,
            icon: 'home',
            click: this.handleCamera3d
        },
        resetCameraLastSave3d: {
            title: 'Reset camera to last save',
            attr: 'resetLastSave',
            val: false,
            icon: 'camera-retro',
            click: this.handleCamera3d
        },
        hoverClosest3d: {
            title: 'Toggle show closest data on hover',
            attr: 'hovermode',
            val: 'closest',
            icon: 'tooltip_basic',
            gravity: 'ne',
            click: this.handleHover3d
        }
    };
};

module.exports = ModeBar;
<commit_msg>refactor 3d toggle-hover button:<commit_after>'use strict';
/* global d3:false */

/**
 * UI controller for interactive plots
 * @Class
 * @Param {object} config
 * @Param {object} config.buttons    nested arrays of grouped buttons to initialize
 * @Param {object} config.container  container div to append modebar
 * @Param {object} config.Plotly     main plotly namespace module
 * @Param {object} config.graphInfo  primary plot object containing data and layout
 */
function ModeBar (config) {

    if (!(this instanceof ModeBar)) return new ModeBar();

    var _this = this;

    this.Plotly = config.Plotly;
    this.graphInfo = config.graphInfo;
    this.element = document.createElement('div');

    if(this.graphInfo._context.displayModeBar === 'hover') {
        this.element.className = 'modebar modebar--hover';
    } else {
        this.element.className = 'modebar';
    }

    this.buttons = config.buttons;
    this.buttonElements = [];

    this.buttons.forEach( function (buttonGroup) {
        var group = _this.createGroup();

        buttonGroup.forEach( function (buttonName) {
            var buttonConfig = _this.config()[buttonName];

            if (!buttonConfig) {
                throw new Error(buttonName + ' not specfied in modebar configuration');
            }

            var button = _this.createButton(buttonConfig);

            _this.buttonElements.push(button);
            group.appendChild(button);
        });

        _this.element.appendChild(group);
    });

    if (this.graphInfo._context.displaylogo) {
        this.element.appendChild(this.getLogo());
    }

    config.container.appendChild(this.element);

    this.updateActiveButton();

}

/**
 * Empty div for containing a group of buttons
 * @Return {HTMLelement}
 */
ModeBar.prototype.createGroup = function () {
    var group = document.createElement('div');
    group.className = 'modebar-group';

    return group;
};

/**
 * Create a new button div and set constant and configurable attributes
 * @Param {object} config
 * @Param {string} config.attr
 * @Param {string} config.val
 * @Param {string} config.title
 * @Param {function} config.click
 * @Return {HTMLelement}
 */
ModeBar.prototype.createButton = function (config) {
    var _this = this,
        button = document.createElement('a');

    button.setAttribute('rel', 'tooltip');
    button.className = 'modebar-btn';

    button.setAttribute('data-attr', config.attr);
    button.setAttribute('data-val', config.val);
    button.setAttribute('data-title', config.title);
    button.setAttribute('data-gravity', config.gravity || 'n');
    button.addEventListener('click', function () {
            config.click.apply(_this, arguments);
        });

    button.appendChild(this.createIcon(this.Plotly.Icons[config.icon]));

    return button;
};

/**
 * Add an icon to a button
 * @Param {object} thisIcon
 * @Param {number} thisIcon.width
 * @Param {string} thisIcon.path
 * @Return {HTMLelement}
 */
ModeBar.prototype.createIcon = function (thisIcon) {
    var iconDef = this.Plotly.Icons,
        iconHeight = iconDef.ascent - iconDef.descent,
        svgNS = 'http://www.w3.org/2000/svg',
        icon = document.createElementNS(svgNS, 'svg'),
        path = document.createElementNS(svgNS, 'path');

    icon.setAttribute('height', '1em');
    icon.setAttribute('width', (thisIcon.width / iconHeight)+'em');
    icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' '));

    path.setAttribute('d', thisIcon.path);
    path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + iconDef.ascent + ')');
    icon.appendChild(path);

    return icon;
};

/**
 * Updates active button with attribute specified in layout
 * @Param {object} graphInfo plot object containing data and layout
 * @Return {HTMLelement}
 */
ModeBar.prototype.updateActiveButton = function () {
    var graphInfo = this.graphInfo;
    this.buttonElements.forEach( function (button) {
        var thisval = button.getAttribute('data-val') || true,
            dataAttr = button.getAttribute('data-attr'),
            curval = graphInfo._fullLayout[dataAttr];

        d3.select(button).classed('active', curval===thisval);
    });
};

/**
 * Check if modebar is configured as button configuration argument
 * @Param {object} buttons 2d array of grouped button names
 * @Return {boolean}
 */
ModeBar.prototype.hasButtons = function (buttons) {
    var currentButtons = this.buttons;

    if (buttons.length !== currentButtons.length) return false;

    for (var i = 0; i < buttons.length; ++i) {
        if (buttons[i].length !== currentButtons[i].length) return false;
        for (var j = 0; j < buttons[i].length; j++) {
            if (buttons[i][j] !== currentButtons[i][j]) return false;
        }
    }

    return true;
};

/**
 * @return {HTMLDivElement} The logo image wrapped in a group
 */
ModeBar.prototype.getLogo = function(){
    var group = this.createGroup(),
        a = document.createElement('a');

    a.href = 'https://plot.ly/';
    a.target = '_blank';
    a.setAttribute('data-title', 'Produced with Plotly');
    a.className = 'modebar-btn plotlyjsicon modebar-btn--logo';

    a.appendChild(this.createIcon(this.Plotly.Icons.plotlylogo));

    group.appendChild(a);
    return group;
};

/**
 * Apply D3 cartesian mode attributes to layout to update hover functionality
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCartesian = function(ev) {
    var button = ev.currentTarget,
        astr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        graphInfo = this.graphInfo,
        fullLayout = this.graphInfo._fullLayout,
        Plotly = this.Plotly,
        aobj = {};

    if(astr === 'zoom') {
        var xr = fullLayout.xaxis.range,
            yr = fullLayout.yaxis.range,
            mag = (val==='in') ? 0.5 : 2,
            r0 = (1+mag)/2, r1 = (1-mag)/2;
        aobj = {
            'xaxis.range[0]': r0*xr[0] + r1*xr[1],
            'xaxis.range[1]': r0*xr[1] + r1*xr[0],
            'yaxis.range[0]': r0*yr[0] + r1*yr[1],
            'yaxis.range[1]': r0*yr[1] + r1*yr[0]
        };
    }

    // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y'
    if (astr==='hovermode' && (val==='x' || val==='y')) {
        val = fullLayout._isHoriz ? 'y' : 'x';
        button.setAttribute('data-val', val);
    }

    aobj[astr] = val;

    Plotly.relayout(graphInfo, aobj).then( function() {
        _this.updateActiveButton();
        if(astr === 'dragmode') {
            Plotly.Fx.setCursor(
                fullLayout._paper.select('.nsewdrag'),
                {pan:'move', zoom:'crosshair'}[val]
            );
            Plotly.Fx.supplyLayoutDefaults(graphInfo.layout, fullLayout,
                graphInfo._fullData);
        }
    });
};

/**
 * Toggle the data hover mode
 * @Param {object} ev event object
 */
ModeBar.prototype.handleHover3d = function(ev) {
    var button = ev.currentTarget,
        val = JSON.parse(button.getAttribute('data-val')) || false,
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d'),
        layoutUpdate = {},

        // initialize 'current spike' object to be stored in the DOM
        currentSpikes = {},
        axes = ['xaxis', 'yaxis', 'zaxis'],
        spikeAttrs = ['showspikes', 'spikesides', 'spikethickness', 'spikecolor'];

    var i, sceneId, sceneLayout, sceneSpikes;
    var j, axis, axisSpikes;
    var k, spikeAttr;

    if (val) {
        layoutUpdate = val;
        button.setAttribute('data-val', JSON.stringify(null));
    }
    else {
        layoutUpdate = {'allaxes.showspikes': false};

        for (i = 0;  i < sceneIds.length; i++) {
            sceneId = sceneIds[i];
            sceneLayout = fullLayout[sceneId];
            sceneSpikes = currentSpikes[sceneId] = {};

            // copy all the current spike attrs
            for (j = 0; j < 3; j++) {
                axis = axes[j];
                axisSpikes = sceneSpikes[axis] = {};
                for (k = 0; k < spikeAttrs.length; k++) {
                    spikeAttr = spikeAttrs[k];
                    axisSpikes[spikeAttr] = sceneLayout[axis][spikeAttr];
                }
            }
        }

        button.setAttribute('data-val', JSON.stringify(currentSpikes));
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });

};

/**
 * Reconfigure keyboard bindings for webgl3D camera control on drag
 * @Param {object} ev event object
 */
ModeBar.prototype.handleDrag3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        val = button.getAttribute('data-val') || true,
        _this = this,
        Plotly = this.Plotly,
        graphInfo = this.graphInfo,
        fullLayout = graphInfo._fullLayout,
        sceneLayouts = Plotly.Lib.getSceneLayouts(fullLayout),
        layoutUpdate = {};

    // set dragmode to given value
    layoutUpdate[attr] = val;

    // Update the webgl3D key binding of all scenes
    for (var i = 0;  i < sceneLayouts.length; ++i) {
        var sceneLayout = sceneLayouts[i],
            scene = sceneLayout._scene;

        if ('camera' in scene) {
            scene.camera.keyBindingMode = val;
            scene.container.focus();
        }
    }

    Plotly.relayout(graphInfo, layoutUpdate).then( function() {
        _this.updateActiveButton();
    });
};

/**
 * Reset the position of the webgl3D camera
 * @Param {object} ev event object
 */
ModeBar.prototype.handleCamera3d = function(ev) {
    var button = ev.currentTarget,
        attr = button.getAttribute('data-attr'),
        Plotly = this.Plotly,
        fullLayout = this.graphInfo._fullLayout,
        sceneIds = Plotly.Plots.getSubplotIds(fullLayout, 'gl3d');

    var i, sceneLayout, scene, cameraposition;

    for (i = 0;  i < sceneIds.length; i++) {
        sceneLayout = fullLayout[sceneIds[i]];
        scene = sceneLayout._scene;

        if (attr === 'resetDefault') scene.setCameraToDefault();
        else if (attr === 'resetLastSave') {
            cameraposition = sceneLayout.cameraposition;
            if (cameraposition) scene.setCameraPosition(cameraposition);
            else scene.setCameraToDefault();
        }
    }

    /* TODO have a sceneLastTouched in _fullLayout to only
     * update the camera of the scene last touched by the user
     */
};

ModeBar.prototype.cleanup = function(){
    this.element.innerHTML = '';
    var modebarParent = this.element.parentNode;
    if (modebarParent) modebarParent.removeChild(this.element);
};

/**
 *
 * @Property config specification hash of button parameters
 */
ModeBar.prototype.config = function config() {
    return {
        zoom2d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'zoombox',
            click: this.handleCartesian
        },
        pan2d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'pan',
            click: this.handleCartesian
        },
        zoomIn2d: {
            title: 'Zoom in',
            attr: 'zoom',
            val: 'in',
            icon: 'zoom_plus',
            click: this.handleCartesian
        },
        zoomOut2d: {
            title: 'Zoom out',
            attr: 'zoom',
            val: 'out',
            icon: 'zoom_minus',
            click: this.handleCartesian
        },
        autoScale2d: {
            title: 'Autoscale',
            attr: 'allaxes.autorange',
            val: '',
            icon: 'autoscale',
            click: this.handleCartesian
        },
        hoverClosest2d: {
            title: 'Show closest data on hover',
            attr: 'hovermode',
            val: 'closest',
            icon: 'tooltip_basic',
            gravity: 'ne',
            click: this.handleCartesian
        },
        hoverCompare2d: {
            title: 'Compare data on hover',
            attr: 'hovermode',
            val: this.graphInfo._fullLayout._isHoriz ? 'y' : 'x',
            icon: 'tooltip_compare',
            gravity: 'ne',
            click: this.handleCartesian
        },
        zoom3d: {
            title: 'Zoom',
            attr: 'dragmode',
            val: 'zoom',
            icon: 'zoombox',
            click: this.handleDrag3d
        },
        pan3d: {
            title: 'Pan',
            attr: 'dragmode',
            val: 'pan',
            icon: 'pan',
            click: this.handleDrag3d
        },
        rotate3d: {
            title: 'Rotate',
            attr: 'dragmode',
            val: 'rotate',
            icon: 'undo',
            click: this.handleDrag3d
        },
        resetCameraDefault3d: {
            title: 'Reset camera to default',
            attr: 'resetDefault',
            val: false,
            icon: 'home',
            click: this.handleCamera3d
        },
        resetCameraLastSave3d: {
            title: 'Reset camera to last save',
            attr: 'resetLastSave',
            val: false,
            icon: 'camera-retro',
            click: this.handleCamera3d
        },
        hoverClosest3d: {
            title: 'Toggle show closest data on hover',
            attr: 'hovermode',
            val: null,
            icon: 'tooltip_basic',
            gravity: 'ne',
            click: this.handleHover3d
        }
    };
};

module.exports = ModeBar;
<|endoftext|>