<commit_before>var $document = $(document),
    $source = $('#source');

var editorModes = {
  html: 'htmlmixed',
  javascript: 'javascript',
  css: 'css',
  markdown: 'markdown',
  coffeescript: 'coffeescript',
  less: 'css'
};

if (jsbin.settings.editor.tabMode === 'default') {
  CodeMirror.keyMap.basic.Tab = undefined;
} else if (jsbin.settings.editor.tabMode !== 'classic') {
  CodeMirror.keyMap.basic.Tab = 'indentMore';
}

var Panel = function (name, settings) {
  var panel = this,
      showPanelButton = true,
      $panel = null,
      splitterSettings = {},
      cmSettings = {},
      panelLanguage = name;

  panel.settings = settings = settings || {};
  panel.id = panel.name = name;
  $panel = $('.panel.' + name);
  $panel.data('name', name);
  panel.$el = $panel.detach();
  panel.$el.appendTo($source);
  panel.$el.wrapAll('
');
  panel.$el = panel.$el.parent().hide();
  panel.el = document.getElementById(name);
  panel.order = ++Panel.order;

  panel.$el.data('panel', panel);

  this._eventHandlers = {};

  // keyboard shortcut (set in keyboardcontrol.js)
  panelShortcuts[panelShortcuts.start + panel.order] = panel.id;

  if (panel.order === 1) {
    settings.nosplitter = true;
  }

  // this is nasty and wrong, but I'm going to put here anyway .i..
  if (this.id === 'javascript') {
    this.on('processor', function (e, preprocessor) {
      if (preprocessor === 'none') {
        jshintEnabled = true;
        checkForErrors();
      } else {
        jshintEnabled = false;
        $error.hide();
      }
    });
  }

  if (jsbin.state.processors && jsbin.state.processors[name]) {
    panelLanguage = jsbin.state.processors[name];
    jsbin.processors.set(panel, jsbin.state.processors[name]);
  } else if (settings.processor) { // FIXME is this even used?
    panelLanguage = settings.processors[settings.processor];
    jsbin.processors.set(panel, settings.processor);
  } else {
    panel.processor = function (str) { return str; };
  }

  if (settings.editor) {
    cmSettings = {
      parserfile: [],
      readOnly: jsbin.state.embed ? 'nocursor' : false,
      dragDrop: false, // we handle it ourselves
      mode: editorModes[panelLanguage],
      onChange: function (event) {
        $document.trigger('codeChange', [{ panelId: panel.id, revert: true }]);
        return true;
      },
      lineWrapping: true,
      theme: jsbin.settings.theme || 'jsbin'
    };

    $.extend(cmSettings, jsbin.settings.editor || {});
    panel.editor = CodeMirror.fromTextArea(panel.el, cmSettings);
    panel._setupEditor(panel.editor, name);
  }

  if (!settings.nosplitter) {
    panel.splitter = panel.$el.splitter(splitterSettings).data('splitter');
    panel.splitter.hide();
  } else {
    // create a fake splitter to let the rest of the code work
    panel.splitter = $();
  }

  if (settings.beforeRender) {
    $document.bind('render', $.proxy(settings.beforeRender, panel));
  }

  if (!settings.editor) {
    panel.ready = true;
  }

  // append panel to controls
  if (jsbin.state.embed) {
    // showPanelButton = window.location.search.indexOf(panel.id) !== -1;
  }

  if (showPanelButton) {
    this.controlButton = $('' + (settings.label || name) + '');
    this.controlButton.click(function () {
      panel.toggle();
      return false;
    });
    this.controlButton.appendTo('#panels');
  }

  $panel.focus(function () {
    panel.focus();
  });
  $panel.add(this.$el.find('.label')).click(function () {
    panel.focus();
  });
};

Panel.order = 0;

Panel.prototype = {
  virgin: true,
  visible: false,
  show: function (x) {
    // check to see if there's a panel to the left.
    // if there is, take it's size/2 and make this our
    // width
    var panel = this,
        panelCount = panel.$el.find('.panel').length;

    analytics.showPanel(panel.id);

    // panel.$el.show();
    if (panel.splitter.length) {
      if (panelCount === 0 || panelCount > 1) {
        var $panel = $('.panel.' + panel.id).show();
        // $panel.next().show(); // should be the splitter...
        $panel.closest('.panelwrapper').show();
      } else {
        panel.$el.show();
      }
      panel.splitter.show();
    } else {
      panel.$el.show();
    }

    $body.addClass('panelsVisible');

    if (panel.settings.show) {
      panel.settings.show.call(panel, true);
    }
    panel.controlButton.addClass('active');
    panel.visible = true;

    // update the splitter - but do it on the next tick
    // required to allow the splitter to see it's visible first
    setTimeout(function () {
      if (x !== undefined) {
        panel.splitter.trigger('init', x);
      } else {
        panel.distribute();
      }
      if (panel.editor) {
        // populate the panel for the first time
        if (panel.virgin) {
          $(panel.editor.win).find('.CodeMirror-scroll .CodeMirror-lines, .CodeMirror-gutter-text').css('padding-top', panel.$el.find('.label').outerHeight());

          populateEditor(panel, panel.name);
        }
        if (!panel.virgin || jsbin.panels.ready) {
          panel.editor.focus();
          panel.focus();
        }
      } else {
        panel.focus();
      }
      // update all splitter positions
      $document.trigger('sizeeditors');

      panel.trigger('show');

      panel.virgin = false;
  }, 0);

    // TODO save which panels are visible in their profile - but check whether it's their code
  },
  hide: function () {
    var panel = this;
    // panel.$el.hide();
    panel.visible = false;
    analytics.hidePanel(panel.id);

    // update all splitter positions
    // LOGIC: when you go to hide, you need to check if there's
    // other panels inside the panel wrapper - if there are
    // hide the nested panel and any previous visible splitter
    // if there's only one - then hide the whole thing.
    // if (panel.splitter.length) {
    var panelCount = panel.$el.find('.panel').length;
    if (panelCount === 0 || panelCount > 1) {
      var $panel = $('.panel.' + panel.id).hide();
      $panel.prev().hide(); // hide the splitter if there is one

      // TODO trigger a distribute horizontally
      if ($panel.closest('.panelwrapper').find('.panel:visible').length === 0) {
        $panel.closest('.panelwrapper').hide();
        // panel.splitter.hide();
        // TODO FIXME
      }
    } else {
      panel.$el.hide();
      panel.splitter.hide();
    }
    // } else {
    //   panel.$el.hide();
    // }
    if (panel.editor) {
      panel.controlButton.toggleClass('hasContent', !!$.trim(this.getCode()).length);
    }

    panel.controlButton.removeClass('active');
    panel.distribute();

    if (panel.settings.hide) {
      panel.settings.hide.call(panel, true);
    }

    // this.controlButton.show();
    setTimeout(function () {
      var visible = jsbin.panels.getVisible();
      if (visible.length) {
        jsbin.panels.focused = visible[0];
        if (jsbin.panels.focused.editor) {
          jsbin.panels.focused.editor.focus();
        } else {
          jsbin.panels.focused.$el.focus();
        }
        jsbin.panels.focused.focus();
      }

      $document.trigger('sizeeditors');
      panel.trigger('hide');
    }, 110);
  },
  toggle: function () {
    (this)[this.visible ? 'hide' : 'show']();
  },
  getCode: function () {
    if (this.editor) {
      return this.editor.getCode();
    }
  },
  setCode: function (content) {
    if (this.editor) {
      if (content === undefined) content = '';
      this.controlButton.toggleClass('hasContent', !!$.trim(content).length);
      this.codeSet = true;
      this.editor.setCode(content);
    }
  },
  codeSet: false,
  focus: function () {
    jsbin.panels.focus(this);
  },
  render: function () {
    var panel = this,
        ret = null;
    if (panel.editor) {
      return panel.processor(panel.getCode());
    } else if (this.visible && this.settings.render) {
      if (jsbin.panels.ready) {
        this.settings.render.apply(this, arguments);
      }
    }
  },
  init: function () {
    if (this.settings.init) this.settings.init.call(this);
  },
  _setupEditor: function () {
    var focusedPanel = sessionStorage.getItem('panel'),
        panel = this,
        editor = panel.editor;

    // overhang from CodeMirror1
    editor.setCode = function (str) {
      //Cannot call method 'chunkSize' of undefined
      try {
        editor.setValue(str);
      } catch(err) {
        // console.error(panel.id, err);
      }
    };

    editor.getCode = function () {
      return editor.getValue();
    };

    editor.currentLine = function () {
      var pos = editor.getCursor();
      return pos.line;
    };

    // editor.setOption('onKeyEvent', keycontrol);
    // editor.setOption('onFocus', function () {
      // panel.$el.trigger('focus');
    // });

    if (jsbin.mobile || jsbin.tablet) {
      editor._focus = editor.focus;
      editor.focus = function () {
        // console.log('ignoring manual call');
      };
    }

    editor.id = panel.name;

    editor.win = editor.getWrapperElement();
    editor.scroller = $(editor.getScrollerElement());

    $(editor.win).click(function () {
      editor.focus();
      panel.focus();
    });

    var $label = panel.$el.find('.label');
    if (document.body.className.indexOf('ie6') === -1 && $label.length) {
      editor.setOption('onScroll', function (event) {
        var scrollInfo = editor.getScrollInfo();
        if (scrollInfo.y > 10) {
          $label.stop().animate({ opacity: 0 }, 20, function () {
            $(this).hide();
          });
        } else {
          $label.show().stop().animate({ opacity: 1 }, 150);
        }
      });
    }

    var $error = null;
    $document.bind('sizeeditors', function () {
      if (panel.visible) {
        var height = panel.editor.scroller.closest('.panel').outerHeight(),
            offset = 0;
            // offset = panel.$el.find('> .label').outerHeight();

        // special case for the javascript panel
        if (panel.name === 'javascript') {
          if ($error === null) { // it wasn't there right away, so we populate
            $error = panel.$el.find('details');
          }
          offset += ($error.filter(':visible').height() || 0);
        }

        editor.scroller.height(height - offset);
        try { editor.refresh(); } catch (e) {}
      }
    });

    // required because the populate looks at the height, and at
    // this point in the code, the editor isn't visible, the browser
    // needs one more tick and it'll be there.
    setTimeout(function () {
      // if the panel isn't visible this only has the effect of putting
      // the code in the textarea (though probably costs us a lot more)
      // it has to be re-populated upon show for the first time because
      // it appears that CM2 uses the visible height to work out what
      // should be shown.
      panel.ready = true;
      populateEditor(panel, panel.name);

      if (focusedPanel == panel.name) {
        if (panel.visible) {
          // another fracking timeout to avoid conflict with other panels firing up
          setTimeout(function () {
            if (!jsbin.mobile && !jsbin.tablet) {
              editor.focus();
              editor.setCursor({ line: (sessionStorage.getItem('line') || 0) * 1, ch: (sessionStorage.getItem('character') || 0) * 1 });
            }
            panel.focus();
          }, 0);
        }
      }
    }, 0);
  },
  populateEditor: function () {
    populateEditor(this, this.name);
  },

  // events
  on: function (event, fn) {
    (this._eventHandlers[event] = this._eventHandlers[event] || []).push(fn);
    return this;
  },

  trigger: function (event) {
    var args = [].slice.call(arguments, 1);
    args.unshift({ type: event });
    for (var list = this._eventHandlers[event], i = 0; list && list[i];) {
      list[i++].apply(this, args);
    }
    return this;
  }
};

function populateEditor(editor, panel) {
  if (!editor.codeSet) {
    // populate - should eventually use: session, saved data, local storage
    var cached = sessionStorage.getItem('jsbin.content.' + panel), // session code
        saved = localStorage.getItem('saved-' + panel), // user template
        sessionURL = sessionStorage.getItem('url'),
        changed = false;

    // if we clone the bin, there will be a checksum on the state object
    // which means we happily have write access to the bin
    if (sessionURL !== template.url && !jsbin.state.checksum) {
      // nuke the live saving checksum
      sessionStorage.removeItem('checksum');
      saveChecksum = false;
    }

    if (template && cached == template[panel]) { // restored from original saved
      editor.setCode(cached);
    } else if (cached && sessionURL == template.url) { // try to restore the session first - only if it matches this url
      editor.setCode(cached);
      // tell the document that it's currently being edited, but check that it doesn't match the saved template
      // because sessionStorage gets set on a reload
      changed = cached != saved && cached != template[panel];
    } else if (saved !== null && !/edit/.test(window.location) && !window.location.search) { // then their saved preference
      editor.setCode(saved);
    } else { // otherwise fall back on the JS Bin default
      editor.setCode(template[panel]);
    }
  } else {
    // this means it was set via the url
    changed = true;
  }

  if (changed) {
    $document.trigger('codeChange', [ { revert: false, onload: true } ]);
  }
}
<commit_msg>Removed timeout on hiding panel<commit_after>var $document = $(document),
    $source = $('#source');

var editorModes = {
  html: 'htmlmixed',
  javascript: 'javascript',
  css: 'css',
  markdown: 'markdown',
  coffeescript: 'coffeescript',
  less: 'css'
};

if (jsbin.settings.editor.tabMode === 'default') {
  CodeMirror.keyMap.basic.Tab = undefined;
} else if (jsbin.settings.editor.tabMode !== 'classic') {
  CodeMirror.keyMap.basic.Tab = 'indentMore';
}

var Panel = function (name, settings) {
  var panel = this,
      showPanelButton = true,
      $panel = null,
      splitterSettings = {},
      cmSettings = {},
      panelLanguage = name;

  panel.settings = settings = settings || {};
  panel.id = panel.name = name;
  $panel = $('.panel.' + name);
  $panel.data('name', name);
  panel.$el = $panel.detach();
  panel.$el.appendTo($source);
  panel.$el.wrapAll('
');
  panel.$el = panel.$el.parent().hide();
  panel.el = document.getElementById(name);
  panel.order = ++Panel.order;

  panel.$el.data('panel', panel);

  this._eventHandlers = {};

  // keyboard shortcut (set in keyboardcontrol.js)
  panelShortcuts[panelShortcuts.start + panel.order] = panel.id;

  if (panel.order === 1) {
    settings.nosplitter = true;
  }

  // this is nasty and wrong, but I'm going to put here anyway .i..
  if (this.id === 'javascript') {
    this.on('processor', function (e, preprocessor) {
      if (preprocessor === 'none') {
        jshintEnabled = true;
        checkForErrors();
      } else {
        jshintEnabled = false;
        $error.hide();
      }
    });
  }

  if (jsbin.state.processors && jsbin.state.processors[name]) {
    panelLanguage = jsbin.state.processors[name];
    jsbin.processors.set(panel, jsbin.state.processors[name]);
  } else if (settings.processor) { // FIXME is this even used?
    panelLanguage = settings.processors[settings.processor];
    jsbin.processors.set(panel, settings.processor);
  } else {
    panel.processor = function (str) { return str; };
  }

  if (settings.editor) {
    cmSettings = {
      parserfile: [],
      readOnly: jsbin.state.embed ? 'nocursor' : false,
      dragDrop: false, // we handle it ourselves
      mode: editorModes[panelLanguage],
      onChange: function (event) {
        $document.trigger('codeChange', [{ panelId: panel.id, revert: true }]);
        return true;
      },
      lineWrapping: true,
      theme: jsbin.settings.theme || 'jsbin'
    };

    $.extend(cmSettings, jsbin.settings.editor || {});
    panel.editor = CodeMirror.fromTextArea(panel.el, cmSettings);
    panel._setupEditor(panel.editor, name);
  }

  if (!settings.nosplitter) {
    panel.splitter = panel.$el.splitter(splitterSettings).data('splitter');
    panel.splitter.hide();
  } else {
    // create a fake splitter to let the rest of the code work
    panel.splitter = $();
  }

  if (settings.beforeRender) {
    $document.bind('render', $.proxy(settings.beforeRender, panel));
  }

  if (!settings.editor) {
    panel.ready = true;
  }

  // append panel to controls
  if (jsbin.state.embed) {
    // showPanelButton = window.location.search.indexOf(panel.id) !== -1;
  }

  if (showPanelButton) {
    this.controlButton = $('' + (settings.label || name) + '');
    this.controlButton.click(function () {
      panel.toggle();
      return false;
    });
    this.controlButton.appendTo('#panels');
  }

  $panel.focus(function () {
    panel.focus();
  });
  $panel.add(this.$el.find('.label')).click(function () {
    panel.focus();
  });
};

Panel.order = 0;

Panel.prototype = {
  virgin: true,
  visible: false,
  show: function (x) {
    // check to see if there's a panel to the left.
    // if there is, take it's size/2 and make this our
    // width
    var panel = this,
        panelCount = panel.$el.find('.panel').length;

    analytics.showPanel(panel.id);

    // panel.$el.show();
    if (panel.splitter.length) {
      if (panelCount === 0 || panelCount > 1) {
        var $panel = $('.panel.' + panel.id).show();
        // $panel.next().show(); // should be the splitter...
        $panel.closest('.panelwrapper').show();
      } else {
        panel.$el.show();
      }
      panel.splitter.show();
    } else {
      panel.$el.show();
    }

    $body.addClass('panelsVisible');

    if (panel.settings.show) {
      panel.settings.show.call(panel, true);
    }
    panel.controlButton.addClass('active');
    panel.visible = true;

    // update the splitter - but do it on the next tick
    // required to allow the splitter to see it's visible first
    setTimeout(function () {
      if (x !== undefined) {
        panel.splitter.trigger('init', x);
      } else {
        panel.distribute();
      }
      if (panel.editor) {
        // populate the panel for the first time
        if (panel.virgin) {
          $(panel.editor.win).find('.CodeMirror-scroll .CodeMirror-lines, .CodeMirror-gutter-text').css('padding-top', panel.$el.find('.label').outerHeight());

          populateEditor(panel, panel.name);
        }
        if (!panel.virgin || jsbin.panels.ready) {
          panel.editor.focus();
          panel.focus();
        }
      } else {
        panel.focus();
      }
      // update all splitter positions
      $document.trigger('sizeeditors');

      panel.trigger('show');

      panel.virgin = false;
  }, 0);

    // TODO save which panels are visible in their profile - but check whether it's their code
  },
  hide: function () {
    var panel = this;
    // panel.$el.hide();
    panel.visible = false;
    analytics.hidePanel(panel.id);

    // update all splitter positions
    // LOGIC: when you go to hide, you need to check if there's
    // other panels inside the panel wrapper - if there are
    // hide the nested panel and any previous visible splitter
    // if there's only one - then hide the whole thing.
    // if (panel.splitter.length) {
    var panelCount = panel.$el.find('.panel').length;
    if (panelCount === 0 || panelCount > 1) {
      var $panel = $('.panel.' + panel.id).hide();
      $panel.prev().hide(); // hide the splitter if there is one

      // TODO trigger a distribute horizontally
      if ($panel.closest('.panelwrapper').find('.panel:visible').length === 0) {
        $panel.closest('.panelwrapper').hide();
        // panel.splitter.hide();
        // TODO FIXME
      }
    } else {
      panel.$el.hide();
      panel.splitter.hide();
    }
    // } else {
    //   panel.$el.hide();
    // }
    if (panel.editor) {
      panel.controlButton.toggleClass('hasContent', !!$.trim(this.getCode()).length);
    }

    panel.controlButton.removeClass('active');
    panel.distribute();

    if (panel.settings.hide) {
      panel.settings.hide.call(panel, true);
    }

    // this.controlButton.show();
    // setTimeout(function () {
    var visible = jsbin.panels.getVisible();
    if (visible.length) {
      jsbin.panels.focused = visible[0];
      if (jsbin.panels.focused.editor) {
        jsbin.panels.focused.editor.focus();
      } else {
        jsbin.panels.focused.$el.focus();
      }
      jsbin.panels.focused.focus();
    }

    $document.trigger('sizeeditors');
    panel.trigger('hide');
    // }, 110);
  },
  toggle: function () {
    (this)[this.visible ? 'hide' : 'show']();
  },
  getCode: function () {
    if (this.editor) {
      return this.editor.getCode();
    }
  },
  setCode: function (content) {
    if (this.editor) {
      if (content === undefined) content = '';
      this.controlButton.toggleClass('hasContent', !!$.trim(content).length);
      this.codeSet = true;
      this.editor.setCode(content);
    }
  },
  codeSet: false,
  focus: function () {
    jsbin.panels.focus(this);
  },
  render: function () {
    var panel = this,
        ret = null;
    if (panel.editor) {
      return panel.processor(panel.getCode());
    } else if (this.visible && this.settings.render) {
      if (jsbin.panels.ready) {
        this.settings.render.apply(this, arguments);
      }
    }
  },
  init: function () {
    if (this.settings.init) this.settings.init.call(this);
  },
  _setupEditor: function () {
    var focusedPanel = sessionStorage.getItem('panel'),
        panel = this,
        editor = panel.editor;

    // overhang from CodeMirror1
    editor.setCode = function (str) {
      //Cannot call method 'chunkSize' of undefined
      try {
        editor.setValue(str);
      } catch(err) {
        // console.error(panel.id, err);
      }
    };

    editor.getCode = function () {
      return editor.getValue();
    };

    editor.currentLine = function () {
      var pos = editor.getCursor();
      return pos.line;
    };

    // editor.setOption('onKeyEvent', keycontrol);
    // editor.setOption('onFocus', function () {
      // panel.$el.trigger('focus');
    // });

    if (jsbin.mobile || jsbin.tablet) {
      editor._focus = editor.focus;
      editor.focus = function () {
        // console.log('ignoring manual call');
      };
    }

    editor.id = panel.name;

    editor.win = editor.getWrapperElement();
    editor.scroller = $(editor.getScrollerElement());

    $(editor.win).click(function () {
      editor.focus();
      panel.focus();
    });

    var $label = panel.$el.find('.label');
    if (document.body.className.indexOf('ie6') === -1 && $label.length) {
      editor.setOption('onScroll', function (event) {
        var scrollInfo = editor.getScrollInfo();
        if (scrollInfo.y > 10) {
          $label.stop().animate({ opacity: 0 }, 20, function () {
            $(this).hide();
          });
        } else {
          $label.show().stop().animate({ opacity: 1 }, 150);
        }
      });
    }

    var $error = null;
    $document.bind('sizeeditors', function () {
      if (panel.visible) {
        var height = panel.editor.scroller.closest('.panel').outerHeight(),
            offset = 0;
            // offset = panel.$el.find('> .label').outerHeight();

        // special case for the javascript panel
        if (panel.name === 'javascript') {
          if ($error === null) { // it wasn't there right away, so we populate
            $error = panel.$el.find('details');
          }
          offset += ($error.filter(':visible').height() || 0);
        }

        editor.scroller.height(height - offset);
        try { editor.refresh(); } catch (e) {}
      }
    });

    // required because the populate looks at the height, and at
    // this point in the code, the editor isn't visible, the browser
    // needs one more tick and it'll be there.
    setTimeout(function () {
      // if the panel isn't visible this only has the effect of putting
      // the code in the textarea (though probably costs us a lot more)
      // it has to be re-populated upon show for the first time because
      // it appears that CM2 uses the visible height to work out what
      // should be shown.
      panel.ready = true;
      populateEditor(panel, panel.name);

      if (focusedPanel == panel.name) {
        if (panel.visible) {
          // another fracking timeout to avoid conflict with other panels firing up
          setTimeout(function () {
            if (!jsbin.mobile && !jsbin.tablet) {
              editor.focus();
              editor.setCursor({ line: (sessionStorage.getItem('line') || 0) * 1, ch: (sessionStorage.getItem('character') || 0) * 1 });
            }
            panel.focus();
          }, 0);
        }
      }
    }, 0);
  },
  populateEditor: function () {
    populateEditor(this, this.name);
  },

  // events
  on: function (event, fn) {
    (this._eventHandlers[event] = this._eventHandlers[event] || []).push(fn);
    return this;
  },

  trigger: function (event) {
    var args = [].slice.call(arguments, 1);
    args.unshift({ type: event });
    for (var list = this._eventHandlers[event], i = 0; list && list[i];) {
      list[i++].apply(this, args);
    }
    return this;
  }
};

function populateEditor(editor, panel) {
  if (!editor.codeSet) {
    // populate - should eventually use: session, saved data, local storage
    var cached = sessionStorage.getItem('jsbin.content.' + panel), // session code
        saved = localStorage.getItem('saved-' + panel), // user template
        sessionURL = sessionStorage.getItem('url'),
        changed = false;

    // if we clone the bin, there will be a checksum on the state object
    // which means we happily have write access to the bin
    if (sessionURL !== template.url && !jsbin.state.checksum) {
      // nuke the live saving checksum
      sessionStorage.removeItem('checksum');
      saveChecksum = false;
    }

    if (template && cached == template[panel]) { // restored from original saved
      editor.setCode(cached);
    } else if (cached && sessionURL == template.url) { // try to restore the session first - only if it matches this url
      editor.setCode(cached);
      // tell the document that it's currently being edited, but check that it doesn't match the saved template
      // because sessionStorage gets set on a reload
      changed = cached != saved && cached != template[panel];
    } else if (saved !== null && !/edit/.test(window.location) && !window.location.search) { // then their saved preference
      editor.setCode(saved);
    } else { // otherwise fall back on the JS Bin default
      editor.setCode(template[panel]);
    }
  } else {
    // this means it was set via the url
    changed = true;
  }

  if (changed) {
    $document.trigger('codeChange', [ { revert: false, onload: true } ]);
  }
}
<|endoftext|>


<commit_before>(function() {
  var treeBrownMean = {r: 71.0, g: 37.0, b: 24.0};
  var treeBrownStDev = {r: 16.0, g: 4.0, b: 8.0};

  var branchColour = function(depth) {
    var rgb = ['r', 'g', 'b'].map(function(colour) {
      return Math.floor(chance.normal({mean: treeBrownMean[colour], dev: treeBrownStDev[colour]}));
    })
    return 'rgb(' + rgb.join(', ') + ')'
  }

  var branchThickness = function(depth) {
    return 1.5 * depth;
  }

  var branchLength = function(depth) {
    return depth * chance.normal({mean: 8.0, dev: 3.0});
  }

  var branchAngle = function(depth) {
    return chance.normal({mean: Math.PI / 9.0, dev: Math.PI / 18.0});
  }

  var treeDepth = function() {
    return Math.floor(chance.normal({mean: 9.0, dev: 1.0}));
  }

  var drawBranch = function(x1, y1, x2, y2, styles){
    Utils.extend(ctx, styles);
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.closePath()
    ctx.stroke()
  }

  var drawTree = function(x1, y1, angle1, depth, styles){
    if (depth === 0) return;
    styles = Utils.merge(styles, {lineWidth: branchThickness(depth)});
    var length = branchLength(depth);
    var x2 = x1 + (Math.cos(angle1) * length);
    var y2 = y1 + (Math.sin(angle1) * length);
    drawBranch(x1, y1, x2, y2, styles);
    drawTree(x2, y2, angle1 - branchAngle(depth), depth - 1, styles);
    drawTree(x2, y2, angle1 + branchAngle(depth), depth - 1, styles);
  }

  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  ctx.canvas.width  = window.innerWidth;
  ctx.canvas.height = window.innerHeight;

  canvas.addEventListener('click', function(e) {
    drawTree(e.clientX, e.clientY, -Math.PI/2, treeDepth(), {strokeStyle: branchColour()});
    return true;
  });

})();
<commit_msg>Animated tree growth<commit_after>(function() {
  var treeBrownMean = {r: 71.0, g: 37.0, b: 24.0};
  var treeBrownStDev = {r: 16.0, g: 4.0, b: 8.0};

  var branchColour = function(depth) {
    var rgb = ['r', 'g', 'b'].map(function(colour) {
      return Math.floor(chance.normal({mean: treeBrownMean[colour], dev: treeBrownStDev[colour]}));
    })
    return 'rgb(' + rgb.join(', ') + ')'
  }

  var branchThickness = function(depth) {
    return 1.5 * depth;
  }

  var branchLength = function(depth) {
    return depth * chance.normal({mean: 8.0, dev: 3.0});
  }

  var branchAngle = function(depth) {
    return chance.normal({mean: Math.PI / 9.0, dev: Math.PI / 18.0});
  }

  var treeDepth = function() {
    return Math.floor(chance.normal({mean: 9.0, dev: 1.0}));
  }

  var drawBranch = function(x1, y1, x2, y2, styles){
    Utils.extend(ctx, styles);
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.closePath()
    ctx.stroke()
  }

  var drawTree = function(x1, y1, angle1, depth, styles){
    if (depth === 0) return;

    styles = Utils.merge(styles, {lineWidth: branchThickness(depth)});
    var length = branchLength(depth);
    var startTime = null;
    var growTime = 1000.0;

    var cosAngle = Math.cos(angle1)
    var sinAngle = Math.sin(angle1)

    var step = function(timestamp) {
      if (startTime === null) startTime = timestamp;
      var currentTime = (timestamp - startTime);
      var currentLength = length * (currentTime / growTime);
      var x2 = x1 + (cosAngle * currentLength);
      var y2 = y1 + (sinAngle * currentLength);

      if (currentLength < length) {
        drawBranch(x1, y1, x2, y2, styles);
        window.requestAnimationFrame(step);
      } else {
        drawTree(x2, y2, angle1 - branchAngle(depth), depth - 1, styles);
        drawTree(x2, y2, angle1 + branchAngle(depth), depth - 1, styles);
      }
    }

    window.requestAnimationFrame(step);
  }

  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  ctx.canvas.width  = window.innerWidth;
  ctx.canvas.height = window.innerHeight;

  canvas.addEventListener('click', function(e) {
    drawTree(e.clientX, e.clientY, -Math.PI/2, treeDepth(), {strokeStyle: branchColour()});
    return true;
  });

})();
<|endoftext|>


<commit_before>(function() {
  var treeBrownMean = {r: 64.0, g: 40.0, b: 24.0};
  var treeBrownStDev = {r: 12.0, g: 4.0, b: 8.0};

  var leafGreenMean = {r: 96.0, g: 112.0, b: 56.0};
  var leafGreenStDev = {r: 4.0, g: 12.0, b: 4.0};

  var randomColour = function(meanColour, stDevColour) {
    var rgb = ['r', 'g', 'b'].map(function(colour) {
      return Math.floor(chance.normal({mean: meanColour[colour], dev: stDevColour[colour]}));
    })
    return 'rgb(' + rgb.join(', ') + ')'
  }

  var branchColour = function(depth) {
    return randomColour(treeBrownMean, treeBrownStDev);
  }

  var branchThickness = function(depth) {
    return 1.5 * depth;
  }

  var branchLength = function(depth) {
    return depth * chance.normal({mean: 8.0, dev: 3.0});
  }

  var branchAngle = function(depth) {
    return chance.normal({mean: Math.PI / 9.0, dev: Math.PI / 18.0});
  }

  var branchGrowTime = function(depth) {
    return chance.normal({mean: 1000.0, dev: 100.0});
  }

  var treeDepth = function() {
    return Math.floor(chance.normal({mean: 9.0, dev: 1.0}));
  }

  var drawBranch = function(x1, y1, x2, y2, styles){
    Utils.extend(ctx, styles);
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.closePath()
    ctx.stroke()
  }

  var leavesPerBranch = function() {
    return chance.normal({mean: 4.0, dev: 2.0});
  }

  var leafLength = function() {
    return chance.normal({mean: 15.0, dev: 4.0});
  }

  var leafAngle = function() {
    return chance.normal({mean: 0.0, dev: (2.0 * Math.PI / 3.0)});
  }

  var leafColour = function(depth) {
    return randomColour(leafGreenMean, leafGreenStDev);
  }

  var drawLeaf = function(x1, y1, x2, y2, x3, y3, x4, y4, styles) {
    Utils.extend(ctx, styles);
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.quadraticCurveTo(x3, y3, x2, y2);
    ctx.moveTo(x2, y2);
    ctx.quadraticCurveTo(x4, y4, x1, y1);
    ctx.closePath();
    ctx.fill();
  }

  var drawTree = function(x1, y1, angle1, depth, styles){
    if (depth === 0) {
      styles = Utils.merge(styles, {fillStyle: leafColour()});

      var leafCount = leavesPerBranch();
      for(var i = 0; i < leafCount; i++) {
        var angle2 = leafAngle();
        var currentLeafAngle = angle1 + angle2;
        var length = leafLength();

        var x2 = x1 + (Math.cos(currentLeafAngle) * length);
        var y2 = y1 + (Math.sin(currentLeafAngle) * length);

        var angle3 = currentLeafAngle + (Math.PI / 4.0);
        var x3 = x1 + (Math.cos(angle3) * length / 3.0);
        var y3 = y1 + (Math.sin(angle3) * length / 3.0);

        var angle4 = currentLeafAngle - (Math.PI / 4.0);
        var x4 = x1 + (Math.cos(angle4) * length / 3.0);
        var y4 = y1 + (Math.sin(angle4) * length / 3.0);

        drawLeaf(x1, y1, x2, y2, x3, y3, x4, y4, styles)
      }
    } else {
      styles = Utils.merge(styles, {lineWidth: branchThickness(depth)});
      var length = branchLength(depth);
      var startTime = null;
      var growTime = branchGrowTime(depth);

      var cosAngle = Math.cos(angle1)
      var sinAngle = Math.sin(angle1)
      var x2 = x1;
      var y2 = y2;

      var step = function(timestamp) {
        if (startTime === null) startTime = timestamp;
        var currentTime = (timestamp - startTime);
        var currentLength = length * (currentTime / growTime);

        if (currentLength < length) {
          x2 = x1 + (cosAngle * currentLength);
          y2 = y1 + (sinAngle * currentLength);
          drawBranch(x1, y1, x2, y2, styles);
          window.requestAnimationFrame(step);
        } else {
          drawTree(x2, y2, angle1 - branchAngle(depth), depth - 1, styles);
          drawTree(x2, y2, angle1 + branchAngle(depth), depth - 1, styles);
        }
      }

      window.requestAnimationFrame(step);
    }
  }

  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  ctx.canvas.width  = window.innerWidth;
  ctx.canvas.height = window.innerHeight;

  canvas.addEventListener('click', function(e) {
    window.setTimeout(function() {
      drawTree(e.clientX, e.clientY, -Math.PI/2, treeDepth(), {strokeStyle: branchColour()});
    }, 200);
    return true;
  });

})();
<commit_msg>Made leaves less red<commit_after>(function() {
  var treeBrownMean = {r: 64.0, g: 40.0, b: 24.0};
  var treeBrownStDev = {r: 12.0, g: 4.0, b: 8.0};

  var leafGreenMean = {r: 80.0, g: 112.0, b: 48.0};
  var leafGreenStDev = {r: 4.0, g: 12.0, b: 4.0};

  var randomColour = function(meanColour, stDevColour) {
    var rgb = ['r', 'g', 'b'].map(function(colour) {
      return Math.floor(chance.normal({mean: meanColour[colour], dev: stDevColour[colour]}));
    })
    return 'rgb(' + rgb.join(', ') + ')'
  }

  var branchColour = function(depth) {
    return randomColour(treeBrownMean, treeBrownStDev);
  }

  var branchThickness = function(depth) {
    return 1.5 * depth;
  }

  var branchLength = function(depth) {
    return depth * chance.normal({mean: 8.0, dev: 3.0});
  }

  var branchAngle = function(depth) {
    return chance.normal({mean: Math.PI / 9.0, dev: Math.PI / 18.0});
  }

  var branchGrowTime = function(depth) {
    return chance.normal({mean: 1000.0, dev: 100.0});
  }

  var treeDepth = function() {
    return Math.floor(chance.normal({mean: 9.0, dev: 1.0}));
  }

  var drawBranch = function(x1, y1, x2, y2, styles){
    Utils.extend(ctx, styles);
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.closePath()
    ctx.stroke()
  }

  var leavesPerBranch = function() {
    return chance.normal({mean: 4.0, dev: 2.0});
  }

  var leafLength = function() {
    return chance.normal({mean: 15.0, dev: 4.0});
  }

  var leafAngle = function() {
    return chance.normal({mean: 0.0, dev: (2.0 * Math.PI / 3.0)});
  }

  var leafColour = function(depth) {
    return randomColour(leafGreenMean, leafGreenStDev);
  }

  var drawLeaf = function(x1, y1, x2, y2, x3, y3, x4, y4, styles) {
    Utils.extend(ctx, styles);
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.quadraticCurveTo(x3, y3, x2, y2);
    ctx.moveTo(x2, y2);
    ctx.quadraticCurveTo(x4, y4, x1, y1);
    ctx.closePath();
    ctx.fill();
  }

  var drawTree = function(x1, y1, angle1, depth, styles){
    if (depth === 0) {
      styles = Utils.merge(styles, {fillStyle: leafColour()});

      var leafCount = leavesPerBranch();
      for(var i = 0; i < leafCount; i++) {
        var angle2 = leafAngle();
        var currentLeafAngle = angle1 + angle2;
        var length = leafLength();

        var x2 = x1 + (Math.cos(currentLeafAngle) * length);
        var y2 = y1 + (Math.sin(currentLeafAngle) * length);

        var angle3 = currentLeafAngle + (Math.PI / 4.0);
        var x3 = x1 + (Math.cos(angle3) * length / 3.0);
        var y3 = y1 + (Math.sin(angle3) * length / 3.0);

        var angle4 = currentLeafAngle - (Math.PI / 4.0);
        var x4 = x1 + (Math.cos(angle4) * length / 3.0);
        var y4 = y1 + (Math.sin(angle4) * length / 3.0);

        drawLeaf(x1, y1, x2, y2, x3, y3, x4, y4, styles)
      }
    } else {
      styles = Utils.merge(styles, {lineWidth: branchThickness(depth)});
      var length = branchLength(depth);
      var startTime = null;
      var growTime = branchGrowTime(depth);

      var cosAngle = Math.cos(angle1)
      var sinAngle = Math.sin(angle1)
      var x2 = x1;
      var y2 = y2;

      var step = function(timestamp) {
        if (startTime === null) startTime = timestamp;
        var currentTime = (timestamp - startTime);
        var currentLength = length * (currentTime / growTime);

        if (currentLength < length) {
          x2 = x1 + (cosAngle * currentLength);
          y2 = y1 + (sinAngle * currentLength);
          drawBranch(x1, y1, x2, y2, styles);
          window.requestAnimationFrame(step);
        } else {
          drawTree(x2, y2, angle1 - branchAngle(depth), depth - 1, styles);
          drawTree(x2, y2, angle1 + branchAngle(depth), depth - 1, styles);
        }
      }

      window.requestAnimationFrame(step);
    }
  }

  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  ctx.canvas.width  = window.innerWidth;
  ctx.canvas.height = window.innerHeight;

  canvas.addEventListener('click', function(e) {
    window.setTimeout(function() {
      drawTree(e.clientX, e.clientY, -Math.PI/2, treeDepth(), {strokeStyle: branchColour()});
    }, 200);
    return true;
  });

})();
<|endoftext|>


<commit_before>import d3 from 'd3';
import _ from 'underscore';
import Grapher from './grapher';
import * as Helpers from './visualisation_helpers';

class Graph {

  static name() {
    return 'Matching sequences';
  }

  static className() {
    return 'alignment-overview';
  }

  static collapseId(props) {
    return 'alignment_'+props.query.number;
  }

  static dataName(props) {
      return 'Alignment-Overview-'+props.query.id;
  }

  constructor($svgContainer, props) {
      this.svg_container = $svgContainer;
      $queryDiv = $svgContainer.parents('.resultn');
      hits = this.extractData(props.query.hits, props.query.number);
      this.graphIt($queryDiv, $svgContainer, 0, 20, null, hits);
  }

  extractData(query_hits, number) {
    var hits = [];
    query_hits.map(function (hit) {
        var _hsps = [];
        var hsps = hit.hsps;
        _.each(hsps, function (hsp) {
            var _hsp = {};
            _hsp.hspEvalue = hsp.evalue;
            _hsp.hspStart = hsp.qstart;
            _hsp.hspEnd = hsp.qend;
            _hsp.hspFrame = hsp.sframe;
            _hsp.hspId = "Query_"+number+"_hit_"+hit.number+"_hsp_"+hsp.number;
            _hsps.push(_hsp);
        });
        _hsps.hitId = hit.id;
        _hsps.hitDef = "Query_"+number+"_hit_"+hit.number;
        _hsps.hitEvalue = hit.evalue;
        hits.push(_hsps);
    });
    return hits;
  }

  setupTooltip() {
      this.svg_container.find('[data-toggle="tooltip"]').tooltip({
          'placement': 'top', 'container': 'body', 'html': 'true',
          'delay': 0, 'white-space': 'nowrap'
      });
  }

  setupClick($graphDiv) {
    $('a', $graphDiv).click(function (evt) {
        evt.preventDefault();
        evt.stopPropagation();
        window.location.hash = $(this).attr('href');
    });
  }

  graphControls($queryDiv, $graphDiv, isInit, opts, hits) {
    var MIN_HITS_TO_SHOW = 20;

    var totalHits, shownHits, lessButton, moreButton;

    var countHits = function () {
        totalHits = hits.length;
        shownHits = $queryDiv.find('.ghit > g').length;
    };

    var setupButtons = function($queryDiv, $graphDiv) {
        $graphDiv
        .append(
            $('<button/>')
            .addClass('btn btn-link more')
            .attr('type', 'button')
            .attr('data-parent-query', $queryDiv.attr('id'))
            .html('View More ')
            .append(
                $('')
                .html('  ')
                .addClass('fa fa-angle-double-down')
            ),
            $('<button/>')
            .addClass('btn btn-link less')
            .attr('type', 'button')
            .attr('data-parent-query', $queryDiv.attr('id'))
            .html('View Less ')
            .append(
                $('')
                .html('  ')
                .addClass('fa fa-angle-double-up')
            )
        );

        lessButton = $('.less', $graphDiv);
        moreButton = $('.more', $graphDiv);
    };

    var initButtons = function () {
        countHits();
        if (totalHits === MIN_HITS_TO_SHOW ||
            shownHits < MIN_HITS_TO_SHOW) {
            lessButton.hide();
            moreButton.hide();
        }
        else if (shownHits === totalHits) {
            moreButton.hide();
            lessButton.show();
        }
        else if (shownHits === MIN_HITS_TO_SHOW) {
            lessButton.hide();
            moreButton.show();
        }
        else {
            lessButton.show();
            moreButton.show();
        }
    };

    // Setup view buttons' state properly if called for first time.
    if (isInit === true) {
        setupButtons($queryDiv, $graphDiv);
        initButtons();
    }

    moreButton.on('click', _.bind(function (e) {
        countHits();
        this.graphIt($queryDiv, $graphDiv, shownHits, MIN_HITS_TO_SHOW, opts, hits);
        initButtons();
        this.setupTooltip();
        e.stopPropagation();
    },this));

    lessButton.on('click', _.bind(function (e) {
        countHits();
        var diff = shownHits - MIN_HITS_TO_SHOW;

        // Decrease number of shown hits by defined constant.
        if (diff >= MIN_HITS_TO_SHOW) {
            this.graphIt($queryDiv, $graphDiv, shownHits, -MIN_HITS_TO_SHOW, opts, hits);
            initButtons();
        }
        else if (diff !== 0) {
            // Ensure a certain number of hits always stay in graph.
            this.graphIt($queryDiv, $graphDiv, shownHits, MIN_HITS_TO_SHOW - shownHits, opts, hits);
            initButtons();
        }
        this.setupTooltip();
        e.stopPropagation();
    },this));
  }

  drawLegend(svg, options, width, height, hits) {
    var svg_legend = svg.append('g')
        .attr('transform',
            'translate(0,' + (height - 1.88 * options.margin) + ')');

    svg_legend.append('rect')
        .attr('x', 7 * (width - 2 * options.margin) / 10)
        .attr('width', 2 * (width - 4 * options.margin) / 10)
        .attr('height', options.legend)
        .attr('fill', 'url(#legend-grad)');

    svg_legend.append('text')
        .attr('class',' legend-text')
        .attr('transform', 'translate(0, ' +options.legend +')')
        .attr('x', 6 * (width - 2 * options.margin) / 10 - options.margin / 2)
        .text("Weaker hits");
        // .text(function() {
        //   return Helpers.prettify_evalue(hits[hits.length-1].hitEvalue);
        // })

    svg_legend.append('text')
        .attr('class',' legend-text')
        .attr('transform', 'translate(0, ' + options.legend + ')')
        .attr('x', 9 * (width - 2 * options.margin) / 10 + options.margin / 2)
        .text("Stronger hits");
        // .text(function () {
        //   return Helpers.prettify_evalue(hits[0].hitEvalue);
        // })

    svg.append('linearGradient')
        .attr('id', 'legend-grad')
    .selectAll('stop')
    .data([
        {offset: "0%", color: "#ccc"},
        {offset: '50%', color: '#888'},
        {offset: "100%", color: "#000"}
    ])
    .enter()
    .append('stop')
        .attr('offset', function (d) {
            return d.offset;
        })
        .attr('stop-color', function (d) {
            return d.color;
        });
  }

  graphIt($queryDiv, $graphDiv, index, howMany, opts, inhits) {
    /* barHeight: Height of each hit track.
     * barPadding: Padding around each hit track.
     * legend: Height reserved for the overview legend.
     * margin: Margin around the svg element.
     */
    var defaults = {
        barHeight: 2,
        barPadding: 1,
        legend: inhits.length > 1 ? 5 : 0,
        margin: 20
    },
        options = $.extend(defaults, opts);
        var hits = inhits.slice(0 , index + howMany);

    // Don't draw anything when no hits are obtained.
    if (hits.length < 1) return false;

    if (index !== 0) {
        // Currently, we have no good way to extend pre-existing graph
        // and hence, are removing the old one and redrawing.
        $graphDiv.find('svg').remove();
    }

    var queryLen = $queryDiv.data().queryLen;
    var q_i = $queryDiv.attr('id');

    var width = $graphDiv.width();
    var height = hits.length * (options.barHeight + options.barPadding) +
        2 * options.legend + 5 * options.margin;
    // var height = $graphDiv.height();

    var SEQ_TYPES = {
        blastn: 'nucleic_acid',
        blastp: 'amino_acid',
        blastx: 'nucleic_acid',
        tblastx: 'nucleic_acid',
        tblastn: 'amino_acid'
    };

    var svg = d3.select($graphDiv[0])
        .selectAll('svg')
        .data([hits])
        .enter()
        .insert('svg', ':first-child')
            .attr('width', width)
            .attr('height', height)
        .append('g')
            .attr('transform', 'translate(' + options.margin / 2 + ', ' + (1.5 * options.margin) + ')');

    var x = d3.scale
        .linear()
        .range([0, width - options.margin]);

    x.domain([1, queryLen]);

    var algorithm = $queryDiv.data().algorithm;
    var formatter = Helpers.tick_formatter(x, SEQ_TYPES[algorithm]);

    var _tValues = x.ticks(11);
    _tValues.pop();

    var xAxis = d3.svg
        .axis()
        .scale(x)
        .orient('top')
        .tickValues(_tValues.concat([1, queryLen]))
        .tickFormat(formatter);

    // Attach the axis to DOM (<svg> element)
    var container = svg.append('g')
        .attr('transform', 'translate(0, ' + options.margin + ')')
        .append('g')
            .attr('class', 'x axis')
            .call(xAxis);

    // Vertical alignment of ticks
    container.selectAll('text')
            .attr('x','25px')
            .attr('y','2px')
            .attr('transform','rotate(-90)');

    var y = d3.scale
        .ordinal()
        .rangeBands([0, height - 3 * options.margin - 2 * options.legend], 0.3);

    y.domain(hits.map(function (d) {
        return d.hitId;
    }));

    var gradScale = d3.scale
        .log()
        .domain([
            d3.min([1e-5, d3.min(hits.map(function (d) {
                if (parseFloat(d.hitEvalue) === 0.0) return undefined;
                return d.hitEvalue;
                }))
            ]),
            d3.max(hits.map(function (d) {
                return d.hitEvalue;
            }))
        ])
        .range([40,150]);

    svg.append('g')
        .attr('class', 'ghit')
        .attr('transform', 'translate(0, ' + 1.65 * (options.margin - options.legend) + ')')
        .selectAll('.hits')
        .data(hits)
        .enter()
        .append('g')
            .each(function (d,i) {
                // TODO: Avoid too many variables and improve naming.

                d3.select(this)
                .selectAll('.hsp')
                .data(d).enter()
                .append('a')
                .each(function (v, j) {
                    // Drawing the HSPs connector line using the same
                    // color as that of the hit track (using lookahead).
                    var yHspline = y(d.hitId) + options.barHeight / 2;
                    var hsplineColor = d3.rgb(gradScale(v.hspEvalue),
                                              gradScale(v.hspEvalue),
                                              gradScale(v.hspEvalue));

                    if (j+1 < d.length) {
                        if (d[j].hspEnd <= d[j+1].hspStart) {
                            d3.select(this.parentNode)
                            .append('line')
                                .attr('x1', x(d[j].hspEnd))
                                .attr('y1', yHspline)
                                .attr('x2', x(d[j+1].hspStart))
                                .attr('y2', yHspline)
                                .attr('stroke', hsplineColor);
                        }
                        else if (d[j].hspStart > d[j+1].hspEnd) {
                            d3.select(this.parentNode)
                            .append('line')
                                .attr('x1', x(d[j+1].hspEnd))
                                .attr('y1', yHspline)
                                .attr('x2', x(d[j].hspStart))
                                .attr('y2', yHspline)
                                .attr('stroke', hsplineColor);
                        }
                    }

                    // Draw the rectangular hit tracks itself.
                    d3.select(this)
                        .attr('xlink:href', '#' + q_i + '_hit_' + (i+1))
                        .append('rect')
                            .attr('data-toggle', 'tooltip')
                            .attr('title', d.hitId + '
E value: '+Helpers.prettify_evalue(v.hspEvalue))
                            .attr('class','bar')
                            .attr('x', function (d) {
                                return x(d.hspStart);
                            })
                            .attr('y', y(d.hitId))
                            .attr('width', function (d) {
                                return x(d.hspEnd - d.hspStart + 1);
                            })
                            .attr('height', options.barHeight)
                            .attr('fill', d3.rgb(hsplineColor));
                });
            });

    // Draw legend only when more than one hit present
    if (hits.length > 1) {
        this.drawLegend(svg, options, width, height, inhits);
    }
    // Bind listener events once all the graphical elements have
    // been drawn for first time.
    if (index === 0) {
        this.graphControls($queryDiv, $graphDiv, true, opts, inhits);
    }
    // Refresh tooltip each time graph is redrawn.
    this.setupTooltip();
    // Ensure clicking on 'rect' takes user to the relevant hit on all
    // browsers.
    this.setupClick($graphDiv);
  }
}

var HitsOverview = Grapher(Graph);
export default HitsOverview;
<commit_msg>Hits overview: increase bar height, decrease legend height<commit_after>import d3 from 'd3';
import _ from 'underscore';
import Grapher from './grapher';
import * as Helpers from './visualisation_helpers';

class Graph {

  static name() {
    return 'Matching sequences';
  }

  static className() {
    return 'alignment-overview';
  }

  static collapseId(props) {
    return 'alignment_'+props.query.number;
  }

  static dataName(props) {
      return 'Alignment-Overview-'+props.query.id;
  }

  constructor($svgContainer, props) {
      this.svg_container = $svgContainer;
      $queryDiv = $svgContainer.parents('.resultn');
      hits = this.extractData(props.query.hits, props.query.number);
      this.graphIt($queryDiv, $svgContainer, 0, 20, null, hits);
  }

  extractData(query_hits, number) {
    var hits = [];
    query_hits.map(function (hit) {
        var _hsps = [];
        var hsps = hit.hsps;
        _.each(hsps, function (hsp) {
            var _hsp = {};
            _hsp.hspEvalue = hsp.evalue;
            _hsp.hspStart = hsp.qstart;
            _hsp.hspEnd = hsp.qend;
            _hsp.hspFrame = hsp.sframe;
            _hsp.hspId = "Query_"+number+"_hit_"+hit.number+"_hsp_"+hsp.number;
            _hsps.push(_hsp);
        });
        _hsps.hitId = hit.id;
        _hsps.hitDef = "Query_"+number+"_hit_"+hit.number;
        _hsps.hitEvalue = hit.evalue;
        hits.push(_hsps);
    });
    return hits;
  }

  setupTooltip() {
      this.svg_container.find('[data-toggle="tooltip"]').tooltip({
          'placement': 'top', 'container': 'body', 'html': 'true',
          'delay': 0, 'white-space': 'nowrap'
      });
  }

  setupClick($graphDiv) {
    $('a', $graphDiv).click(function (evt) {
        evt.preventDefault();
        evt.stopPropagation();
        window.location.hash = $(this).attr('href');
    });
  }

  graphControls($queryDiv, $graphDiv, isInit, opts, hits) {
    var MIN_HITS_TO_SHOW = 20;

    var totalHits, shownHits, lessButton, moreButton;

    var countHits = function () {
        totalHits = hits.length;
        shownHits = $queryDiv.find('.ghit > g').length;
    };

    var setupButtons = function($queryDiv, $graphDiv) {
        $graphDiv
        .append(
            $('<button/>')
            .addClass('btn btn-link more')
            .attr('type', 'button')
            .attr('data-parent-query', $queryDiv.attr('id'))
            .html('View More ')
            .append(
                $('')
                .html('  ')
                .addClass('fa fa-angle-double-down')
            ),
            $('<button/>')
            .addClass('btn btn-link less')
            .attr('type', 'button')
            .attr('data-parent-query', $queryDiv.attr('id'))
            .html('View Less ')
            .append(
                $('')
                .html('  ')
                .addClass('fa fa-angle-double-up')
            )
        );

        lessButton = $('.less', $graphDiv);
        moreButton = $('.more', $graphDiv);
    };

    var initButtons = function () {
        countHits();
        if (totalHits === MIN_HITS_TO_SHOW ||
            shownHits < MIN_HITS_TO_SHOW) {
            lessButton.hide();
            moreButton.hide();
        }
        else if (shownHits === totalHits) {
            moreButton.hide();
            lessButton.show();
        }
        else if (shownHits === MIN_HITS_TO_SHOW) {
            lessButton.hide();
            moreButton.show();
        }
        else {
            lessButton.show();
            moreButton.show();
        }
    };

    // Setup view buttons' state properly if called for first time.
    if (isInit === true) {
        setupButtons($queryDiv, $graphDiv);
        initButtons();
    }

    moreButton.on('click', _.bind(function (e) {
        countHits();
        this.graphIt($queryDiv, $graphDiv, shownHits, MIN_HITS_TO_SHOW, opts, hits);
        initButtons();
        this.setupTooltip();
        e.stopPropagation();
    },this));

    lessButton.on('click', _.bind(function (e) {
        countHits();
        var diff = shownHits - MIN_HITS_TO_SHOW;

        // Decrease number of shown hits by defined constant.
        if (diff >= MIN_HITS_TO_SHOW) {
            this.graphIt($queryDiv, $graphDiv, shownHits, -MIN_HITS_TO_SHOW, opts, hits);
            initButtons();
        }
        else if (diff !== 0) {
            // Ensure a certain number of hits always stay in graph.
            this.graphIt($queryDiv, $graphDiv, shownHits, MIN_HITS_TO_SHOW - shownHits, opts, hits);
            initButtons();
        }
        this.setupTooltip();
        e.stopPropagation();
    },this));
  }

  drawLegend(svg, options, width, height, hits) {
    var svg_legend = svg.append('g')
        .attr('transform',
            'translate(0,' + (height - 1.88 * options.margin) + ')');

    svg_legend.append('rect')
        .attr('x', 7 * (width - 2 * options.margin) / 10)
        .attr('width', 2 * (width - 4 * options.margin) / 10)
        .attr('height', options.legend)
        .attr('fill', 'url(#legend-grad)');

    svg_legend.append('text')
        .attr('class',' legend-text')
        .attr('transform', 'translate(0, ' +options.legend +')')
        .attr('x', 6 * (width - 2 * options.margin) / 10 - options.margin / 2)
        .text("Weaker hits");
        // .text(function() {
        //   return Helpers.prettify_evalue(hits[hits.length-1].hitEvalue);
        // })

    svg_legend.append('text')
        .attr('class',' legend-text')
        .attr('transform', 'translate(0, ' + options.legend + ')')
        .attr('x', 9 * (width - 2 * options.margin) / 10 + options.margin / 2)
        .text("Stronger hits");
        // .text(function () {
        //   return Helpers.prettify_evalue(hits[0].hitEvalue);
        // })

    svg.append('linearGradient')
        .attr('id', 'legend-grad')
    .selectAll('stop')
    .data([
        {offset: "0%", color: "#ccc"},
        {offset: '50%', color: '#888'},
        {offset: "100%", color: "#000"}
    ])
    .enter()
    .append('stop')
        .attr('offset', function (d) {
            return d.offset;
        })
        .attr('stop-color', function (d) {
            return d.color;
        });
  }

  graphIt($queryDiv, $graphDiv, index, howMany, opts, inhits) {
    /* barHeight: Height of each hit track.
     * barPadding: Padding around each hit track.
     * legend: Height reserved for the overview legend.
     * margin: Margin around the svg element.
     */
    var defaults = {
        barHeight: 3,
        barPadding: 1,
        legend: inhits.length > 1 ? 3 : 0,
        margin: 20
    },
        options = $.extend(defaults, opts);
        var hits = inhits.slice(0 , index + howMany);

    // Don't draw anything when no hits are obtained.
    if (hits.length < 1) return false;

    if (index !== 0) {
        // Currently, we have no good way to extend pre-existing graph
        // and hence, are removing the old one and redrawing.
        $graphDiv.find('svg').remove();
    }

    var queryLen = $queryDiv.data().queryLen;
    var q_i = $queryDiv.attr('id');

    var width = $graphDiv.width();
    var height = hits.length * (options.barHeight + options.barPadding) +
        2 * options.legend + 5 * options.margin;
    // var height = $graphDiv.height();

    var SEQ_TYPES = {
        blastn: 'nucleic_acid',
        blastp: 'amino_acid',
        blastx: 'nucleic_acid',
        tblastx: 'nucleic_acid',
        tblastn: 'amino_acid'
    };

    var svg = d3.select($graphDiv[0])
        .selectAll('svg')
        .data([hits])
        .enter()
        .insert('svg', ':first-child')
            .attr('width', width)
            .attr('height', height)
        .append('g')
            .attr('transform', 'translate(' + options.margin / 2 + ', ' + (1.5 * options.margin) + ')');

    var x = d3.scale
        .linear()
        .range([0, width - options.margin]);

    x.domain([1, queryLen]);

    var algorithm = $queryDiv.data().algorithm;
    var formatter = Helpers.tick_formatter(x, SEQ_TYPES[algorithm]);

    var _tValues = x.ticks(11);
    _tValues.pop();

    var xAxis = d3.svg
        .axis()
        .scale(x)
        .orient('top')
        .tickValues(_tValues.concat([1, queryLen]))
        .tickFormat(formatter);

    // Attach the axis to DOM (<svg> element)
    var container = svg.append('g')
        .attr('transform', 'translate(0, ' + options.margin + ')')
        .append('g')
            .attr('class', 'x axis')
            .call(xAxis);

    // Vertical alignment of ticks
    container.selectAll('text')
            .attr('x','25px')
            .attr('y','2px')
            .attr('transform','rotate(-90)');

    var y = d3.scale
        .ordinal()
        .rangeBands([0, height - 3 * options.margin - 2 * options.legend], 0.3);

    y.domain(hits.map(function (d) {
        return d.hitId;
    }));

    var gradScale = d3.scale
        .log()
        .domain([
            d3.min([1e-5, d3.min(hits.map(function (d) {
                if (parseFloat(d.hitEvalue) === 0.0) return undefined;
                return d.hitEvalue;
                }))
            ]),
            d3.max(hits.map(function (d) {
                return d.hitEvalue;
            }))
        ])
        .range([40,150]);

    svg.append('g')
        .attr('class', 'ghit')
        .attr('transform', 'translate(0, ' + 1.65 * (options.margin - options.legend) + ')')
        .selectAll('.hits')
        .data(hits)
        .enter()
        .append('g')
            .each(function (d,i) {
                // TODO: Avoid too many variables and improve naming.

                d3.select(this)
                .selectAll('.hsp')
                .data(d).enter()
                .append('a')
                .each(function (v, j) {
                    // Drawing the HSPs connector line using the same
                    // color as that of the hit track (using lookahead).
                    var yHspline = y(d.hitId) + options.barHeight / 2;
                    var hsplineColor = d3.rgb(gradScale(v.hspEvalue),
                                              gradScale(v.hspEvalue),
                                              gradScale(v.hspEvalue));

                    if (j+1 < d.length) {
                        if (d[j].hspEnd <= d[j+1].hspStart) {
                            d3.select(this.parentNode)
                            .append('line')
                                .attr('x1', x(d[j].hspEnd))
                                .attr('y1', yHspline)
                                .attr('x2', x(d[j+1].hspStart))
                                .attr('y2', yHspline)
                                .attr('stroke', hsplineColor);
                        }
                        else if (d[j].hspStart > d[j+1].hspEnd) {
                            d3.select(this.parentNode)
                            .append('line')
                                .attr('x1', x(d[j+1].hspEnd))
                                .attr('y1', yHspline)
                                .attr('x2', x(d[j].hspStart))
                                .attr('y2', yHspline)
                                .attr('stroke', hsplineColor);
                        }
                    }

                    // Draw the rectangular hit tracks itself.
                    d3.select(this)
                        .attr('xlink:href', '#' + q_i + '_hit_' + (i+1))
                        .append('rect')
                            .attr('data-toggle', 'tooltip')
                            .attr('title', d.hitId + '
E value: '+Helpers.prettify_evalue(v.hspEvalue))
                            .attr('class','bar')
                            .attr('x', function (d) {
                                return x(d.hspStart);
                            })
                            .attr('y', y(d.hitId))
                            .attr('width', function (d) {
                                return x(d.hspEnd - d.hspStart + 1);
                            })
                            .attr('height', options.barHeight)
                            .attr('fill', d3.rgb(hsplineColor));
                });
            });

    // Draw legend only when more than one hit present
    if (hits.length > 1) {
        this.drawLegend(svg, options, width, height, inhits);
    }
    // Bind listener events once all the graphical elements have
    // been drawn for first time.
    if (index === 0) {
        this.graphControls($queryDiv, $graphDiv, true, opts, inhits);
    }
    // Refresh tooltip each time graph is redrawn.
    this.setupTooltip();
    // Ensure clicking on 'rect' takes user to the relevant hit on all
    // browsers.
    this.setupClick($graphDiv);
  }
}

var HitsOverview = Grapher(Graph);
export default HitsOverview;
<|endoftext|>


<commit_before>define(['app'], function (app) {
  "use strict";

  return app.service('userService', ['$http', '$cookies', '$q', function (http, cookies, Q) {
    var signedIn = cookies.signin == 'true';

    function guardUser (userFunc) {
      return function () {
        if (!signedIn) {
          var deferred = Q.defer();
          deferred.reject('User is not signed in');
          return deferred.promise;
        }
        return userFunc.apply(null, arguments);
      };
    }

    this.signedIn = function () {
      return signedIn;
    };

    this.signOut = function () {
      return http.post('/signout').success(function () {
        signedIn = false;
      });
    };

    this.savePrefs = guardUser(function (prefs) {
      return http.post('/user/prefs', { prefs: prefs });
    });

    this.getPrefs = guardUser(function () {
      return http.get('/user/prefs').then(function (response) {
        return response.data.result;
      });
    });

    this.savePomodoro = guardUser(function (pomodoro) {
      return http.post('/user/pomodoro', pomodoro).then(function (response) {
        return response.data.result;
      });
    });

    this.getPomodoro = guardUser(function () {
      return http.get('/user/pomodoro').then(function (response) {
        return response.data.result;
      });
    });

    this.syncPomodori = guardUser(function (pomodori) {
      return http.post('/user/pomodoro/sync', { pomodori: pomodori });
    });
  }]);
});
<commit_msg>Reuse same responder function<commit_after>define(['app'], function (app) {
  "use strict";

  return app.service('userService', ['$http', '$cookies', '$q', function (http, cookies, Q) {
    var signedIn = cookies.signin == 'true';

    function guardUser (userFunc) {
      return function () {
        if (!signedIn) {
          var deferred = Q.defer();
          deferred.reject('User is not signed in');
          return deferred.promise;
        }
        return userFunc.apply(null, arguments);
      };
    }

    function defaultResponder (response) {
      return response.data.result;
    }

    this.signedIn = function () {
      return signedIn;
    };

    this.signOut = function () {
      return http.post('/signout').success(function () {
        signedIn = false;
      });
    };

    this.savePrefs = guardUser(function (prefs) {
      return http.post('/user/prefs', { prefs: prefs });
    });

    this.getPrefs = guardUser(function () {
      return http.get('/user/prefs').then(defaultResponder);
    });

    this.savePomodoro = guardUser(function (pomodoro) {
      return http.post('/user/pomodoro', pomodoro).then(defaultResponder);
    });

    this.getPomodoro = guardUser(function () {
      return http.get('/user/pomodoro').then(defaultResponder);
    });

    this.syncPomodori = guardUser(function (pomodori) {
      return http.post('/user/pomodoro/sync', { pomodori: pomodori }).then(defaultResponder);
    });
  }]);
});
<|endoftext|>


<commit_before>// Copyright 2002-2015, University of Colorado Boulder

/**
 * Javascript for translate-sim.html
 * @author Michael Kauzmann
 * @author Aaron Davis
 */
$( document ).ready( function() {

  var testButtonTop = document.getElementById( 'testButtonTop' );
  var saveButtonTop = document.getElementById( 'saveButtonTop' );
  var testButtonBottom = document.getElementById( 'testButtonBottom' );
  var saveButtonBottom = document.getElementById( 'saveButtonBottom' );
  var simData = document.getElementById( 'simData' );
  var rtl = simData.getAttribute( 'data-direction' ) === 'rtl';

  function testButtonEventListener() {
    var simUrl = simData.getAttribute( 'data-sim-url' );
    var stringsToReplace = {};
    var rows = document.getElementsByTagName( 'TR' );
    for ( var i = 1, row; row = rows[ i ]; i++ ) {
      var translation = row.cells[ 2 ].children[ 0 ].innerHTML;
      var repo = row.getAttribute( 'data-string-repo' );
      if ( repo && translation && translation.length > 0 ) {
        // add rtl embedding markers for rtl strings
        if ( rtl ) {
          translation = '%E2%80%AB' + translation + '%E2%80%AC';
        }
        var stringKey = repo.replace( /-/g, '_' ).toUpperCase() + '/' + row.getAttribute( 'data-string-key' );
        stringsToReplace[ stringKey ] = translation;
      }
    }
    var encodedStrings = encodeURIComponent( JSON.stringify( stringsToReplace ) );
    encodedStrings = encodedStrings.replace( /%5C%5Cn/g, '%5Cn' ); // put back newlines

    var win = window.open( simUrl + '?' + 'strings=' + encodedStrings, '_blank' );
    win.focus();
  }

  testButtonTop.addEventListener( 'click', testButtonEventListener );
  testButtonBottom.addEventListener( 'click', testButtonEventListener );

  // Var to help only restore save button once from inputs
  var once = false;

  var inputs = $( 'input' );
  var savedSpans = $( '.savedSpan' );

  function saveButtonEventListener() {
    var strings = {};
    inputs.each( function( index, item ) {
      strings[ item.name ] = item.value;
    } );

    $.post( '/translate/sim/save/' + simData.getAttribute( 'data-sim-name' ) + '/' + simData.getAttribute( 'data-locale-name' ), strings, function( data ) {
      $( '.saving-gif' ).hide();

      if ( data.success ) {
        // grey out both save buttons, add saved below
        saveButtonTop.style.backgroundColor = 'grey';
        saveButtonTop.style.borderColor = 'black';
        saveButtonTop.disabled = true;
        saveButtonBottom.style.backgroundColor = 'grey';
        saveButtonBottom.style.borderColor = 'black';
        saveButtonBottom.disabled = true;
        savedSpans.attr( 'style', 'visibility: visible; color: green;' );
        savedSpans.html( '

Saved' );
        once = false;
      }
      else {
        savedSpans.attr( 'style', 'visibility: visible; color: red;' );
        savedSpans.html( '

Error Saving' );
      }
    } );
    $( '.saving-gif' ).show();
  }

  saveButtonTop.addEventListener( 'click', saveButtonEventListener );
  saveButtonBottom.addEventListener( 'click', saveButtonEventListener );

  /**
   * Restore a button to its original (non-greyed out) state
   * @param button
   */
  function restoreButton( button ) {
    button.style.backgroundColor = '#2a326a';
    button.style.borderColor = '#1A87B9';
    button.disabled = false;
    $( '#' + button.id ).hover( function() {
        $( this ).css( {
          background: '#6D77BD'
        } );
      },
      function() {
        $( this ).css( {
          background: '#2a326a'
        } );
      } );
  }

  /**
   * remove "saved" text when any change is made to inputs
   */
  function inputEventListener() {
    if ( !once ) {
      $( '.savedSpan' ).attr( 'style', 'visibility: hidden' );
      restoreButton( saveButtonTop );
      restoreButton( saveButtonBottom );
      once = true;
    }
  }

  inputs.each( function( index, item ) {
    item.addEventListener( 'keydown', inputEventListener );
  } );

  // add highlight to "\n" so users are aware that this is treated specially
  $( 'td:nth-child(2):contains("\\\\n")' ).each( function( index, element ) {
    $( element ).html( $( element ).text().replace( '\\n', '\\n' ) );
  } );

  // fix dir and text-align for rtl languages
  if ( rtl ) {
    inputs.attr( 'dir', 'rtl' );
    inputs.css( 'text-align', 'right' );
  }

  function validatePatterns() {
    var validated = true;

    $( 'tr' ).each( function( index, item ) {
      var tds = $( item ).find( 'td' );
      var matches = $( tds[ 0 ] ).text().match( /\{\d\}/g );
      if ( matches ) {
        var td = $( tds[ 1 ] );
        var input = $( td.find( 'input' ).get( 0 ) );
        var value = input.val();
        var redOutline = false;
        for ( var i = 0; i < matches.length; i++ ) {
          if ( value.length > 0 && value.indexOf( matches[ i ] ) === -1 ) {
            validated = false;
            redOutline = true;
          }
        }
        if ( redOutline ) {
          input.css( { outline: '1px solid red' } );
        }
        else {
          input.css( { outline: 'initial' } );
        }
      }
    } );
    return validated;
  }

  $( '#strings' ).submit( function( event ) {
    if ( !validatePatterns() ) {
      $( '.validation-message' ).text( 'Error: You must submit the same pattern replacements as the English strings' );
      event.preventDefault();
    }
    else {
      $( '.validation-message' ).text( '' );
    }
  } );

  $( document ).on( 'keypress', '.rosetta-table div[contenteditable]', function( e ) {
    return e.which !== 13;
  } );

  $( document ).on( 'keyup', '.rosetta-table div[contenteditable]', function( e ) {
    var contentEditable = $( this );
    var input = contentEditable.next().get( 0 );
    input.value = contentEditable.text();
    console.log( input.value );
  } );
} );
<commit_msg>add some commets, remove console.log<commit_after>// Copyright 2002-2015, University of Colorado Boulder

/**
 * Javascript for translate-sim.html
 * @author Michael Kauzmann
 * @author Aaron Davis
 */
$( document ).ready( function() {

  var testButtonTop = document.getElementById( 'testButtonTop' );
  var saveButtonTop = document.getElementById( 'saveButtonTop' );
  var testButtonBottom = document.getElementById( 'testButtonBottom' );
  var saveButtonBottom = document.getElementById( 'saveButtonBottom' );
  var simData = document.getElementById( 'simData' );
  var rtl = simData.getAttribute( 'data-direction' ) === 'rtl';

  function testButtonEventListener() {
    var simUrl = simData.getAttribute( 'data-sim-url' );
    var stringsToReplace = {};
    var rows = document.getElementsByTagName( 'TR' );
    for ( var i = 1, row; row = rows[ i ]; i++ ) {
      var translation = row.cells[ 2 ].children[ 0 ].innerHTML;
      var repo = row.getAttribute( 'data-string-repo' );
      if ( repo && translation && translation.length > 0 ) {
        // add rtl embedding markers for rtl strings
        if ( rtl ) {
          translation = '%E2%80%AB' + translation + '%E2%80%AC';
        }
        var stringKey = repo.replace( /-/g, '_' ).toUpperCase() + '/' + row.getAttribute( 'data-string-key' );
        stringsToReplace[ stringKey ] = translation;
      }
    }
    var encodedStrings = encodeURIComponent( JSON.stringify( stringsToReplace ) );
    encodedStrings = encodedStrings.replace( /%5C%5Cn/g, '%5Cn' ); // put back newlines

    var win = window.open( simUrl + '?' + 'strings=' + encodedStrings, '_blank' );
    win.focus();
  }

  testButtonTop.addEventListener( 'click', testButtonEventListener );
  testButtonBottom.addEventListener( 'click', testButtonEventListener );

  // Var to help only restore save button once from inputs
  var once = false;

  var inputs = $( 'input' );
  var savedSpans = $( '.savedSpan' );

  function saveButtonEventListener() {
    var strings = {};
    inputs.each( function( index, item ) {
      strings[ item.name ] = item.value;
    } );

    $.post( '/translate/sim/save/' + simData.getAttribute( 'data-sim-name' ) + '/' + simData.getAttribute( 'data-locale-name' ), strings, function( data ) {
      $( '.saving-gif' ).hide();

      if ( data.success ) {
        // grey out both save buttons, add saved below
        saveButtonTop.style.backgroundColor = 'grey';
        saveButtonTop.style.borderColor = 'black';
        saveButtonTop.disabled = true;
        saveButtonBottom.style.backgroundColor = 'grey';
        saveButtonBottom.style.borderColor = 'black';
        saveButtonBottom.disabled = true;
        savedSpans.attr( 'style', 'visibility: visible; color: green;' );
        savedSpans.html( '

Saved' );
        once = false;
      }
      else {
        savedSpans.attr( 'style', 'visibility: visible; color: red;' );
        savedSpans.html( '

Error Saving' );
      }
    } );
    $( '.saving-gif' ).show();
  }

  saveButtonTop.addEventListener( 'click', saveButtonEventListener );
  saveButtonBottom.addEventListener( 'click', saveButtonEventListener );

  /**
   * Restore a button to its original (non-greyed out) state
   * @param button
   */
  function restoreButton( button ) {
    button.style.backgroundColor = '#2a326a';
    button.style.borderColor = '#1A87B9';
    button.disabled = false;
    $( '#' + button.id ).hover( function() {
        $( this ).css( {
          background: '#6D77BD'
        } );
      },
      function() {
        $( this ).css( {
          background: '#2a326a'
        } );
      } );
  }

  /**
   * remove "saved" text when any change is made to inputs
   */
  function inputEventListener() {
    if ( !once ) {
      $( '.savedSpan' ).attr( 'style', 'visibility: hidden' );
      restoreButton( saveButtonTop );
      restoreButton( saveButtonBottom );
      once = true;
    }
  }

  inputs.each( function( index, item ) {
    item.addEventListener( 'keydown', inputEventListener );
  } );

  // add highlight to "\n" so users are aware that this is treated specially
  $( 'td:nth-child(2):contains("\\\\n")' ).each( function( index, element ) {
    $( element ).html( $( element ).text().replace( '\\n', '\\n' ) );
  } );

  // fix dir and text-align for rtl languages
  if ( rtl ) {
    inputs.attr( 'dir', 'rtl' );
    inputs.css( 'text-align', 'right' );
  }

  function validatePatterns() {
    var validated = true;

    $( 'tr' ).each( function( index, item ) {
      var tds = $( item ).find( 'td' );
      var matches = $( tds[ 0 ] ).text().match( /\{\d\}/g );
      if ( matches ) {
        var td = $( tds[ 1 ] );
        var input = $( td.find( 'input' ).get( 0 ) );
        var value = input.val();
        var redOutline = false;
        for ( var i = 0; i < matches.length; i++ ) {
          if ( value.length > 0 && value.indexOf( matches[ i ] ) === -1 ) {
            validated = false;
            redOutline = true;
          }
        }
        if ( redOutline ) {
          input.css( { outline: '1px solid red' } );
        }
        else {
          input.css( { outline: 'initial' } );
        }
      }
    } );
    return validated;
  }

  // validate the inputs before submitting the form
  $( '#strings' ).submit( function( event ) {
    if ( !validatePatterns() ) {
      $( '.validation-message' ).text( 'Error: You must submit the same pattern replacements as the English strings' );
      event.preventDefault();
    }
    else {
      $( '.validation-message' ).text( '' );
    }
  } );

  // disable pressing enter in inputs because it complicates things by adding
 and possibly other html
  $( document ).on( 'keypress', '.rosetta-table div[contenteditable]', function( e ) {
    return e.which !== 13;
  } );

  // on every keyup, copy the content from the editable div to a hidden input so it gets submitted with the form
  $( document ).on( 'keyup', '.rosetta-table div[contenteditable]', function( e ) {
    var contentEditable = $( this );
    var input = contentEditable.next().get( 0 );
    input.value = contentEditable.text();
  } );
} );
<|endoftext|>


<commit_before>function getUrlParameter(sParam)
{
    var sPageURL = window.location.search.substring(1);
    var sURLVariables = sPageURL.split('&');
    for (var i = 0; i < sURLVariables.length; i++)
    {
        var sParameterName = sURLVariables[i].split('=');
        if (sParameterName[0] == sParam)
        {
            return sParameterName[1];
        }
    }
}

function initialize(searchTerm) {

  if (typeof getUrlParameter('search') === 'undefined') {
    if ($("#searchBox").val()=="") { $("#searchBox").val("cat") };
    searchTerm = typeof searchTerm !== 'undefined' ? searchTerm : $("#searchBox").val();
  } else {
    $("#searchBox").val(getUrlParameter('search'));
    searchTerm = $("#searchBox").val();
  }

  $("#mapButton").click(function() {
    initialize();
  });

  $(":input").on("keyup", function(e) {
    if(e.which == 13)
      initialize();
  });

  console.log("Now tracking the term '"+searchTerm+"'");

  //Setup Google Map
  var myLatlng = new google.maps.LatLng(15.0,0);

  var light_grey_style = [{"featureType":"water","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":17}]},{"featureType":"landscape","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":20}]},{"featureType":"road.highway","elementType":"geometry.fill","stylers":[{"color":"#000000"},{"lightness":17}]},{"featureType":"road.highway","elementType":"geometry.stroke","stylers":[{"color":"#000000"},{"lightness":29},{"weight":0.2}]},{"featureType":"road.arterial","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":18}]},{"featureType":"road.local","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":16}]},{"featureType":"poi","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":21}]},{"elementType":"labels.text.stroke","stylers":[{"visibility":"on"},{"color":"#000000"},{"lightness":16}]},{"elementType":"labels.text.fill","stylers":[{"saturation":36},{"color":"#000000"},{"lightness":40}]},{"elementType":"labels.icon","stylers":[{"visibility":"off"}]},{"featureType":"transit","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":19}]},{"featureType":"administrative","elementType":"geometry.fill","stylers":[{"color":"#000000"},{"lightness":20}]},{"featureType":"administrative","elementType":"geometry.stroke","stylers":[{"color":"#000000"},{"lightness":17},{"weight":1.2}]}];

  var myOptions = {
    zoom: 2,
    center: myLatlng,
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    mapTypeControl: true,
    mapTypeControlOptions: {
      style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
      position: google.maps.ControlPosition.LEFT_BOTTOM
    },
    styles: light_grey_style
  };
  var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

  //Setup heat map and link to Twitter array we will append data to
  //positive comments
  var heatmapPos;
  var liveTweetsPos = new google.maps.MVCArray();
    heatmapPos = new google.maps.visualization.HeatmapLayer({
    data: liveTweetsPos,
    radius: 25
  });
  var gradient = [
    'rgba(255,255,255,0)',
    'rgba(255,255,255,0.1)',
    'rgba(255,255,255,0.2)',
    'rgba(210,253,254,0.3)',
    'rgba(30,244,248,0.4)',
    'rgba(32,248,158,0.5)',
    'rgba(33,249,152,0.66)',
    'rgba(51,255,75,0.66)',
    'rgba(51,255,75,0.66)'
  ]
  heatmapPos.set('gradient', heatmapPos.get('gradient') ? null : gradient);
  heatmapPos.setMap(map);

  //negative comments
  var heatmapNeg;
  var liveTweetsNeg = new google.maps.MVCArray();
    heatmapNeg = new google.maps.visualization.HeatmapLayer({
    data: liveTweetsNeg,
    radius: 25
  });
  var gradient = [
    'rgba(255,255,255,0)',
    'rgba(255,255,255,0.1)',
    'rgba(255,255,255,0.2)',
    'rgba(250,247,213,0.3)',
    'rgba(232,213,46,0.4)',
    'rgba(234,134,46,0.5)',
    'rgba(236,128,46,0.66)',
    'rgba(255,50,50,0.66)',
    'rgba(255,50,50,0.66)'
  ]
  heatmapNeg.set('gradient', heatmapNeg.get('gradient') ? null : gradient);
  heatmapNeg.setMap(map);

  if(io !== undefined) {
    // Storage for WebSocket connections
    var socket = io.connect('/');

    // This listens on the "twitter-steam" channel and data is
    // received everytime a new tweet is receieved.
    socket.on('twitter-stream', function (data) {

      //Add tweet to the heat map array.
      var tweetLocation = new google.maps.LatLng(data.lng,data.lat);

      var pattern = new RegExp('\\b' + searchTerm + '\\b', 'i');
      if (pattern.test(data.text) == true) {
        if (data.sentiment.score > 0) {
          liveTweetsPos.push({location: tweetLocation, weight: data.sentiment.score});
          // liveTweetsNeg.push({location: tweetLocation, weight: 0});
        }
        if (data.sentiment.score < 0) {
          liveTweetsNeg.push({location: tweetLocation, weight: data.sentiment.score});
          // liveTweetsPos.push({location: tweetLocation, weight: 0});
        }
      }

    });

    // Listens for a success response from the server to
    // say the connection was successful.
    socket.on("connected", function(r) {

      //Now that we are connected to the server let's tell
      //the server we are ready to start receiving tweets.
      socket.emit("start tweets");
    });
  }
}<commit_msg>Getting there with heat maps<commit_after>function getUrlParameter(sParam)
{
    var sPageURL = window.location.search.substring(1);
    var sURLVariables = sPageURL.split('&');
    for (var i = 0; i < sURLVariables.length; i++)
    {
        var sParameterName = sURLVariables[i].split('=');
        if (sParameterName[0] == sParam)
        {
            return sParameterName[1];
        }
    }
}

