<commit_before>/*
 * http://video.baidu.com/
 */

'use strict';
function https () {
    this.caseNumber = 0;
    this.keyword = ['src', 'action', 'href', 'background','background-image'];
}

https.prototype.parse = function (content, hostmap, mode) {
    var self = this;
    self.rawContent = content.toString();
    self.error_result = [];
    self.fileContent = '';
    self.hostmap = hostmap;
    self.mode = mode;
    self.inAtag = false; //是否在a标签内部
    var res;
    while (self.rawContent.length) {
        var matchKey = false;
        for (var i = 0; i < self.keyword.length; i++) {
            var reg = new RegExp('\\W(' + self.keyword[i] + ')\\W', 'gm');
            if (reg.exec(self.rawContent) != null) {
                if(!matchKey || (reg.lastIndex - 1) < matchKey[0]) {
                    matchKey = [reg.lastIndex - 1, self.keyword[i]];
                }
            }

        }
        if (matchKey) {
            self.fixKeyUrl(matchKey[0], matchKey[1]);
        } else {
            self.fileContent += self.rawContent;
            self.rawContent = '';
        }
    }
    var headPos = self.fileContent.indexOf('{%head%}');
    if (headPos != -1) {
        var before = self.fileContent.substr(0, headPos + 8);
        var after = self.fileContent.substr(headPos + 8);
        self.fileContent = before
            + ('\n{%$protocolHostMap = json_decode(\''
            + JSON.stringify(self.hostmap).replace(/\\\\/g, '') +'\')%}\n'
            + '\n')
            + after;
    }
    // console.log('总计有:' + self.caseNumber);
    if (self.error_result.length) {
        //fis.log.error
        var log = '未处理url协议\n修改方案：js语境中用 "__protocol" 方法把url包起来;smarty变量尾部添加 "|protocol" 修饰器\n'
            + '所在文件：' + self.fileInfo.realpath + '\n'
            + self.error_result.join('\n')
            + '\n--------------------------------------------------------------------------------------------------------------------------------------\n\n';
        fis.util.write('./protocol-fix.log', log, null, true);

    }
    // console.log(self.fileContent);
    return self.fileContent;
}
https.prototype.setFileInfo = function (file) {
    this.fileInfo = file
}
https.prototype.fixUrl = function (url) {
    var res = url;
    // 无需处理的
    if (url == '' || url.indexOf('?__inline') != -1 || url.match(/^('|")?(\.|\.\.|\/|#|#\w|javascript)/g)) {
        return url;
    }
    if (url.indexOf('|protocol_none') != -1) {
        res = url.replace(/\|protocol_none/gm, '');
        return res;
    }
    if (url.match(/^[^(http|https|\/\/|\.)].+\.(jpg|png|gif|jpeg)$/g)) {
        return url;
    }
    // 第一步，如果能够匹配到就直接替换
    res = this.fixProtocol(url);
    if (res == url) {
        // 处理smarty和js插件
        var reg = new RegExp('(__protocol\\(|\\$(\\S)+\\|protocol)', 'g');
        if (reg.test(res)) {
            res = res.replace(/\|protocol/gm, '|protocol:$protocolHostMap');
            return res;
        } else {
            return false;
        }
    }
    return res;
}

https.prototype.fixProtocol = function (url) {
    var res = url;
    for (var hostReg in this.hostmap) {
        var reg = new RegExp('((http|https)\:)?(\/\/)?' + hostReg);
        res = url.replace(reg, this.hostmap[hostReg]);
        if (res != url) {
            return res;
        }
    }
    return res;
}

https.prototype.isInAtag = function () {
    // 查找a标签
    var self = this;
    var mark = 0;
    var len = 30;
    var pos = self.fileContent.lastIndexOf('<a ');
    if (pos == -1) {
        return false;
    }
    var content = self.fileContent.substr(pos + 3);
    if (content.lastIndexOf('') == -1) {
        return true;
    } else {
        return false;
    }
}

https.prototype.fixKeyUrl = function (pos, key) {
    var self = this;
    self.fileContent += self.rawContent.substr(0, pos);
    self.rawContent = self.rawContent.substr(pos);
    var url;
    var mark = 0;
    var isBgImage = false;
    var quotes = 0;
    if (key == 'href' && self.isInAtag()) {
        return;
    }
    // 查找链接的开头位置
    while (true) {
        if (self.rawContent == '' || self.rawContent[0] == '=' && (self.rawContent[1] == '>' || self.rawContent[1] == '=')) {
            return;
        }

        if (key == 'background' && self.rawContent[mark] != '-' || key == 'background-image') {
            if (self.rawContent.substr(mark, mark + 4) == 'url(') {
                isBgImage = true;
                self.fileContent += self.rawContent.substr(0, mark + 4);
                self.rawContent = self.rawContent.substr(mark + 4);
                break;
            } else {
                if (self.rawContent[0] == ';') {
                    return;
                }
                self.fileContent += self.rawContent[0];
                self.rawContent = self.rawContent.substr(1);
                continue;
            }
        }
        if (self.rawContent[0] == '=' && self.rawContent[1] == '\\') {
            self.fileContent += self.rawContent.substr(0,3);
            self.rawContent = self.rawContent.substr(3);
            break;
        }
        if (self.rawContent[0] == '=' && self.rawContent[1] != '>' || self.rawContent[0] == ':') {
            self.fileContent += self.rawContent[0];
            self.rawContent = self.rawContent.substr(1);
            break;
        }
        else if (self.rawContent[0] == ' '){
            self.fileContent += self.rawContent[0];
            self.rawContent = self.rawContent.substr(1);
        }else {
            return;
        }
    }
    var urlhead = self.rawContent[0];
    var plus = 0;
    // 查找链接的结束位置
    while (true) {
        mark++;
        if (mark > self.rawContent.length -1) {
            url = self.rawContent.substr(0, mark-1);
            self.rawContent = self.rawContent.substr(mark-1);
            break;
        }
        if (!isBgImage) {
            if(self.rawContent[mark] == '('){
                quotes++;
            }else if (self.rawContent[mark] == ')') {
                quotes--;
                if (quotes<0) {
                    quotes = 0;
                }
            }
        }

        if(quotes) continue;

        if (isBgImage && self.rawContent[mark] == ')') {
            url = self.rawContent.substr(0, mark);
            self.rawContent = self.rawContent.substr(mark);
            break;
        }

        if (self.rawContent[mark] == '\'' || self.rawContent[mark] == '"') {
            if(urlhead == self.rawContent[mark] && self.rawContent[mark - 1] != '\\' && self.rawContent[mark - 1] != '[' && self.rawContent[mark + 1] != ']') {
                self.fileContent += self.rawContent[0];
                url = self.rawContent.substr(1, mark-1);
                self.rawContent = self.rawContent.substr(mark);
                break;
            }
        }
        if (!isBgImage && !quotes && (self.rawContent[mark] == ';' || self.rawContent[mark] == '\n' ||  self.rawContent[mark] == ',')) {
            url = self.rawContent.substr(0, mark);
            self.rawContent = self.rawContent.substr(mark);
            break;
        }

    }
    /*如果是背景图片，并且没有协议，说明是相对路径，直接跳过(todo bug: 如果语境是js，那么这个判断会失效)
    if (isBgImage && !url.match(/((http|https)\:)?\/\//g)) {
        return;
    };*/
    var fixedUrl = self.fixUrl(url);
    if (fixedUrl === false) {
        self.error_result.push('case' + (self.error_result.length+1)
        + ': line ' + self.fileContent.split('\n').length + ' --> ' + url
        + '\n\t ....' + self.fileContent.substr(-20) + url + self.rawContent.substr(0, 20) + '....');
        self.fileContent += url;
    } else {
        self.fileContent += fixedUrl;
    }
    this.caseNumber++;
}
var hp = new https();
exports.check = function (content, file, hostmap, mode) {
    if (mode == 'fix') {
        if (content.indexOf('skip fis-parse-protocolfix') != -1) {
            return content.replace(/\|protocol_none/gm, '');
        } else {
            hp.setFileInfo(file);
            // 逐字扫描文件
            return hp.parse(content, hostmap, mode);
        }
    } else if (mode == 'remove') {
        content = content.replace(/\|protocol_none/gm, '');
        return content.replace(/(__protocol|\|protocol)/gm, '');
    }
}
<commit_msg>修复url匹配bug<commit_after>/*
 * http://video.baidu.com/
 */

'use strict';
function https () {
    this.caseNumber = 0;
    this.keyword = ['src', 'action', 'href', 'background','background-image'];
}

https.prototype.parse = function (content, hostmap, mode) {
    var self = this;
    self.rawContent = content.toString();
    self.error_result = [];
    self.fileContent = '';
    self.hostmap = hostmap;
    self.mode = mode;
    self.inAtag = false; //是否在a标签内部
    var res;
    while (self.rawContent.length) {
        var matchKey = false;
        for (var i = 0; i < self.keyword.length; i++) {
            var reg = new RegExp('\\W(' + self.keyword[i] + ')\\W', 'gm');
            if (reg.exec(self.rawContent) != null) {
                if(!matchKey || (reg.lastIndex - 1) < matchKey[0]) {
                    matchKey = [reg.lastIndex - 1, self.keyword[i]];
                }
            }

        }
        if (matchKey) {
            self.fixKeyUrl(matchKey[0], matchKey[1]);
        } else {
            self.fileContent += self.rawContent;
            self.rawContent = '';
        }
    }
    var headPos = self.fileContent.indexOf('{%head%}');
    if (headPos != -1) {
        var before = self.fileContent.substr(0, headPos + 8);
        var after = self.fileContent.substr(headPos + 8);
        self.fileContent = before
            + ('\n{%$protocolHostMap = json_decode(\''
            + JSON.stringify(self.hostmap).replace(/\\\\/g, '') +'\')%}\n'
            + '\n')
            + after;
    }
    // console.log('总计有:' + self.caseNumber);
    if (self.error_result.length) {
        //fis.log.error
        var log = '未处理url协议\n修改方案：js语境中用 "__protocol" 方法把url包起来;smarty变量尾部添加 "|protocol" 修饰器\n'
            + '所在文件：' + self.fileInfo.realpath + '\n'
            + self.error_result.join('\n')
            + '\n--------------------------------------------------------------------------------------------------------------------------------------\n\n';
        fis.util.write('./protocol-fix.log', log, null, true);

    }
    // console.log(self.fileContent);
    return self.fileContent;
}
https.prototype.setFileInfo = function (file) {
    this.fileInfo = file
}
https.prototype.fixUrl = function (url) {
    var res = url;
    // 无需处理的
    if (url == '' || url.indexOf('?__inline') != -1 || url.match(/^('|")?(\.|\.\.|\/|#|#\w|javascript)/g)) {
        return url;
    }
    if (url.indexOf('|protocol_none') != -1) {
        res = url.replace(/\|protocol_none/gm, '');
        return res;
    }
    if (url.match(/^(?!http|https|\/\/|\.).+\.(jpg|png|gif|jpeg)$/g)) {
        return url;
    }
    // 第一步，如果能够匹配到就直接替换
    res = this.fixProtocol(url);
    if (res == url) {
        // 处理smarty和js插件
        var reg = new RegExp('(__protocol\\(|\\$(\\S)+\\|protocol)', 'g');
        if (reg.test(res)) {
            res = res.replace(/\|protocol/gm, '|protocol:$protocolHostMap');
            return res;
        } else {
            return false;
        }
    }
    return res;
}

https.prototype.fixProtocol = function (url) {
    var res = url;
    for (var hostReg in this.hostmap) {
        var reg = new RegExp('((http|https)\:)?(\/\/)?' + hostReg);
        res = url.replace(reg, this.hostmap[hostReg]);
        if (res != url) {
            return res;
        }
    }
    return res;
}

https.prototype.isInAtag = function () {
    // 查找a标签
    var self = this;
    var mark = 0;
    var len = 30;
    var pos = self.fileContent.lastIndexOf('<a ');
    if (pos == -1) {
        return false;
    }
    var content = self.fileContent.substr(pos + 3);
    if (content.lastIndexOf('') == -1) {
        return true;
    } else {
        return false;
    }
}

https.prototype.fixKeyUrl = function (pos, key) {
    var self = this;
    self.fileContent += self.rawContent.substr(0, pos);
    self.rawContent = self.rawContent.substr(pos);
    var url;
    var mark = 0;
    var isBgImage = false;
    var quotes = 0;
    if (key == 'href' && self.isInAtag()) {
        return;
    }
    // 查找链接的开头位置
    while (true) {
        if (self.rawContent == '' || self.rawContent[0] == '=' && (self.rawContent[1] == '>' || self.rawContent[1] == '=')) {
            return;
        }

        if (key == 'background' && self.rawContent[mark] != '-' || key == 'background-image') {
            if (self.rawContent.substr(mark, mark + 4) == 'url(') {
                isBgImage = true;
                self.fileContent += self.rawContent.substr(0, mark + 4);
                self.rawContent = self.rawContent.substr(mark + 4);
                break;
            } else {
                if (self.rawContent[0] == ';') {
                    return;
                }
                self.fileContent += self.rawContent[0];
                self.rawContent = self.rawContent.substr(1);
                continue;
            }
        }
        if (self.rawContent[0] == '=' && self.rawContent[1] == '\\') {
            self.fileContent += self.rawContent.substr(0,3);
            self.rawContent = self.rawContent.substr(3);
            break;
        }
        if (self.rawContent[0] == '=' && self.rawContent[1] != '>' || self.rawContent[0] == ':') {
            self.fileContent += self.rawContent[0];
            self.rawContent = self.rawContent.substr(1);
            break;
        }
        else if (self.rawContent[0] == ' '){
            self.fileContent += self.rawContent[0];
            self.rawContent = self.rawContent.substr(1);
        }else {
            return;
        }
    }
    var urlhead = self.rawContent[0];
    var plus = 0;
    // 查找链接的结束位置
    while (true) {
        mark++;
        if (mark > self.rawContent.length -1) {
            url = self.rawContent.substr(0, mark-1);
            self.rawContent = self.rawContent.substr(mark-1);
            break;
        }
        if (!isBgImage) {
            if(self.rawContent[mark] == '('){
                quotes++;
            }else if (self.rawContent[mark] == ')') {
                quotes--;
                if (quotes<0) {
                    quotes = 0;
                }
            }
        }

        if(quotes) continue;

        if (isBgImage && self.rawContent[mark] == ')') {
            url = self.rawContent.substr(0, mark);
            self.rawContent = self.rawContent.substr(mark);
            break;
        }

        if (self.rawContent[mark] == '\'' || self.rawContent[mark] == '"') {
            if(urlhead == self.rawContent[mark] && self.rawContent[mark - 1] != '\\' && self.rawContent[mark - 1] != '[' && self.rawContent[mark + 1] != ']') {
                self.fileContent += self.rawContent[0];
                url = self.rawContent.substr(1, mark-1);
                self.rawContent = self.rawContent.substr(mark);
                break;
            }
        }
        if (!isBgImage && !quotes && (self.rawContent[mark] == ';' || self.rawContent[mark] == '\n' ||  self.rawContent[mark] == ',')) {
            url = self.rawContent.substr(0, mark);
            self.rawContent = self.rawContent.substr(mark);
            break;
        }

    }
    /*如果是背景图片，并且没有协议，说明是相对路径，直接跳过(todo bug: 如果语境是js，那么这个判断会失效)
    if (isBgImage && !url.match(/((http|https)\:)?\/\//g)) {
        return;
    };*/
    var fixedUrl = self.fixUrl(url);
    if (fixedUrl === false) {
        self.error_result.push('case' + (self.error_result.length+1)
        + ': line ' + self.fileContent.split('\n').length + ' --> ' + url
        + '\n\t ....' + self.fileContent.substr(-20) + url + self.rawContent.substr(0, 20) + '....');
        self.fileContent += url;
    } else {
        self.fileContent += fixedUrl;
    }
    this.caseNumber++;
}
var hp = new https();
exports.check = function (content, file, hostmap, mode) {
    if (mode == 'fix') {
        if (content.indexOf('skip fis-parse-protocolfix') != -1) {
            return content.replace(/\|protocol_none/gm, '');
        } else {
            hp.setFileInfo(file);
            // 逐字扫描文件
            return hp.parse(content, hostmap, mode);
        }
    } else if (mode == 'remove') {
        content = content.replace(/\|protocol_none/gm, '');
        return content.replace(/(__protocol|\|protocol)/gm, '');
    }
}
<|endoftext|>


<commit_before>/*
 * JavaScript to dynamically change form action on the UserData upload page according to the option selected (or radio buttons checked)
 */

Ensembl.Panel.UserData = Ensembl.Panel.extend({
  init: function () {
    var panel = this;
    this.base();

    this.elLk.form          = this.el.find('form');
    this.elLk.actionInputs  = this.elLk.form.find(':input._action').each(function() {
      $(this).on('change', function() {

        var action;
        if (this.nodeName == 'SELECT') {
          var radio = panel.elLk.actionInputs.filter('input:visible:checked');
          if (radio.length) { // give priority to radio buttons if they are visible
            action = radio[0];
          } else {
            action = $(this).find('option:selected')[0];
          }
        } else if (this.checked) {
          action = this;
        }
        action = action ? (action.className.match(/(?:\s+|^)_action_([^\s]+)/) || []).pop() || '' : '';
        if (action) {
          panel.elLk.form.toggleClass('upload', action == 'upload').attr('action', panel.elLk.form.find('input[name=' + action + ']').val());
        }
      });
    });
  }
});<commit_msg>add form validation to UserData upload form<commit_after>/*
 * JavaScript to dynamically change form action on the UserData upload page according to the option selected (or radio buttons checked) and do validation on the form
 */

Ensembl.Panel.UserData = Ensembl.Panel.ModalContent.extend({ // inheriting ModalContent because it has the generic 'formSubmit' method
  init: function () {
    var panel = this;
    this.base();

    // form
    this.elLk.form = this.el.find('form').validate().on('submit.UserData', function() {
      var visibleInps = panel.elLk.requiredInputs.filter(':visible');
      if (visibleInps.length > 1 && !$.grep(visibleInps, function (inp) { return !!inp.value; })[0]) {// if neither of the multiple inputs have any value
        panel.elLk.errorMessage.show();
        return false;
      }
      return panel.formSubmit($(this));
    });

    this.elLk.requiredInputs = this.elLk.form.find(':input.required');

    this.elLk.errorMessage = this.elLk.form.find('label._userdata_upload_error');

    this.elLk.actionInputs = this.elLk.form.find(':input._action').each(function() {
      $(this).on('change.UserData', function() {

        // change the form action according to the dropdown/radio buttons (if <select> is changed, give priority to radio buttons if they are visible)
        var action = this.nodeName === 'SELECT' ? panel.elLk.actionInputs.filter('input:visible:checked')[0] || $(this).find('option:selected')[0] : this;
            action = action ? (action.className.match(/(?:\s+|^)_action_([^\s]+)/) || []).pop() || '' : '';
        if (action) {
          panel.elLk.form.toggleClass('upload', action === 'upload').attr('action', panel.elLk.form.find('input[name=' + action + ']').val());
        }

        // Apply/remove the validation to/from individual input field
        panel.elLk.form.validate(); // reset any validation error messages
        panel.elLk.errorMessage.hide();
        var visibleInps = panel.elLk.requiredInputs.validate(false).filter(':visible');
        if (visibleInps.length === 1) {
          visibleInps.validate(true);
        } else {  // validate only if any value is entered in the inputs, ignore any null value
          visibleInps.off('.UserData').on({
            'keyup.UserData': function (e) {
              if (e.keyCode !== 9) { // ignore TAB
                $(this).validate(!!this.value);
              }
              panel.elLk.errorMessage.hide();
            },
            'change.UserData': function () {
              panel.elLk.errorMessage.hide();
            }
          });
        }
      });
    }).filter('select').validate(true).end(); // not to forget validating the dropdown to select the format
  }
});<|endoftext|>


<commit_before>/**
@typedef {Object} whitelist_entry A whitelist entry
@property {string} title The title of the whitelist entry.
@property {string} url The URL for the whitelist entry.
*/

/**
@typedef {Array<whitelist_entry>} whitelist A whitelist array
*/

/**
@desc Whitelist component
*/
class Whitelist {

  /**
  @desc Initialize the instance.
  @private
  */
  constructor(email) {
    /**
    @desc Whitelist's Email
    @private
    */
    this.email = email;
  }

  /**
  @desc Add URL to whitelist.

  @param {string} title - Title for whitelist entry
  @param {string} url - URL for whitelist entry
  @return {boolean} Was successfully added to whitelist
  @throws {Error} URL is already in whitelist.
  @public
  */
  addURL(title, url) {
    // Trim whitespace from input
    title = title.trim();
    url = url.trim();
    // Validation entry input
    if (title === "") {
      throw new Error("Please enter a Title!");
    }
    if (url === "") {
      throw new Error("Please enter a URL!");
    }

    // Get whitelist
    let whitelist = this.get();

    // Check that URL is not already in whitelist
    if (_.findIndex(whitelist, (entry) => {
        return entry.url === url;
      }) !== -1) {
      // Must already be in whitelist
      // Throw an error!
      throw new Error(`URL pattern '${url}' is already in whitelist`);
    }
    // Not already in whitelist

    // Create new entry
    title = title || url;
    let entry = {
      title,
      url
    };
    // Add entry to whitelist
    whitelist.push(entry);
    // Save whitelist
    this.set(whitelist);
    // Return successful
    return true;
  }

  /**
  @desc Remove URL from whitelist
  @param {string} url - The URL to remove from the whitelist
  @return {whitelist} whitelist The updated whitelist
  @public
  */
  removeURL(url) {
    // Get whitelist
    let whitelist = this.get();
    // Find entry with matching URL in whitelist
    // Remove entry
    _.remove(whitelist, (entry) => {
      return url === entry.url
    });
    // Save new whitelist
    this.set(whitelist);
    return whitelist;
  }

  /**
  @desc Check if URL is allowed
  @param {string} url - The URL to check if whitelist allows.
  @return {boolean} Whether the URL is allowed.
  @public
  */
  isAllowed(url) {
    // Check if Chrome extension
    if (url.indexOf('chrome-extension://') !== -1) {
      return true;
    }
    // Get whitelist
    let whitelist = this.get();
    // Find entry with matching URL in whitelist
    let idx = _.findIndex(whitelist, (entry) => {
      try {
        return Whitelist.testURLs(url, entry.url);
      } catch (error) {
        console.error(error);
        return false;
      }
    });
    // Allow if not idx isnt -1
    console.log('isAllowed', url, idx !== -1);
    return idx !== -1;
  }

  /**
  @desc Get the whitelist in localStorage.

  @return {whitelist} The whitelist.
  @private
  */
  get() {
    return Whitelist.getWhitelistForEmail(this.email);
  }

  /**
  @desc Set the whitelist in localStorage.

  @param {whitelist} whitelist - The whitelist to save.
  @return {boolean} Whether successfully saved.
  @private
  */
  set(whitelist) {
    return Whitelist.setWhitelistForEmail(this.email, whitelist);
  }

  /**
  @desc Test URLs.
  @param {string} a - URL to test against. Can be a RegExp.
  @param {string} b - URL to test with.
  @return {boolean} If URL `a` matches URL `b`
  @private
  @example
  testURLs('https://google.com','*://google.com/**'); // True
  testURLs('https://google.com','*://google.ca/**'); // False
  */
  static testURLs(a, b) {
    // Ensure that a URL ends with "/" such that "/**" matches
    // Check to make sure there is a path
    if (_.endsWith(b, '/**') && a.split('?').length === 1 && a[a.length - 1] !== '/') {
      a += '/';
    }
    return minimatch(a, b);
  }

  /**
  @desc Get the key to lookup in localStorage for the whitelist.
  @return {string} localStorage key for the whitelist for the given email.
  @private
  */
  static getWhitelistKeyForEmail(email) {
    return 'whitelist:' + email;
  }

  /**
  @desc Get the whitelist from localStorage
  @private
  @return {whitelist} The whitelist
  @see https://developer.chrome.com/extensions/storage
  @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
  */
  static getWhitelistForEmail(email) {
    let key = Whitelist.getWhitelistKeyForEmail(email);
    let whitelist = localStorage[key];
    if (typeof whitelist === 'string') {
      return JSON.parse(whitelist);
    }
    /*
      else if (typeof whitelist === 'object') {
      return whitelist;
    } */
    else {
      //console.warn('Unknown whitelist type: ', typeof whitelist, whitelist);
      return [];
    }
  }

  /**
  @desc Set the whitelist in localStorage
  @private
  @param {string} email - The admin's email address.
  @param {whitelist} whitelist - The whitelist to save.
  @return {boolean} Whether successfully saved.
  @see https://developer.chrome.com/extensions/storage
  @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
  */
  static setWhitelistForEmail(email, whitelist) {
    let key = Whitelist.getWhitelistKeyForEmail(email);
    if (typeof whitelist !== 'string') {
      whitelist = JSON.stringify(whitelist);
    }
    localStorage[key] = whitelist;
    // Return successful
    return true;
  }

}

/**
@desc Moderator component
*/
class Moderator {

  /**
  @desc Initialize the instance.
  @private
  */
  constructor() {

    /**
    @desc Logged in state
    */
    this.loggedIn = false;
    /**
    @desc Moderator is in disabled state
    */
    this.isDisabled = false;
    /**
    @desc The Whitelist instance
    @private
    */
    this.whitelist = null;

    // Event listeners
    this.setupListeners();

  }

  /**
  @desc Setup all event listeners.

  @see https://developer.chrome.com/extensions/webRequest
  @listens {onBeforeRequest} Listens for before requests
  @private
  */
  setupListeners() {

    // Request listener
    chrome.webRequest.onBeforeRequest.addListener((info) => {
      var url = info.url;
      let whitelist = this.getWhitelist();
      // console.log('webRequest', this, this.isDisabled, whitelist);
      if (this.isDisabled || !whitelist) {
        return {
          cancel: false
        };
      }
      let shouldAllow = whitelist.isAllowed(url);
      // console.log('URL: ', url, shouldAllow);
      return {
        cancel: !shouldAllow
      };
    }, {
      urls: ["<all_urls>"]
    }, ["blocking"]);

    // Message Passing listener
    // chrome.runtime.onMessage.addListener(
    //   (request, sender, sendResponse) => {
    //     console.log(sender.tab ?
    //       "from a content script:" + sender.tab.url :
    //       "from the extension");
    //     if (request.type == "login") {
    //       // Login!
    //       this.session.login((error, userInfo) => {
    //         console.log('userInfo', userInfo, error);
    //         sendResponse(userInfo);
    //       });
    //     } else {
    //       console.warn('Unknown message type', request);
    //     }
    //   });

  }

  /**
  @desc Disable moderator
  */
  disable() {
    this.isDisabled = true;
  }
  /**
  @desc Enable moderator
  */
  enable() {
    this.isDisabled = false;
  }

  /**
  @desc Check if Chrome is locked.
        Locked means that an admin has signed in with their email.
  @return {boolean} Whether Chrome is locked.
  @public
  */
  isLocked() {
    return !this.isUnlocked();
  }

  /**
  @desc Check if Chrome is unlocked.
        Locked means that an admin has signed in with their email.
  @return {boolean} Whether Chrome is unlocked.
  @public
  */
  isUnlocked() {
    return !this.getEmail();
  }

  /**
  @desc Lock
  @return {boolean} Whether successfully locked
  @public
  */
  lock(email, password) {
    // Check to make sure it is not already locked
    if (this.isUnlocked()) {
      // Not already locked
      this.setEmail(email);
      this.setPassword(password);
      return this.login(email, password);
    } else {
      return this.login(email, password);
    }
  }

  /**
  @desc Unlock
  @return {boolean} Whether successfully unlocked
  @public
  */
  unlock(email, password) {
    if (this.loggedIn || this.login(email, password)) {
      // Logged in now
      this.setEmail(null);
      this.setPassword(null);
      this.whitelist = null;
      return this.logout();
    } else {
      // Could not login to unlock
      return false;
    }
  }

  /**
  @desc Login
  @return {boolean} Whether successfully logged in
  @public
  */
  login(email, password) {
    var enteredHash = CryptoJS.SHA256(password);
    var passwordHash = enteredHash.toString(CryptoJS.enc.Base64)
    if (email === this.getEmail() && passwordHash === this.getPassword()) {
      this.loggedIn = true;
      this.whitelist = new Whitelist(this.getEmail());
      return true;
    } else {
      return false;
    }
  }

  /**
  @desc logout
  @return {boolean} Whether successfully logged out
  @public
  */
  logout() {
    this.loggedIn = false;
    return true;
  }

  /**
  @desc Get administator's email.
  @private
  @return {string} Admin email.
  */
  getEmail() {
      return localStorage['admin:email'];
    }
    /**
    @desc Set administrator's email.
    @private
    @param {string} email - The new email.
    @return {void} Void.
    */
  setEmail(email) {
    if (email === null) {
      delete localStorage['admin:email'];
    } else {
      localStorage['admin:email'] = email;
    }
  }

  /**
  @desc Get administator's password.
  @private
  @return {string} Admin password.
  */
  getPassword() {
      return localStorage['admin:password'];
    }
    /**
    @desc Set administrator's password.
    @private
    @param {string} password - The new password.
    @return {void} Void.
    */
  setPassword(password) {
    if (password === null) {
      delete localStorage['admin:password'];
    } else {
      var hash = CryptoJS.SHA256(password);
      localStorage['admin:password'] = hash.toString(CryptoJS.enc.Base64);
    }
  }

  /**
  @desc Get the current Whitelist instance.
  @return {Object} The whitelist instance.
  @private
  */
  getWhitelist() {
    return this.whitelist;
  }

  /**
  @desc Get the serialized JSON form of the Whitelist.
  @return {whitelist} The whitelist JSON.
  @public
  */
  getWhitelistJSON() {
    let whitelist = this.getWhitelist();
    if (whitelist) {
      return whitelist.get();
    } else {
      return [];
    }
  }

  /**
  @desc Add to Whitelist
  @return {boolean} Whether successfully added to whitelist.
  @public
  */
  addToWhitelist(title, url) {
    if (this.loggedIn && this.whitelist) {
      this.whitelist.addURL(title, url);
      return true;
    } else {
      return false;
    }
  }

  /**
  @desc Remove from Whitelist
  @return {boolean} Whether successfully removed from whitelist.
  @public
  */
  removeFromWhitelist(url) {
    if (this.loggedIn && this.whitelist) {
      this.whitelist.removeURL(url);
      return true;
    } else {
      return false;
    }
  }

}

/**
===== Main =====
Start the background script

@ignore
*/
let moderator = window.moderator = new Moderator();<commit_msg>See #25. Improve glob matching, allow missing scheme/prefix in pattern<commit_after>/**
@typedef {Object} whitelist_entry A whitelist entry
@property {string} title The title of the whitelist entry.
@property {string} url The URL for the whitelist entry.
*/

/**
@typedef {Array<whitelist_entry>} whitelist A whitelist array
*/

/**
@desc Whitelist component
*/
class Whitelist {

  /**
  @desc Initialize the instance.
  @private
  */
  constructor(email) {
    /**
    @desc Whitelist's Email
    @private
    */
    this.email = email;
  }

  /**
  @desc Add URL to whitelist.

  @param {string} title - Title for whitelist entry
  @param {string} url - URL for whitelist entry
  @return {boolean} Was successfully added to whitelist
  @throws {Error} URL is already in whitelist.
  @public
  */
  addURL(title, url) {
    // Trim whitespace from input
    title = title.trim();
    url = url.trim();
    // Validation entry input
    if (title === "") {
      throw new Error("Please enter a Title!");
    }
    if (url === "") {
      throw new Error("Please enter a URL!");
    }

    // Get whitelist
    let whitelist = this.get();

    // Check that URL is not already in whitelist
    if (_.findIndex(whitelist, (entry) => {
        return entry.url === url;
      }) !== -1) {
      // Must already be in whitelist
      // Throw an error!
      throw new Error(`URL pattern '${url}' is already in whitelist`);
    }
    // Not already in whitelist

    // Create new entry
    title = title || url;
    let entry = {
      title,
      url
    };
    // Add entry to whitelist
    whitelist.push(entry);
    // Save whitelist
    this.set(whitelist);
    // Return successful
    return true;
  }

  /**
  @desc Remove URL from whitelist
  @param {string} url - The URL to remove from the whitelist
  @return {whitelist} whitelist The updated whitelist
  @public
  */
  removeURL(url) {
    // Get whitelist
    let whitelist = this.get();
    // Find entry with matching URL in whitelist
    // Remove entry
    _.remove(whitelist, (entry) => {
      return url === entry.url
    });
    // Save new whitelist
    this.set(whitelist);
    return whitelist;
  }

  /**
  @desc Check if URL is allowed
  @param {string} url - The URL to check if whitelist allows.
  @return {boolean} Whether the URL is allowed.
  @public
  */
  isAllowed(url) {
    // Check if Chrome extension
    if (url.indexOf('chrome-extension://') !== -1) {
      return true;
    }
    // Get whitelist
    let whitelist = this.get();
    // Find entry with matching URL in whitelist
    let idx = _.findIndex(whitelist, (entry) => {
      try {
        return Whitelist.testURLs(url, entry.url);
      } catch (error) {
        console.error(error);
        return false;
      }
    });
    // Allow if not idx isnt -1
    console.log('isAllowed', url, idx !== -1);
    return idx !== -1;
  }

  /**
  @desc Get the whitelist in localStorage.

  @return {whitelist} The whitelist.
  @private
  */
  get() {
    return Whitelist.getWhitelistForEmail(this.email);
  }

  /**
  @desc Set the whitelist in localStorage.

  @param {whitelist} whitelist - The whitelist to save.
  @return {boolean} Whether successfully saved.
  @private
  */
  set(whitelist) {
    return Whitelist.setWhitelistForEmail(this.email, whitelist);
  }

  /**
  @desc Test URLs.
  @param {string} url - URL to test against.
  @param {string} pattern - URL pattern with glob support to test with.
  @return {boolean} If URL matches pattern
  @private
  @example
  testURLs('https://google.com','*://google.com/**'); // True
  testURLs('https://google.com','*://google.ca/**'); // False
  */
  static testURLs(url, pattern) {
    // Ensure that a URL ends with "/" such that "/**" matches
    // Check to make sure there is a path
    if (_.endsWith(pattern, '/**') && url.split('?').length === 1 && url[url.length - 1] !== '/') {
      url += '/';
    }
    // Handle cases where scheme is not provided in pattern
    const defaultScheme = "*";
    const schemeSep = "://";
    let urlParts = url.split(schemeSep);
    let patternParts = pattern.split(schemeSep);
    if (patternParts.length === 1) {
      patternParts = ["*", patternParts[0]];
    }
    return minimatch(urlParts[0], patternParts[0]) && minimatch(urlParts[1], patternParts[1]);
  }

  /**
  @desc Get the key to lookup in localStorage for the whitelist.
  @return {string} localStorage key for the whitelist for the given email.
  @private
  */
  static getWhitelistKeyForEmail(email) {
    return 'whitelist:' + email;
  }

  /**
  @desc Get the whitelist from localStorage
  @private
  @return {whitelist} The whitelist
  @see https://developer.chrome.com/extensions/storage
  @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
  */
  static getWhitelistForEmail(email) {
    let key = Whitelist.getWhitelistKeyForEmail(email);
    let whitelist = localStorage[key];
    if (typeof whitelist === 'string') {
      return JSON.parse(whitelist);
    }
    /*
      else if (typeof whitelist === 'object') {
      return whitelist;
    } */
    else {
      //console.warn('Unknown whitelist type: ', typeof whitelist, whitelist);
      return [];
    }
  }

  /**
  @desc Set the whitelist in localStorage
  @private
  @param {string} email - The admin's email address.
  @param {whitelist} whitelist - The whitelist to save.
  @return {boolean} Whether successfully saved.
  @see https://developer.chrome.com/extensions/storage
  @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
  */
  static setWhitelistForEmail(email, whitelist) {
    let key = Whitelist.getWhitelistKeyForEmail(email);
    if (typeof whitelist !== 'string') {
      whitelist = JSON.stringify(whitelist);
    }
    localStorage[key] = whitelist;
    // Return successful
    return true;
  }

}

/**
@desc Moderator component
*/
class Moderator {

  /**
  @desc Initialize the instance.
  @private
  */
  constructor() {

    /**
    @desc Logged in state
    */
    this.loggedIn = false;
    /**
    @desc Moderator is in disabled state
    */
    this.isDisabled = false;
    /**
    @desc The Whitelist instance
    @private
    */
    this.whitelist = null;

    // Event listeners
    this.setupListeners();

  }

  /**
  @desc Setup all event listeners.

  @see https://developer.chrome.com/extensions/webRequest
  @listens {onBeforeRequest} Listens for before requests
  @private
  */
  setupListeners() {

    // Request listener
    chrome.webRequest.onBeforeRequest.addListener((info) => {
      var url = info.url;
      let whitelist = this.getWhitelist();
      // console.log('webRequest', this, this.isDisabled, whitelist);
      if (this.isDisabled || !whitelist) {
        return {
          cancel: false
        };
      }
      let shouldAllow = whitelist.isAllowed(url);
      // console.log('URL: ', url, shouldAllow);
      return {
        cancel: !shouldAllow
      };
    }, {
      urls: ["<all_urls>"]
    }, ["blocking"]);

    // Message Passing listener
    // chrome.runtime.onMessage.addListener(
    //   (request, sender, sendResponse) => {
    //     console.log(sender.tab ?
    //       "from a content script:" + sender.tab.url :
    //       "from the extension");
    //     if (request.type == "login") {
    //       // Login!
    //       this.session.login((error, userInfo) => {
    //         console.log('userInfo', userInfo, error);
    //         sendResponse(userInfo);
    //       });
    //     } else {
    //       console.warn('Unknown message type', request);
    //     }
    //   });

  }

  /**
  @desc Disable moderator
  */
  disable() {
    this.isDisabled = true;
  }
  /**
  @desc Enable moderator
  */
  enable() {
    this.isDisabled = false;
  }

  /**
  @desc Check if Chrome is locked.
        Locked means that an admin has signed in with their email.
  @return {boolean} Whether Chrome is locked.
  @public
  */
  isLocked() {
    return !this.isUnlocked();
  }

  /**
  @desc Check if Chrome is unlocked.
        Locked means that an admin has signed in with their email.
  @return {boolean} Whether Chrome is unlocked.
  @public
  */
  isUnlocked() {
    return !this.getEmail();
  }

  /**
  @desc Lock
  @return {boolean} Whether successfully locked
  @public
  */
  lock(email, password) {
    // Check to make sure it is not already locked
    if (this.isUnlocked()) {
      // Not already locked
      this.setEmail(email);
      this.setPassword(password);
      return this.login(email, password);
    } else {
      return this.login(email, password);
    }
  }

  /**
  @desc Unlock
  @return {boolean} Whether successfully unlocked
  @public
  */
  unlock(email, password) {
    if (this.loggedIn || this.login(email, password)) {
      // Logged in now
      this.setEmail(null);
      this.setPassword(null);
      this.whitelist = null;
      return this.logout();
    } else {
      // Could not login to unlock
      return false;
    }
  }

  /**
  @desc Login
  @return {boolean} Whether successfully logged in
  @public
  */
  login(email, password) {
    var enteredHash = CryptoJS.SHA256(password);
    var passwordHash = enteredHash.toString(CryptoJS.enc.Base64)
    if (email === this.getEmail() && passwordHash === this.getPassword()) {
      this.loggedIn = true;
      this.whitelist = new Whitelist(this.getEmail());
      return true;
    } else {
      return false;
    }
  }

  /**
  @desc logout
  @return {boolean} Whether successfully logged out
  @public
  */
  logout() {
    this.loggedIn = false;
    return true;
  }

  /**
  @desc Get administator's email.
  @private
  @return {string} Admin email.
  */
  getEmail() {
      return localStorage['admin:email'];
    }
    /**
    @desc Set administrator's email.
    @private
    @param {string} email - The new email.
    @return {void} Void.
    */
  setEmail(email) {
    if (email === null) {
      delete localStorage['admin:email'];
    } else {
      localStorage['admin:email'] = email;
    }
  }

  /**
  @desc Get administator's password.
  @private
  @return {string} Admin password.
  */
  getPassword() {
      return localStorage['admin:password'];
    }
    /**
    @desc Set administrator's password.
    @private
    @param {string} password - The new password.
    @return {void} Void.
    */
  setPassword(password) {
    if (password === null) {
      delete localStorage['admin:password'];
    } else {
      var hash = CryptoJS.SHA256(password);
      localStorage['admin:password'] = hash.toString(CryptoJS.enc.Base64);
    }
  }

  /**
  @desc Get the current Whitelist instance.
  @return {Object} The whitelist instance.
  @private
  */
  getWhitelist() {
    return this.whitelist;
  }

  /**
  @desc Get the serialized JSON form of the Whitelist.
  @return {whitelist} The whitelist JSON.
  @public
  */
  getWhitelistJSON() {
    let whitelist = this.getWhitelist();
    if (whitelist) {
      return whitelist.get();
    } else {
      return [];
    }
  }

  /**
  @desc Add to Whitelist
  @return {boolean} Whether successfully added to whitelist.
  @public
  */
  addToWhitelist(title, url) {
    if (this.loggedIn && this.whitelist) {
      this.whitelist.addURL(title, url);
      return true;
    } else {
      return false;
    }
  }

  /**
  @desc Remove from Whitelist
  @return {boolean} Whether successfully removed from whitelist.
  @public
  */
  removeFromWhitelist(url) {
    if (this.loggedIn && this.whitelist) {
      this.whitelist.removeURL(url);
      return true;
    } else {
      return false;
    }
  }

}

/**
===== Main =====
Start the background script

@ignore
*/
let moderator = window.moderator = new Moderator();<|endoftext|>


<commit_before>'use strict'

const exec = require('child_process').exec
const gulp = require('gulp')
const runSequence = require('run-sequence')

runSequence.options.showErrorStackTrace = false

gulp.task('kss', (done) => {
  const genCompLib = './node_modules/.bin/kss-node --config component-lib.json'

  exec(genCompLib, (err) => {
    done(err)
  })
})

gulp.task('component-library', (done) => {
  runSequence(
    ['kss', 'build:v3'],
    'copy:component-library',
    done
  )
})
<commit_msg>Found a bug when running the tasks ‘build:v3’ and ‘KSS’ in parallel, as the component library wasn’t being cleaned out when just running the ‘component-library’ task on its own. These are now ran in sequence.<commit_after>'use strict'

const exec = require('child_process').exec
const gulp = require('gulp')
const runSequence = require('run-sequence')

runSequence.options.showErrorStackTrace = false

gulp.task('kss', (done) => {
  const genCompLib = './node_modules/.bin/kss-node --config component-lib.json'
  exec(genCompLib, (err) => {
    done(err)
  })
})

gulp.task('component-library', (done) => {
  runSequence(
    'build:v3',
    'kss',
    'copy:component-library',
    done
  )
})
<|endoftext|>


<commit_before>xdescribe("integration: transport.webrtc.json", function() {
  var TIMEOUT = 3000;
  var freedom, helper;

  beforeEach(function() {
    freedom = setupModule("relative://spec/helper/providers.json");
    helper = new ProviderHelper(freedom);
  });

  afterEach(function() {
    cleanupIframes();
  });

  it("generates signals", function(done) {
    var ids = {};
    var chanId = undefined;
    var signals = [];
    helper.createProvider("t", "transport.webrtc");
    helper.createChannel(function(newChanId) {
      chanId = newChanId;
      helper.setChannelCallback(chanId, function(msg) {
        signals.push(msg);
        expect(signals.length).toBeGreaterThan(0);
        done();
      });
      var sendData = helper.str2ab("HI");
      ids[0] = helper.call('t', "setup", ["t", chanId]);
      ids[1] = helper.call('t', "send", ["tag", sendData]);
    });
  });

  it("sends data", function(done) {
    var testString = "Hi";
    var ids = {};
    var signals = [];
    var chanId1 = undefined;
    var chanId2 = undefined;
    var doSend;

    helper.createProvider("t1", "transport.webrtc");
    helper.createProvider("t2", "transport.webrtc");
    helper.createChannel(function(newChanId) {
      chanId1 = newChanId;
      if (chanId2) {
        doSend()
      }
    });
    helper.createChannel(function(newChanId) {
      chanId2 = newChanId;
      if (chanId1) {
        doSend();
      }
    });

    doSend = function() {
      helper.on("t2", "onData", function(result) {
        var resultStr = helper.ab2str(result.data);
        expect(signals.length).toBeGreaterThan(0);
        expect(result.data instanceof ArrayBuffer).toBe(true);
        expect(result.tag).toEqual("tag");
        done();
      });

      helper.setChannelCallback(chanId1, function(msg) {
        signals.push(msg);
        helper.sendToChannel(chanId2, msg);
      });
      helper.setChannelCallback(chanId2, function(msg) {
        signals.push(msg);
        helper.sendToChannel(chanId1, msg);
      });
      var sendData = helper.str2ab(testString);
      ids[0] = helper.call("t1", "setup", ["t1", chanId1]);
      ids[1] = helper.call("t2", "setup", ["t2", chanId2]);
      ids[2] = helper.call("t1", "send", ["tag", sendData]);
    };
  });
});
<commit_msg>Add transport chunking and multiple tag transport tests. Refactor some of the transport provider setup in transport test.<commit_after>describe("integration: transport.webrtc.json", function() {
  var TIMEOUT = 3000;
  var freedom, helper, signals;

  beforeEach(function() {
    freedom = setupModule("relative://spec/helper/providers.json");
    helper = new ProviderHelper(freedom);
    signals = [];
  });

  afterEach(function() {
    cleanupIframes();
  });

  function createTwoProviders(callback) {
    var channels = {chanId1: undefined,
                    chanId2: undefined};

    helper.createProvider("t1", "transport.webrtc");
    helper.createProvider("t2", "transport.webrtc");

    helper.createChannel(createChannelCallback.bind(undefined, "chanId1"));
    helper.createChannel(createChannelCallback.bind(undefined, "chanId2"));

    function createChannelCallback(channelName, newChannelId) {
      channels[channelName] = newChannelId;
      if (channels.chanId1 && channels.chanId2) {
        helper.setChannelCallback(channels.chanId1, function(msg) {
          signals.push(msg);
          helper.sendToChannel(channels.chanId2, msg);
        });
        helper.setChannelCallback(channels.chanId2, function(msg) {
          signals.push(msg);
          helper.sendToChannel(channels.chanId1, msg);
        });
        callback(channels.chanId1, channels.chanId2);
      }
    }
  }

  it("generates signals", function(done) {
    var ids = {};
    var chanId = undefined;
    helper.createProvider("t", "transport.webrtc");
    helper.createChannel(function(newChanId) {
      chanId = newChanId;
      helper.setChannelCallback(chanId, function(msg) {
        signals.push(msg);
        expect(signals.length).toBeGreaterThan(0);
        done();
      });
      var sendData = helper.str2ab("HI");
      ids[0] = helper.call('t', "setup", ["t", chanId]);
      ids[1] = helper.call('t', "send", ["tag", sendData]);
    });
  });

  it("sends a small amount of data.", function(done) {
    var testString = "Hi";
    var ids = {};
    function doSend (chanId1, chanId2) {
      helper.on("t2", "onData", function(result) {
        var resultStr = helper.ab2str(result.data);
        expect(signals.length).toBeGreaterThan(0);
        expect(result.data instanceof ArrayBuffer).toBe(true);
        expect(result.tag).toEqual("tag");
        expect(resultStr).toEqual(testString);
        done();
      });

      var sendData = helper.str2ab(testString);
      ids[0] = helper.call("t1", "setup", ["t1", chanId1]);
      ids[1] = helper.call("t2", "setup", ["t2", chanId2]);
      ids[2] = helper.call("t1", "send", ["tag", sendData]);
    };

    createTwoProviders(doSend);
  });

  it("chunks data", function(done) {
    var testString = "1234567890ABC";
    var ids = {};

    for (var i = 0; i < 8; i++) {
      testString += testString + testString;
    }
    // This assumes testString.length is greater than the chunk
    // size. If you change the chunk size, modify this test.

    function doSend (chanId1, chanId2) {
      helper.on("t2", "onData", function(result) {
        var resultStr = helper.ab2str(result.data);
        expect(signals.length).toBeGreaterThan(0);
        expect(result.data instanceof ArrayBuffer).toBe(true);
        expect(result.tag).toEqual("tag");
        expect(resultStr).toEqual(testString);
        done();
      });

      var sendData = helper.str2ab(testString);
      ids[0] = helper.call("t1", "setup", ["t1", chanId1]);
      ids[1] = helper.call("t2", "setup", ["t2", chanId2]);
      ids[2] = helper.call("t1", "send", ["tag", sendData]);
    };

    createTwoProviders(doSend);
  });

  it("sends through multiple tags", function(done){
    var ids = {};
    var toSend = {"tag1": "This is tag 1",
                  "tag2": "This is tag 2",
                  "tag3": "This is tag 3"};
    var tags = Object.keys(toSend).sort();

    function doSend (chanId1, chanId2) {
      var onDataCount = 0;
      var tagsRecievedOn = [];
      helper.on("t2", "onData", function(result) {
        onDataCount += 1;
        var resultStr = helper.ab2str(result.data);
        expect(signals.length).toBeGreaterThan(0);
        expect(result.data instanceof ArrayBuffer).toBe(true);
        expect(resultStr).toEqual(toSend[result.tag]);
        tagsRecievedOn.push(result.tag);
        if (onDataCount === tags.length) {
          tagsRecievedOn.sort();
          expect(tagsRecievedOn).toEqual(tags);
          done();
        }
      });

      var id = 0;
      ids[id++] = helper.call("t1", "setup", ["t1", chanId1]);
      ids[id++] = helper.call("t2", "setup", ["t2", chanId2]);
      for (var tag in toSend) {
        ids[id++] = helper.call("t1", "send", [tag,
                                               helper.str2ab(toSend[tag])]);
      }
    };

    createTwoProviders(doSend);
  });

});
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import {Input, Panel, ListGroup, ListGroupItem, ButtonToolbar, Button} from 'react-bootstrap';
import ElementCollectionLabels from './ElementCollectionLabels';
import UIStore from './stores/UIStore';
import UIActions from './actions/UIActions';
import Aviator from 'aviator';
import ScreenWellplates from './ScreenWellplates';
import ElementStore from './stores/ElementStore';
import ElementActions from './actions/ElementActions';

export default class ScreenDetails extends Component {
  constructor(props) {
    super(props);
    const {screen} = props;
    this.state = { screen };
  }

  componentWillReceiveProps(nextProps) {
    const {screen} = nextProps;
    this.setState({ screen });
  }

  handleSubmit() {
    const {currentCollection} = UIStore.getState();
    const {screen} = this.state;

    if(screen.id == '_new_') {
      let params = screen;
      params.collection_id = currentCollection.id;
      ElementActions.createScreen(params);
    } else {
      ElementActions.updateScreen(screen);
    }
  }

  handleInputChange(type, event) {
    let {screen} = this.state;
    const value = event.target.value;
    switch (type) {
      case 'name':
        screen.name = value;
        break;
      case 'requirements':
        screen.requirements = value;
        break;
      case 'collaborator':
        screen.collaborator = value;
        break;
      case 'conditions':
        screen.conditions = value;
        break;
      case 'result':
        screen.result = value;
        break;
      case 'description':
        screen.description = value;
        break;
    }
    this.setState({
      screen: screen
    });
  }

  closeDetails() {
    UIActions.deselectAllElements();

    let uiState = UIStore.getState();
    Aviator.navigate(`/collection/${uiState.currentCollection.id}`);
  }

  dropWellplate(wellplate) {
    const {screen} = this.state;

    screen.wellplates.push(wellplate);
    this.setState({ screen });
  }

  deleteWellplate(wellplate){
    const {screen} = this.state;
    const wellplateIndex = screen.wellplates.indexOf(wellplate);
    screen.wellplates.splice(wellplateIndex, 1);

    this.setState({ screen });
  }

  render() {
    const {screen} = this.state;
    const {id, wellplates, name, collaborator, result, conditions, requirements, description} = screen;

    const submitLabel = (id == '_new_') ?"Save Screen" : "Update Screen";
    return (

        <Panel header="Screen Details" bsStyle='primary'>

{name}

          <ElementCollectionLabels element={screen}/>
          <ListGroup fill>
            <ListGroupItem>

                    <Input
                      type="text"
                      label="Name"
                      value={name}
                      onChange={event => this.handleInputChange('name', event)}
                      />

                    <Input
                      type="text"
                      label="Collaborator"
                      value={collaborator}
                      onChange={event => this.handleInputChange('collaborator', event)}
                      />

                    <Input
                      type="text"
                      label="Requirements"
                      value={requirements}
                      onChange={event => this.handleInputChange('requirements', event)}
                      />

                    <Input
                      type="text"
                      label="Conditions"
                      value={conditions}
                      onChange={event => this.handleInputChange('conditions', event)}
                      />

                    <Input
                      type="text"
                      label="Result"
                      value={result}
                      onChange={event => this.handleInputChange('result', event)}
                      />

                    <Input
                      type="textarea"
                      label="Description"
                      value={description}
                      onChange={event => this.handleInputChange('description', event)}
                      />

            </ListGroupItem>
            <ListGroupItem header="Wellplates">
              <ScreenWellplates
                wellplates={wellplates}
                dropWellplate={wellplate => this.dropWellplate(wellplate)}
                deleteWellplate={wellplate => this.deleteWellplate(wellplate)}
                />
            </ListGroupItem>
          </ListGroup>
          <ButtonToolbar>
            <Button bsStyle="primary" onClick={() => this.closeDetails()}>Back</Button>
            <Button bsStyle="warning" onClick={() => this.handleSubmit()}>{submitLabel}</Button>
          </ButtonToolbar>
        </Panel>

    );
  }
}
<commit_msg>Change button labels for ScreenDetails<commit_after>import React, {Component} from 'react';
import {Input, Panel, ListGroup, ListGroupItem, ButtonToolbar, Button} from 'react-bootstrap';
import ElementCollectionLabels from './ElementCollectionLabels';
import UIStore from './stores/UIStore';
import UIActions from './actions/UIActions';
import Aviator from 'aviator';
import ScreenWellplates from './ScreenWellplates';
import ElementStore from './stores/ElementStore';
import ElementActions from './actions/ElementActions';

export default class ScreenDetails extends Component {
  constructor(props) {
    super(props);
    const {screen} = props;
    this.state = { screen };
  }

  componentWillReceiveProps(nextProps) {
    const {screen} = nextProps;
    this.setState({ screen });
  }

  handleSubmit() {
    const {currentCollection} = UIStore.getState();
    const {screen} = this.state;

    if(screen.id == '_new_') {
      let params = screen;
      params.collection_id = currentCollection.id;
      ElementActions.createScreen(params);
    } else {
      ElementActions.updateScreen(screen);
    }
  }

  handleInputChange(type, event) {
    let {screen} = this.state;
    const value = event.target.value;
    switch (type) {
      case 'name':
        screen.name = value;
        break;
      case 'requirements':
        screen.requirements = value;
        break;
      case 'collaborator':
        screen.collaborator = value;
        break;
      case 'conditions':
        screen.conditions = value;
        break;
      case 'result':
        screen.result = value;
        break;
      case 'description':
        screen.description = value;
        break;
    }
    this.setState({
      screen: screen
    });
  }

  closeDetails() {
    UIActions.deselectAllElements();

    let uiState = UIStore.getState();
    Aviator.navigate(`/collection/${uiState.currentCollection.id}`);
  }

  dropWellplate(wellplate) {
    const {screen} = this.state;

    screen.wellplates.push(wellplate);
    this.setState({ screen });
  }

  deleteWellplate(wellplate){
    const {screen} = this.state;
    const wellplateIndex = screen.wellplates.indexOf(wellplate);
    screen.wellplates.splice(wellplateIndex, 1);

    this.setState({ screen });
  }

  render() {
    const {screen} = this.state;
    const {id, wellplates, name, collaborator, result, conditions, requirements, description} = screen;

    const submitLabel = (id == '_new_') ? "Create" : "Save";
    return (

        <Panel header="Screen Details" bsStyle='primary'>

{name}

          <ElementCollectionLabels element={screen}/>
          <ListGroup fill>
            <ListGroupItem>

                    <Input
                      type="text"
                      label="Name"
                      value={name}
                      onChange={event => this.handleInputChange('name', event)}
                      />

                    <Input
                      type="text"
                      label="Collaborator"
                      value={collaborator}
                      onChange={event => this.handleInputChange('collaborator', event)}
                      />

                    <Input
                      type="text"
                      label="Requirements"
                      value={requirements}
                      onChange={event => this.handleInputChange('requirements', event)}
                      />

                    <Input
                      type="text"
                      label="Conditions"
                      value={conditions}
                      onChange={event => this.handleInputChange('conditions', event)}
                      />

                    <Input
                      type="text"
                      label="Result"
                      value={result}
                      onChange={event => this.handleInputChange('result', event)}
                      />

                    <Input
                      type="textarea"
                      label="Description"
                      value={description}
                      onChange={event => this.handleInputChange('description', event)}
                      />

            </ListGroupItem>
            <ListGroupItem header="Wellplates">
              <ScreenWellplates
                wellplates={wellplates}
                dropWellplate={wellplate => this.dropWellplate(wellplate)}
                deleteWellplate={wellplate => this.deleteWellplate(wellplate)}
                />
            </ListGroupItem>
          </ListGroup>
          <ButtonToolbar>
            <Button bsStyle="primary" onClick={() => this.closeDetails()}>Back</Button>
            <Button bsStyle="warning" onClick={() => this.handleSubmit()}>{submitLabel}</Button>
          </ButtonToolbar>
        </Panel>

    );
  }
}
<|endoftext|>


<commit_before>// @flow

import React from 'react'
import '../sigma/layout.forceLink'

type State = {
	running: boolean,
	timer?: number,
	drawEdges?: ?boolean
};

type Props = {
	barnesHutOptimize?: boolean,
	barnesHutTheta?: number,
	adjustSizes?: boolean,
	iterationsPerRender?: number,
	linLogMode: boolean,
	outboundAttractionDistribution?: boolean,
	edgeWeightInfluence?: number,
	scalingRatio?: number,
	strongGravityMode?: boolean,
	slowDown?: number,
	gravity?: number,
	alignNodeSiblings?: boolean,
	nodeSiblingsScale?: number,
	nodeSiblingsAngleMin?: number,
	worker: boolean,
	background?: boolean,
	easing?: Sigma$Easing,
	randomize?: "globally" | "locally" | "no",
	timeout?: number,
	sigma?: Sigma
};

type DefaultProps = {
	worker: boolean,
	linLogMode: boolean
};

/**

ForceLink component, starts Force Atlas2 algorythm once component is mounted,
it is advanced version of ForceAtlas2 plugin, but it is not included in the main
distribution script react-sigma.min.js , rather should be imported explicitly:

```
import ForceLink from 'react-sigma/lib/ForceLink'
```

It accepts all the parameters of ForceLink described on its github page:
@param {boolean} barnesHutOptimize  Use the algorithm's Barnes-Hut to improve repulsion's scalability
									This is useful for large graph but harmful to small ones.
@param {number} barnesHutTheta
@param {boolean} adjustSizes
@param {number} iterationsPerRender
@param {boolean} [linLogMode=true]
@param {boolean} outboundAttractionDistribution
@param {number} edgeWeightInfluence
@param {number} scalingRatio
@param {boolean} strongGravityMode
@param {number} gravity
@param {boolean} alignNodeSiblings
@param {number} nodeSiblingsScale
@param {number} nodeSiblingsAngleMin
@param {boolean} [worker=true]  Use a web worker to run calculations in separate thread
@param {boolean} background
@param {Sigma$Easing} easing  Easing mode
@param {"globally"|"locally"} randomize  Randomize node positions before start
@param {number} slowDown
@param {number} timeout   how long algorythm should run. default=graph.nodes().length * 10

[see sigma plugin page for more details](https://github.com/Linkurious/linkurious.js/tree/develop/plugins/sigma.layouts.forceLink)

@example
import ForceLink from 'react-sigma/lib/ForceLink'
...
<Sigma>
  <LoadJSON path="/public/graph.json">
  	<RelativeSize initialSize={8}/>
		<ForceLink background easing="cubicInOut"/>
	</LoadJSON>
</Sigma>

**/

class ForceLink extends React.Component {
	state: State;
	props: Props;
	static defaultProps: DefaultProps = {
		worker: true,
		linLogMode: true
	}

	constructor(props: Props) {
		super(props)
		this.state = {running:false}
	}

	componentDidMount() {
		this._refreshGraph()
	}

	// Change sigma status only after react rendering complete
	componentDidUpdate(prevProps: Props, prevState: State) {
		let s = this.props.sigma
		if(prevState.running && !this.state.running && s) {
			this._stopForceLink()
			s.refresh();
		} else if (ForceLink._propsChanged(prevProps, this.props)) {
			this._stopForceLink()
			this._refreshGraph()
		}
	}

	componentWillUnmount() {
		this._stopForceLink()
	}

	//TODO: Add composition of child components after timeout
	render = () => null

  _stopForceLink() {
		sigma.layouts.stopForceLink()
		if(this.state.timer) clearTimeout(this.state.timer)
		if(this.props.sigma && this.props.sigma.settings) this.props.sigma.settings({drawEdges:this.state.drawEdges})
  }

	_refreshGraph() {
		let s = this.props.sigma
		if(!sigma || !s) return

		let drawEdges = s.settings("drawEdges")
		if(s.graph.edges().length > 1000)
				s.settings({drawEdges: false})

		sigma.layouts.configForceLink(s, ForceLink._stripOptions(this.props))
		sigma.layouts.startForceLink(s)
		// TODO: convert running status to state
		let timer = setTimeout(() => {
					this.setState({running:false, timer:undefined})
				}, this.props.timeout || s.graph.nodes().length*8 );
		this.setState({running:true, timer, drawEdges})
	}

	//strip force atlas options from component props
	static _stripOptions(props: Props) {
		return Object.assign({}, props, {sigma: undefined})
	}

	static _propsChanged(prev: Props, next: Props) {
		for(let key in prev)
			if(prev[key] !== next[key])
				return true
		return false
	}
}

export default ForceLink;
<commit_msg>ForceLink now accept children for composition<commit_after>// @flow

import React from 'react'
import '../sigma/layout.forceLink'
import { embedProps } from './tools'

type State = {
	running: boolean,
	timer?: number,
	drawEdges?: ?boolean
};

type Props = {
	barnesHutOptimize?: boolean,
	barnesHutTheta?: number,
	adjustSizes?: boolean,
	iterationsPerRender?: number,
	linLogMode: boolean,
	outboundAttractionDistribution?: boolean,
	edgeWeightInfluence?: number,
	scalingRatio?: number,
	strongGravityMode?: boolean,
	slowDown?: number,
	gravity?: number,
	alignNodeSiblings?: boolean,
	nodeSiblingsScale?: number,
	nodeSiblingsAngleMin?: number,
	worker: boolean,
	background?: boolean,
	easing?: Sigma$Easing,
	randomize?: "globally" | "locally" | "no",
	timeout?: number,
	sigma?: Sigma
};

type DefaultProps = {
	worker: boolean,
	linLogMode: boolean
};

/**

ForceLink component, starts Force Atlas2 algorythm once component is mounted,
it is advanced version of ForceAtlas2 plugin, but it is not included in the main
distribution script react-sigma.min.js , rather should be imported explicitly:

```
import ForceLink from 'react-sigma/lib/ForceLink'
```

It accepts all the parameters of ForceLink described on its github page:
@param {boolean} barnesHutOptimize  Use the algorithm's Barnes-Hut to improve repulsion's scalability
									This is useful for large graph but harmful to small ones.
@param {number} barnesHutTheta
@param {boolean} adjustSizes
@param {number} iterationsPerRender
@param {boolean} [linLogMode=true]
@param {boolean} outboundAttractionDistribution
@param {number} edgeWeightInfluence
@param {number} scalingRatio
@param {boolean} strongGravityMode
@param {number} gravity
@param {boolean} alignNodeSiblings
@param {number} nodeSiblingsScale
@param {number} nodeSiblingsAngleMin
@param {boolean} [worker=true]  Use a web worker to run calculations in separate thread
@param {boolean} background
@param {Sigma$Easing} easing  Easing mode
@param {"globally"|"locally"} randomize  Randomize node positions before start
@param {number} slowDown
@param {number} timeout   how long algorythm should run. default=graph.nodes().length * 10

[see sigma plugin page for more details](https://github.com/Linkurious/linkurious.js/tree/develop/plugins/sigma.layouts.forceLink)

@example
import ForceLink from 'react-sigma/lib/ForceLink'
...
<Sigma>
  <LoadJSON path="/public/graph.json">
  	<RelativeSize initialSize={8}/>
		<ForceLink background easing="cubicInOut"/>
	</LoadJSON>
</Sigma>

**/

class ForceLink extends React.Component {
	state: State = {running: true};
	props: Props;
	static defaultProps: DefaultProps = {
		worker: true,
		linLogMode: true
	}

	componentDidMount() {
		this._refreshGraph()
	}

	// Change sigma status only after react rendering complete
	componentDidUpdate(prevProps: Props, prevState: State) {
		let s = this.props.sigma
		if(prevState.running && !this.state.running && s) {
			this._stopForceLink()
			s.refresh();
		} else if (ForceLink._propsChanged(prevProps, this.props)) {
			this._stopForceLink()
			this._refreshGraph()
		}
	}

	componentWillUnmount() {
		this._stopForceLink()
	}

	render = () => {
		if(this.state.running)
			return null;
    return <div>{ embedProps(this.props.children, {sigma: this.props.sigma}) }</div>;
	}

  _stopForceLink() {
		sigma.layouts.stopForceLink()
		if(this.state.timer) clearTimeout(this.state.timer)
		if(this.props.sigma && this.props.sigma.settings) this.props.sigma.settings({drawEdges:this.state.drawEdges})
  }

	_refreshGraph() {
		let s = this.props.sigma
		if(!sigma || !s) return

		let drawEdges = s.settings("drawEdges")
		if(s.graph.edges().length > 1000)
				s.settings({drawEdges: false})

		sigma.layouts.configForceLink(s, ForceLink._stripOptions(this.props))
		sigma.layouts.startForceLink(s)
		// TODO: convert running status to state
		let timer = setTimeout(() => {
					this.setState({running:false, timer:undefined})
				}, this.props.timeout || s.graph.nodes().length*8 );
		this.setState({running:true, timer, drawEdges})
	}

	//strip force atlas options from component props
	static _stripOptions(props: Props) {
		return Object.assign({}, props, {sigma: undefined})
	}

	static _propsChanged(prev: Props, next: Props) {
		for(let key in prev)
			if(prev[key] !== next[key])
				return true
		return false
	}
}

export default ForceLink;
<|endoftext|>


<commit_before>define([
    'arches',
    'jquery',
    'knockout',
    'leaflet',
    'views/components/workbench',
    'leaflet-iiif',
    'leaflet-fullscreen',
    'bindings/leaflet'
], function(arches, $, ko, L, WorkbenchViewmodel) {
    var IIIFViewerViewmodel = function(params) {
        var self = this;
        var abortFetchManifest;
        var getLabel = function(object) {
            var label = object.label;
            if (Array.isArray(label)) label = object.label[0]["@value"];
            return label;
        };

        this.map = ko.observable();
        this.manifest = ko.observable(params.manifest);
        this.editManifest = ko.observable(!params.manifest);
        this.canvas = ko.observable(params.canvas);
        this.manifestLoading = ko.observable();
        this.filter = ko.observable('');
        this.manifestData = ko.observable();
        this.manifestError = ko.observable();
        this.annotationNodes = ko.observableArray();
        window.fetch(arches.urls.iiifannotationnodes)
            .then(function(response) {
                return response.json();
            })
            .then(function(json) {
                self.annotationNodes(
                    json.map(function(node) {
                        var annotations = ko.observableArray();
                        var updateAnnotations = function() {
                            var canvas = self.canvas();
                            if (canvas) {
                                window.fetch(arches.urls.iiifannotations + '?canvas=' + canvas + '&nodeid=' + node.nodeid)
                                    .then(function(response) {
                                        return response.json();
                                    })
                                    .then(function(json) {
                                        annotations(json.features);
                                    });
                            }
                        };
                        self.canvas.subscribe(updateAnnotations);
                        updateAnnotations();
                        return {
                            name: node['graph_name'] + ' - ' + node.name,
                            icon: node.icon,
                            active: ko.observable(false),
                            opacity: ko.observable(100),
                            annotations: annotations
                        };
                    })
                );
            });
        var annotationLayer = ko.computed(function() {
            var annotationFeatures = [];
            self.annotationNodes().forEach(function(node) {
                if (node.active()) {
                    var annotations = node.annotations();
                    if (params.tile && params.tile.resourceinstance_id) {
                        annotations = annotations.filter(function(annotation) {
                            return annotation.properties.resourceId !== params.tile.resourceinstance_id;
                        });
                    }
                    annotationFeatures = annotationFeatures.concat(annotations);
                }
            });
            return L.geoJson({
                type: 'FeatureCollection',
                features: annotationFeatures
            }, {
                pointToLayer: function(feature, latlng) {
                    var style = {
                        color: feature.properties.color,
                        fillColor: feature.properties.fillColor,
                        weight: feature.properties.weight,
                        radius: feature.properties.radius,
                        opacity: feature.properties.opacity,
                        fillOpacity: feature.properties.fillOpacity
                    };
                    return L.circleMarker(latlng, style);
                },
                style: function(feature) {
                    var style = {
                        color: feature.properties.color,
                        fillColor: feature.properties.fillColor,
                        weight: feature.properties.weight,
                        radius: feature.properties.radius,
                        opacity: feature.properties.opacity,
                        fillOpacity: feature.properties.fillOpacity
                    };
                    return style;
                }
            });
        });
        var annotationFeatureGroup = new L.FeatureGroup();

        annotationLayer.subscribe(function(newAnnotationLayer) {
            var map = self.map();
            if (map) {
                annotationFeatureGroup.clearLayers();
                annotationFeatureGroup.addLayer(newAnnotationLayer);
            }
        });

        this.canvases = ko.pureComputed(function() {
            var manifestData = self.manifestData();
            var sequences = manifestData ? manifestData.sequences : [];
            var canvases = [];
            sequences.forEach(function(sequence) {
                if (sequence.canvases) {
                    sequence.label = getLabel(sequence);
                    sequence.canvases.forEach(function(canvas) {
                        canvas.label = getLabel(canvas);
                        if (typeof canvas.thumbnail === 'object')
                            canvas.thumbnail = canvas.thumbnail["@id"];
                        else if (canvas.images && canvas.images[0] && canvas.images[0].resource)
                            canvas.thumbnail = canvas.images[0].resource["@id"];
                        canvases.push(canvas);
                    });
                }
            });
            return canvases;
        });
        this.manifestName = ko.pureComputed(function() {
            var manifestData = self.manifestData();
            return getLabel(manifestData || {label: ''});
        });
        this.zoomToCanvas = !(params.zoom && params.center);

        var validateUrl = function(value) {
            return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(value);
        };

        var queryTerm;
        var limit = 10;
        this.manifestSelectConfig = {
            value: this.manifest,
            clickBubble: true,
            multiple: false,
            closeOnSlect: false,
            allowClear: true,
            ajax: {
                url: arches.urls.iiifmanifest,
                dataType: 'json',
                quietMillis: 250,
                data: function(term, page) {
                    var data = {
                        start: (page-1)*limit,
                        limit: limit
                    };
                    queryTerm = term;
                    if (term) data.query = term;
                    return data;
                },
                results: function(data, page) {
                    var results = data.results;
                    if (validateUrl(queryTerm)) results.unshift({
                        url: queryTerm,
                        label: queryTerm
                    });
                    return {
                        results: results,
                        more: data.count >= (page*limit)
                    };
                }
            },
            id: function(item) {
                return item.url;
            },
            formatResult: function(item) {
                return item.label;
            },
            formatSelection: function(item) {
                return item.label;
            },
            clear: function() {
                self.manifest('');
            },
            isEmpty: ko.computed(function() {
                return self.manifest() === '' || !self.manifest();
            }, this),
            initSelection: function() {
                return;
            }
        };

        this.getManifestData = function() {
            var manifestURL = self.manifest();
            if (manifestURL) {
                self.manifestLoading(true);
                self.manifestError(undefined);
                abortFetchManifest = new window.AbortController();
                window.fetch(manifestURL, {signal: abortFetchManifest.signal})
                    .then(function(response) {
                        return response.json();
                    })
                    .then(function(manifestData) {
                        self.manifestData(manifestData);
                        self.editManifest(false);
                    })
                    .catch(function(error) {
                        if (error.message !== "The user aborted a request.")
                            self.manifestError(error);
                    })
                    .finally(function() {
                        self.manifestLoading(false);
                        abortFetchManifest = undefined;
                    });
            }
        };
        this.getManifestData();

        WorkbenchViewmodel.apply(this, [params]);

        this.activeTab.subscribe(function() {
            var map = self.map();
            if (map) setTimeout(function() {
                map.invalidateSize();
            }, 1);
        });

        if (params.showGallery === undefined) params.showGallery = true;
        this.showGallery = ko.observable(params.showGallery);
        if (!params.manifest) params.expandGallery = true;
        this.expandGallery = ko.observable(params.expandGallery);
        this.expandGallery.subscribe(function(expandGallery) {
            if (expandGallery) self.showGallery(true);
        });
        this.showGallery.subscribe(function(showGallery) {
            if (!showGallery) self.expandGallery(false);
        });

        this.toggleGallery = function() {
            self.showGallery(!self.showGallery());
        };

        this.leafletConfig = {
            center: params.center || [0, 0],
            crs: L.CRS.Simple,
            zoom: params.zoom || 0,
            afterRender: this.map
        };

        var canvasLayer;
        this.brightness = ko.observable(100);
        this.contrast = ko.observable(100);
        this.saturation = ko.observable(100);
        this.greyscale = ko.observable(false);
        this.canvasFilter = ko.pureComputed(function() {
            var b = self.brightness() / 100;
            var c = self.contrast() / 100;
            var s = self.saturation() / 100;
            var g = self.greyscale() ? 1 : 0;
            return 'brightness(' + b + ') contrast(' + c + ') ' +
                'saturate(' + s + ') grayscale(' + g + ')';
        });
        var updateCanvasLayerFilter = function() {
            var filter = self.canvasFilter();
            var map = self.map();
            if (map) {
                map.getContainer().querySelector('.leaflet-tile-pane').style.filter = filter;
            }
        };
        this.canvasFilter.subscribe(updateCanvasLayerFilter);

        this.resetImageSettings = function() {
            self.brightness(100);
            self.contrast(100);
            self.saturation(100);
            self.greyscale(false);
        };

        var addCanvasLayer = function() {
            var map = self.map();
            var canvas = self.canvas();
            if (map && canvas) {
                if (canvasLayer) {
                    map.removeLayer(canvasLayer);
                    canvasLayer = undefined;
                }
                if (canvas) {
                    canvasLayer = L.tileLayer.iiif(canvas + '/info.json', {
                        fitBounds: self.zoomToCanvas
                    });
                    canvasLayer.addTo(map);
                    updateCanvasLayerFilter();
                }
            }
            self.zoomToCanvas = false;
        };
        this.map.subscribe(function(map) {
            L.control.fullscreen({
                fullscreenElement: $(map.getContainer()).closest('.workbench-card-wrapper')[0]
            }).addTo(map);
            addCanvasLayer();
            map.addLayer(annotationFeatureGroup);
        });
        this.canvas.subscribe(addCanvasLayer);

        this.selectCanvas = function(canvas) {
            var service = self.getCanvasService(canvas);
            self.zoomToCanvas = true;
            if (service) self.canvas(service);
        };

        this.canvasClick = function(canvas) {
            self.selectCanvas(canvas);
            self.expandGallery(false);
        };

        this.getCanvasService = function(canvas) {
            if (canvas.images.length > 0) return canvas.images[0].resource.service['@id'];
        };

        var updateCanvas = !self.canvas();
        this.manifestData.subscribe(function(manifestData) {
            if (updateCanvas && manifestData.sequences.length > 0) {
                var sequence = manifestData.sequences[0];
                if (sequence.canvases.length > 0) {
                    var canvas = sequence.canvases[0];
                    self.selectCanvas(canvas);
                }
            }
            updateCanvas = true;
        });

        this.toggleManifestEditor = function() {
            self.editManifest(!self.editManifest());
            if (abortFetchManifest) abortFetchManifest.abort();
        };

        this.getAnnotationCount = function() {
            return 0;
        };
    };
    ko.components.register('iiif-viewer', {
        viewModel: IIIFViewerViewmodel,
        template: {
            require: 'text!templates/views/components/iiif-viewer.htm'
        }
    });
    return IIIFViewerViewmodel;
});
<commit_msg>adds support for opacity slider, re: #6837<commit_after>define([
    'arches',
    'jquery',
    'knockout',
    'leaflet',
    'views/components/workbench',
    'leaflet-iiif',
    'leaflet-fullscreen',
    'bindings/leaflet'
], function(arches, $, ko, L, WorkbenchViewmodel) {
    var IIIFViewerViewmodel = function(params) {
        var self = this;
        var abortFetchManifest;
        var getLabel = function(object) {
            var label = object.label;
            if (Array.isArray(label)) label = object.label[0]["@value"];
            return label;
        };

        this.map = ko.observable();
        this.manifest = ko.observable(params.manifest);
        this.editManifest = ko.observable(!params.manifest);
        this.canvas = ko.observable(params.canvas);
        this.manifestLoading = ko.observable();
        this.filter = ko.observable('');
        this.manifestData = ko.observable();
        this.manifestError = ko.observable();
        this.annotationNodes = ko.observableArray();
        window.fetch(arches.urls.iiifannotationnodes)
            .then(function(response) {
                return response.json();
            })
            .then(function(json) {
                self.annotationNodes(
                    json.map(function(node) {
                        var annotations = ko.observableArray();
                        var updateAnnotations = function() {
                            var canvas = self.canvas();
                            if (canvas) {
                                window.fetch(arches.urls.iiifannotations + '?canvas=' + canvas + '&nodeid=' + node.nodeid)
                                    .then(function(response) {
                                        return response.json();
                                    })
                                    .then(function(json) {
                                        annotations(json.features);
                                    });
                            }
                        };
                        self.canvas.subscribe(updateAnnotations);
                        updateAnnotations();
                        return {
                            name: node['graph_name'] + ' - ' + node.name,
                            icon: node.icon,
                            active: ko.observable(false),
                            opacity: ko.observable(100),
                            annotations: annotations
                        };
                    })
                );
            });
        var annotationLayer = ko.computed(function() {
            var annotationFeatures = [];
            self.annotationNodes().forEach(function(node) {
                if (node.active()) {
                    var annotations = node.annotations();
                    if (params.tile && params.tile.resourceinstance_id) {
                        annotations = annotations.filter(function(annotation) {
                            return annotation.properties.resourceId !== params.tile.resourceinstance_id;
                        });
                    }
                    annotations.forEach(function(annotation) {
                        annotation.properties.opacityModifier = node.opacity();
                    });
                    annotationFeatures = annotationFeatures.concat(annotations);
                }
            });
            return L.geoJson({
                type: 'FeatureCollection',
                features: annotationFeatures
            }, {
                pointToLayer: function(feature, latlng) {
                    var modifier = feature.properties.opacityModifier / 100;
                    var style = {
                        color: feature.properties.color,
                        fillColor: feature.properties.fillColor,
                        weight: feature.properties.weight,
                        radius: feature.properties.radius,
                        opacity: (feature.properties.opacity * modifier),
                        fillOpacity: (feature.properties.fillOpacity * modifier)
                    };
                    return L.circleMarker(latlng, style);
                },
                style: function(feature) {
                    var modifier = feature.properties.opacityModifier / 100;
                    var style = {
                        color: feature.properties.color,
                        fillColor: feature.properties.fillColor,
                        weight: feature.properties.weight,
                        radius: feature.properties.radius,
                        opacity: (feature.properties.opacity * modifier),
                        fillOpacity: (feature.properties.fillOpacity * modifier)
                    };
                    return style;
                }
            });
        });
        var annotationFeatureGroup = new L.FeatureGroup();

        annotationLayer.subscribe(function(newAnnotationLayer) {
            var map = self.map();
            if (map) {
                annotationFeatureGroup.clearLayers();
                annotationFeatureGroup.addLayer(newAnnotationLayer);
            }
        });

        this.canvases = ko.pureComputed(function() {
            var manifestData = self.manifestData();
            var sequences = manifestData ? manifestData.sequences : [];
            var canvases = [];
            sequences.forEach(function(sequence) {
                if (sequence.canvases) {
                    sequence.label = getLabel(sequence);
                    sequence.canvases.forEach(function(canvas) {
                        canvas.label = getLabel(canvas);
                        if (typeof canvas.thumbnail === 'object')
                            canvas.thumbnail = canvas.thumbnail["@id"];
                        else if (canvas.images && canvas.images[0] && canvas.images[0].resource)
                            canvas.thumbnail = canvas.images[0].resource["@id"];
                        canvases.push(canvas);
                    });
                }
            });
            return canvases;
        });
        this.manifestName = ko.pureComputed(function() {
            var manifestData = self.manifestData();
            return getLabel(manifestData || {label: ''});
        });
        this.zoomToCanvas = !(params.zoom && params.center);

        var validateUrl = function(value) {
            return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(value);
        };

        var queryTerm;
        var limit = 10;
        this.manifestSelectConfig = {
            value: this.manifest,
            clickBubble: true,
            multiple: false,
            closeOnSlect: false,
            allowClear: true,
            ajax: {
                url: arches.urls.iiifmanifest,
                dataType: 'json',
                quietMillis: 250,
                data: function(term, page) {
                    var data = {
                        start: (page-1)*limit,
                        limit: limit
                    };
                    queryTerm = term;
                    if (term) data.query = term;
                    return data;
                },
                results: function(data, page) {
                    var results = data.results;
                    if (validateUrl(queryTerm)) results.unshift({
                        url: queryTerm,
                        label: queryTerm
                    });
                    return {
                        results: results,
                        more: data.count >= (page*limit)
                    };
                }
            },
            id: function(item) {
                return item.url;
            },
            formatResult: function(item) {
                return item.label;
            },
            formatSelection: function(item) {
                return item.label;
            },
            clear: function() {
                self.manifest('');
            },
            isEmpty: ko.computed(function() {
                return self.manifest() === '' || !self.manifest();
            }, this),
            initSelection: function() {
                return;
            }
        };

        this.getManifestData = function() {
            var manifestURL = self.manifest();
            if (manifestURL) {
                self.manifestLoading(true);
                self.manifestError(undefined);
                abortFetchManifest = new window.AbortController();
                window.fetch(manifestURL, {signal: abortFetchManifest.signal})
                    .then(function(response) {
                        return response.json();
                    })
                    .then(function(manifestData) {
                        self.manifestData(manifestData);
                        self.editManifest(false);
                    })
                    .catch(function(error) {
                        if (error.message !== "The user aborted a request.")
                            self.manifestError(error);
                    })
                    .finally(function() {
                        self.manifestLoading(false);
                        abortFetchManifest = undefined;
                    });
            }
        };
        this.getManifestData();

        WorkbenchViewmodel.apply(this, [params]);

        this.activeTab.subscribe(function() {
            var map = self.map();
            if (map) setTimeout(function() {
                map.invalidateSize();
            }, 1);
        });

        if (params.showGallery === undefined) params.showGallery = true;
        this.showGallery = ko.observable(params.showGallery);
        if (!params.manifest) params.expandGallery = true;
        this.expandGallery = ko.observable(params.expandGallery);
        this.expandGallery.subscribe(function(expandGallery) {
            if (expandGallery) self.showGallery(true);
        });
        this.showGallery.subscribe(function(showGallery) {
            if (!showGallery) self.expandGallery(false);
        });

        this.toggleGallery = function() {
            self.showGallery(!self.showGallery());
        };

        this.leafletConfig = {
            center: params.center || [0, 0],
            crs: L.CRS.Simple,
            zoom: params.zoom || 0,
            afterRender: this.map
        };

        var canvasLayer;
        this.brightness = ko.observable(100);
        this.contrast = ko.observable(100);
        this.saturation = ko.observable(100);
        this.greyscale = ko.observable(false);
        this.canvasFilter = ko.pureComputed(function() {
            var b = self.brightness() / 100;
            var c = self.contrast() / 100;
            var s = self.saturation() / 100;
            var g = self.greyscale() ? 1 : 0;
            return 'brightness(' + b + ') contrast(' + c + ') ' +
                'saturate(' + s + ') grayscale(' + g + ')';
        });
        var updateCanvasLayerFilter = function() {
            var filter = self.canvasFilter();
            var map = self.map();
            if (map) {
                map.getContainer().querySelector('.leaflet-tile-pane').style.filter = filter;
            }
        };
        this.canvasFilter.subscribe(updateCanvasLayerFilter);

        this.resetImageSettings = function() {
            self.brightness(100);
            self.contrast(100);
            self.saturation(100);
            self.greyscale(false);
        };

        var addCanvasLayer = function() {
            var map = self.map();
            var canvas = self.canvas();
            if (map && canvas) {
                if (canvasLayer) {
                    map.removeLayer(canvasLayer);
                    canvasLayer = undefined;
                }
                if (canvas) {
                    canvasLayer = L.tileLayer.iiif(canvas + '/info.json', {
                        fitBounds: self.zoomToCanvas
                    });
                    canvasLayer.addTo(map);
                    updateCanvasLayerFilter();
                }
            }
            self.zoomToCanvas = false;
        };
        this.map.subscribe(function(map) {
            L.control.fullscreen({
                fullscreenElement: $(map.getContainer()).closest('.workbench-card-wrapper')[0]
            }).addTo(map);
            addCanvasLayer();
            map.addLayer(annotationFeatureGroup);
        });
        this.canvas.subscribe(addCanvasLayer);

        this.selectCanvas = function(canvas) {
            var service = self.getCanvasService(canvas);
            self.zoomToCanvas = true;
            if (service) self.canvas(service);
        };

        this.canvasClick = function(canvas) {
            self.selectCanvas(canvas);
            self.expandGallery(false);
        };

        this.getCanvasService = function(canvas) {
            if (canvas.images.length > 0) return canvas.images[0].resource.service['@id'];
        };

        var updateCanvas = !self.canvas();
        this.manifestData.subscribe(function(manifestData) {
            if (updateCanvas && manifestData.sequences.length > 0) {
                var sequence = manifestData.sequences[0];
                if (sequence.canvases.length > 0) {
                    var canvas = sequence.canvases[0];
                    self.selectCanvas(canvas);
                }
            }
            updateCanvas = true;
        });

        this.toggleManifestEditor = function() {
            self.editManifest(!self.editManifest());
            if (abortFetchManifest) abortFetchManifest.abort();
        };

        this.getAnnotationCount = function() {
            return 0;
        };
    };
    ko.components.register('iiif-viewer', {
        viewModel: IIIFViewerViewmodel,
        template: {
            require: 'text!templates/views/components/iiif-viewer.htm'
        }
    });
    return IIIFViewerViewmodel;
});
<|endoftext|>


<commit_before><commit_msg>Two-dimensional array<commit_after>var grades = [[89, 77, 78], [76, 82, 81], [91, 94, 89]], total = 0, average = 0.0, row, col;
for (row = 0; row < grades.length; ++row){
	for(col = 0; col < grades[row].length; ++col){
		total += grades[row][col];
	}
	average = total/ grades[row].length;
	print("Student " + parseInt(row +1) + " average: " + average.toFixed(2));
	total = 0;
}
total = 0;
average = 0.0;
for(col = 0; col < grades.length; ++col){
	for(row = 0; row < grades[col].length; ++row){
		total += grades[row][col];
	}
	average = total /grades[col].length;
	print("Test " + parseInt(col+1) + " average: " + average.toFixed(2));
	total = 0;
	average = 0.0;
}<|endoftext|>