function initialize(searchTerm) {

  if (typeof getUrlParameter('search') === 'undefined') {
    if ($("#searchBox").val()=="") { $("#searchBox").val("cat") };
    searchTerm = typeof searchTerm !== 'undefined' ? searchTerm : $("#searchBox").val();
  } else {
    $("#searchBox").val(getUrlParameter('search'));
    searchTerm = $("#searchBox").val();
  }

  $("#mapButton").click(function() {
    initialize();
  });

  $(":input").on("keyup", function(e) {
    if(e.which == 13)
      initialize();
  });

  console.log("Now tracking the term '"+searchTerm+"'");

  //Setup Google Map
  var myLatlng = new google.maps.LatLng(15.0,0);

  var light_grey_style = [{"featureType":"water","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":17}]},{"featureType":"landscape","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":20}]},{"featureType":"road.highway","elementType":"geometry.fill","stylers":[{"color":"#000000"},{"lightness":17}]},{"featureType":"road.highway","elementType":"geometry.stroke","stylers":[{"color":"#000000"},{"lightness":29},{"weight":0.2}]},{"featureType":"road.arterial","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":18}]},{"featureType":"road.local","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":16}]},{"featureType":"poi","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":21}]},{"elementType":"labels.text.stroke","stylers":[{"visibility":"on"},{"color":"#000000"},{"lightness":16}]},{"elementType":"labels.text.fill","stylers":[{"saturation":36},{"color":"#000000"},{"lightness":40}]},{"elementType":"labels.icon","stylers":[{"visibility":"off"}]},{"featureType":"transit","elementType":"geometry","stylers":[{"color":"#000000"},{"lightness":19}]},{"featureType":"administrative","elementType":"geometry.fill","stylers":[{"color":"#000000"},{"lightness":20}]},{"featureType":"administrative","elementType":"geometry.stroke","stylers":[{"color":"#000000"},{"lightness":17},{"weight":1.2}]}];

  var myOptions = {
    zoom: 2,
    center: myLatlng,
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    mapTypeControl: true,
    mapTypeControlOptions: {
      style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
      position: google.maps.ControlPosition.LEFT_BOTTOM
    },
    styles: light_grey_style
  };
  var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

  //Setup heat map and link to Twitter array we will append data to
  //positive comments
  var heatmapPos;
  var liveTweetsPos = new google.maps.MVCArray();
    heatmapPos = new google.maps.visualization.HeatmapLayer({
    data: liveTweetsPos,
    radius: 25
  });
  var gradient = [
    'rgba(255,255,255,0)',
    'rgba(255,255,255,0.1)',
    'rgba(255,255,255,0.2)',
    'rgba(210,253,254,0.3)',
    'rgba(30,244,248,0.4)',
    'rgba(32,248,158,0.5)',
    'rgba(33,249,152,0.66)',
    'rgba(51,255,75,0.66)',
    'rgba(51,255,75,0.66)'
  ]
  heatmapPos.set('gradient', heatmapPos.get('gradient') ? null : gradient);
  heatmapPos.setMap(map);

  //negative comments
  var heatmapNeg;
  var liveTweetsNeg = new google.maps.MVCArray();
    heatmapNeg = new google.maps.visualization.HeatmapLayer({
    data: liveTweetsNeg,
    radius: 25
  });
  var gradient = [
    'rgba(255,255,255,0)',
    'rgba(255,255,255,0.1)',
    'rgba(255,255,255,0.2)',
    'rgba(250,247,213,0.3)',
    'rgba(232,213,46,0.4)',
    'rgba(234,134,46,0.5)',
    'rgba(236,128,46,0.66)',
    'rgba(255,50,50,0.66)',
    'rgba(255,50,50,0.66)'
  ]
  heatmapNeg.set('gradient', heatmapNeg.get('gradient') ? null : gradient);
  heatmapNeg.setMap(map);

  if(io !== undefined) {
    // Storage for WebSocket connections
    var socket = io.connect('/');

    // This listens on the "twitter-steam" channel and data is
    // received everytime a new tweet is receieved.
    socket.on('twitter-stream', function (data) {

      //Add tweet to the heat map array.
      var tweetLocation = new google.maps.LatLng(data.lng,data.lat);

      var pattern = new RegExp('\\b' + searchTerm + '\\b', 'i');
      if (pattern.test(data.text) == true) {
        if (data.sentiment.score > 0) {
          liveTweetsPos.push({location: tweetLocation, weight: data.sentiment.score});
          liveTweetsNeg.push({location: tweetLocation, weight: 0});
        }
        if (data.sentiment.score < 0) {
          liveTweetsNeg.push({location: tweetLocation, weight: (-1*data.sentiment.score)});
          liveTweetsPos.push({location: tweetLocation, weight: 0});
        }
      }

    });

    // Listens for a success response from the server to
    // say the connection was successful.
    socket.on("connected", function(r) {

      //Now that we are connected to the server let's tell
      //the server we are ready to start receiving tweets.
      socket.emit("start tweets");
    });
  }
}<|endoftext|>