<commit_before>// © Copyright 2013 Paul Thomas <paul@stackfull.com>.  All Rights Reserved.

// sf-virtual-repeat directive
// ===========================
// Like `ng-repeat` with reduced rendering and binding
//
(function(){
  'use strict';
  // (part of the sf.virtualScroll module).
  var mod = angular.module('sf.virtualScroll');
  var DONT_WORK_AS_VIEWPORTS = ['TABLE', 'TBODY', 'THEAD', 'TR', 'TFOOT'];
  var DONT_WORK_AS_CONTENT = ['TABLE', 'TBODY', 'THEAD', 'TR', 'TFOOT'];
  var DONT_SET_DISPLAY_BLOCK = ['TABLE', 'TBODY', 'THEAD', 'TR', 'TFOOT'];

  // Utility to clip to range
  function clip(value, min, max){
    if( angular.isArray(value) ){
      return angular.forEach(value, function(v){
        return clip(v, min, max);
      });
    }
    return Math.max(min, Math.min(value, max));
  }

  mod.directive('sfVirtualRepeat', ['$log', '$rootElement', function($log, $rootElement){

    return {
      require: '?ngModel',
      transclude: 'element',
      priority: 1000,
      terminal: true,
      compile: sfVirtualRepeatCompile
    };

    // Turn the expression supplied to the directive:
    //
    //     a in b
    //
    // into `{ value: "a", collection: "b" }`
    function parseRepeatExpression(expression){
      var match = expression.match(/^\s*([\$\w]+)\s+in\s+(\S*)\s*$/);
      if (! match) {
        throw new Error("Expected sfVirtualRepeat in form of '_item_ in _collection_' but got '" +
                        expression + "'.");
      }
      return {
        value: match[1],
        collection: match[2]
      };
    }

    // Utility to filter out elements by tag name
    function isTagNameInList(element, list){
      var t, tag = element.tagName.toUpperCase();
      for( t = 0; t < list.length; t++ ){
        if( list[t] === tag ){
          return true;
        }
      }
      return false;
    }

    // Utility to find the viewport/content elements given the start element:
    function findViewportAndContent(startElement){
      /*jshint eqeqeq:false, curly:false */
      var root = $rootElement[0];
      var e, n;
      // Somewhere between the grandparent and the root node
      for( e = startElement.parent().parent()[0]; e !== root; e = e.parentNode ){
        // is an element
        if( e.nodeType != 1 ) break;
        // that isn't in the blacklist (tables etc.),
        if( isTagNameInList(e, DONT_WORK_AS_VIEWPORTS) ) continue;
        // has a single child element (the content),
        if( e.childElementCount != 1 ) continue;
        // which is not in the blacklist
        if( isTagNameInList(e.firstElementChild, DONT_WORK_AS_CONTENT) ) continue;
        // and no text.
        for( n = e.firstChild; n; n = n.nextSibling ){
          if( n.nodeType == 3 && /\S/g.test(n.textContent) ){
            break;
          }
        }
        if( n == null ){
          // That element should work as a viewport.
          return {
            viewport: angular.element(e),
            content: angular.element(e.firstElementChild)
          };
        }
      }
      throw new Error("No suitable viewport element");
    }

    // Apply explicit height and overflow styles to the viewport element.
    //
    // If the viewport has a max-height (inherited or otherwise), set max-height.
    // Otherwise, set height from the current computed value or use
    // window.innerHeight as a fallback
    //
    function setViewportCss(viewport){
      var viewportCss = {'overflow': 'auto'},
          style = window.getComputedStyle ?
            window.getComputedStyle(viewport[0]) :
            viewport[0].currentStyle,
          maxHeight = style && style.getPropertyValue('max-height'),
          height = style && style.getPropertyValue('height');

      if( maxHeight && maxHeight !== '0px' ){
        viewportCss.maxHeight = maxHeight;
      }else if( height && height !== '0px' ){
        viewportCss.height = height;
      }else{
        viewportCss.height = window.innerHeight;
      }
      viewport.css(viewportCss);
    }

    // Apply explicit styles to the content element to prevent pesky padding
    // or borders messing with our calculations:
    function setContentCss(content){
      var contentCss = {
        margin: 0,
        padding: 0,
        border: 0,
        'box-sizing': 'border-box'
      };
      content.css(contentCss);
    }

    // TODO: compute outerHeight (padding + border unless box-sizing is border)
    function computeRowHeight(element){
      var style = window.getComputedStyle ? window.getComputedStyle(element)
                                          : element.currentStyle,
          maxHeight = style && style.getPropertyValue('max-height'),
          height = style && style.getPropertyValue('height');

      if( height && height !== '0px' && height !== 'auto' ){
        $log.info('Row height is "%s" from css height', height);
      }else if( maxHeight && maxHeight !== '0px' && maxHeight !== 'none' ){
        height = maxHeight;
        $log.info('Row height is "%s" from css max-height', height);
      }else if( element.clientHeight ){
        height = element.clientHeight+'px';
        $log.info('Row height is "%s" from client height', height);
      }else{
        throw new Error("Unable to compute height of row");
      }
      angular.element(element).css('height', height);
      return parseInt(height, 10);
    }

    // The compile gathers information about the declaration. There's not much
    // else we could do in the compile step as we need a viewport parent that
    // is exculsively ours - this is only available at link time.
    function sfVirtualRepeatCompile(element, attr, linker) {
      var ident = parseRepeatExpression(attr.sfVirtualRepeat);

      return {
        post: sfVirtualRepeatPostLink
      };
      // ----

      // Set up the initial value for our watch expression (which is just the
      // start and length of the active rows and the collection length) and
      // adds a listener to handle child scopes based on the active rows.
      function sfVirtualRepeatPostLink(scope, iterStartElement, attrs){

        var rendered = [];
        var rowHeight = 0;
        var sticky = false;
        var dom = findViewportAndContent(iterStartElement);
        // The list structure is controlled by a few simple (visible) variables:
        var state = 'ngModel' in attrs ? scope.$eval(attrs.ngModel) : {};
        //  - The index of the first active element
        state.firstActive = 0;
        //  - The index of the first visible element
        state.firstVisible = 0;
        //  - The number of elements visible in the viewport.
        state.visible = 0;
        // - The number of active elements
        state.active = 0;
        // - The total number of elements
        state.total = 0;
        // - The point at which we add new elements
        state.lowWater = state.lowWater || 100;
        // - The point at which we remove old elements
        state.highWater = state.highWater || 300;
        // TODO: now watch the water marks

        setContentCss(dom.content);
        setViewportCss(dom.viewport);
        // When the user scrolls, we move the `state.firstActive`
        dom.viewport.bind('scroll', sfVirtualRepeatOnScroll);

        // The watch on the collection is just a watch on the length of the
        // collection. We don't care if the content changes.
        scope.$watch(sfVirtualRepeatWatchExpression, sfVirtualRepeatListener, true);

        // and that's the link done! All the action is in the handlers...
        return;
        // ----

        // Apply explicit styles to the item element
        function setElementCss (element) {
          var elementCss = {
            // no margin or it'll screw up the height calculations.
            margin: '0'
          };
          if( !isTagNameInList(element[0], DONT_SET_DISPLAY_BLOCK) ){
            // display: block if it's safe to do so
            elementCss.display = 'block';
          }
          if( rowHeight ){
            elementCss.height = rowHeight+'px';
          }
          element.css(elementCss);
        }

        function makeNewScope (idx, collection, containerScope) {
          var childScope = containerScope.$new();
          childScope[ident.value] = collection[idx];
          childScope.$index = idx;
          childScope.$first = (idx === 0);
          childScope.$last = (idx === (collection.length - 1));
          childScope.$middle = !(childScope.$first || childScope.$last);
          childScope.$watch(function updateChildScopeItem(){
            childScope[ident.value] = collection[idx];
          });
          return childScope;
        }

        function addElements (start, end, collection, containerScope, insPoint) {
          var frag = document.createDocumentFragment();
          var newElements = [], element, idx, childScope;
          for( idx = start; idx !== end; idx ++ ){
            childScope = makeNewScope(idx, collection, containerScope);
            element = linker(childScope, angular.noop);
            setElementCss(element);
            newElements.push(element);
            frag.appendChild(element[0]);
          }
          insPoint.after(frag);
          return newElements;
        }

        function recomputeActive() {
          // We want to set the start to the low water mark unless the current
          // start is already between the low and high water marks.
          var start = clip(state.firstActive, state.firstVisible - state.lowWater, state.firstVisible - state.highWater);
          // Similarly for the end
          var end = clip(state.firstActive + state.active,
                         state.firstVisible + state.visible + state.lowWater,
                         state.firstVisible + state.visible + state.highWater );
          state.firstActive = Math.max(0, start);
          state.active = Math.min(end, state.total) - state.firstActive;
        }

        function sfVirtualRepeatOnScroll(evt){
          if( !rowHeight ){
            return;
          }
          // Enter the angular world for the state change to take effect.
          scope.$apply(function(){
            state.firstVisible = Math.floor(evt.target.scrollTop / rowHeight);
            state.visible = Math.ceil(dom.viewport[0].clientHeight / rowHeight);
            $log.log('scroll to row %o', state.firstVisible);
            sticky = evt.target.scrollTop + evt.target.clientHeight >= evt.target.scrollHeight;
            recomputeActive();
            $log.log(' state is now %o', state);
            $log.log(' sticky = %o', sticky);
          });
        }

        function sfVirtualRepeatWatchExpression(scope){
          var coll = scope.$eval(ident.collection);
          if( coll.length !== state.total ){
            state.total = coll.length;
            recomputeActive();
          }
          return {
            start: state.firstActive,
            active: state.active,
            len: coll.length
          };
        }

        function destroyActiveElements (action, count) {
          var dead, ii, remover = Array.prototype[action];
          for( ii = 0; ii < count; ii++ ){
            dead = remover.call(rendered);
            dead.scope().$destroy();
            dead.remove();
          }
        }

        // When the watch expression for the repeat changes, we may need to add
        // and remove scopes and elements
        function sfVirtualRepeatListener(newValue, oldValue, scope){
          var oldEnd = oldValue.start + oldValue.active,
              collection = scope.$eval(ident.collection),
              newElements;
          if( newValue === oldValue ){
            $log.info('initial listen');
            newElements = addElements(newValue.start, oldEnd, collection, scope, iterStartElement);
            rendered = newElements;
            if( rendered.length ){
              rowHeight = computeRowHeight(newElements[0][0]);
            }
          }else{
            var newEnd = newValue.start + newValue.active;
            var forward = newValue.start >= oldValue.start;
            var delta = forward ? newValue.start - oldValue.start
                                : oldValue.start - newValue.start;
            var endDelta = newEnd >= oldEnd ? newEnd - oldEnd : oldEnd - newEnd;
            var contiguous = delta < (forward ? oldValue.active : newValue.active);
            $log.info('change by %o,%o rows %s', delta, endDelta, forward ? 'forward' : 'backward');
            if( !contiguous ){
              $log.info('non-contiguous change');
              destroyActiveElements('pop', rendered.length);
              rendered = addElements(newValue.start, newEnd, collection, scope, iterStartElement);
            }else{
              if( forward ){
                $log.info('need to remove from the top');
                destroyActiveElements('shift', delta);
              }else if( delta ){
                $log.info('need to add at the top');
                newElements = addElements(
                  newValue.start,
                  oldValue.start,
                  collection, scope, iterStartElement);
                rendered = newElements.concat(rendered);
              }
              if( newEnd < oldEnd ){
                $log.info('need to remove from the bottom');
                destroyActiveElements('pop', oldEnd - newEnd);
              }else if( endDelta ){
                var lastElement = rendered[rendered.length-1];
                $log.info('need to add to the bottom');
                newElements = addElements(
                  oldEnd,
                  newEnd,
                  collection, scope, lastElement);
                rendered = rendered.concat(newElements);
              }
            }
            if( !rowHeight && rendered.length ){
              rowHeight = computeRowHeight(rendered[0][0]);
            }
            dom.content.css({'padding-top': newValue.start * rowHeight + 'px'});
          }
          dom.content.css({'height': newValue.len * rowHeight + 'px'});
          if( sticky ){
            dom.viewport[0].scrollTop = dom.viewport[0].clientHeight + dom.viewport[0].scrollHeight;
          }
        }
      }
    }
  }]);

}());

<commit_msg>support for collection filters<commit_after>// © Copyright 2013 Paul Thomas <paul@stackfull.com>.  All Rights Reserved.

// sf-virtual-repeat directive
// ===========================
// Like `ng-repeat` with reduced rendering and binding
//
(function(){
  'use strict';
  // (part of the sf.virtualScroll module).
  var mod = angular.module('sf.virtualScroll');
  var DONT_WORK_AS_VIEWPORTS = ['TABLE', 'TBODY', 'THEAD', 'TR', 'TFOOT'];
  var DONT_WORK_AS_CONTENT = ['TABLE', 'TBODY', 'THEAD', 'TR', 'TFOOT'];
  var DONT_SET_DISPLAY_BLOCK = ['TABLE', 'TBODY', 'THEAD', 'TR', 'TFOOT'];

  // Utility to clip to range
  function clip(value, min, max){
    if( angular.isArray(value) ){
      return angular.forEach(value, function(v){
        return clip(v, min, max);
      });
    }
    return Math.max(min, Math.min(value, max));
  }

  mod.directive('sfVirtualRepeat', ['$log', '$rootElement', function($log, $rootElement){

    return {
      require: '?ngModel',
      transclude: 'element',
      priority: 1000,
      terminal: true,
      compile: sfVirtualRepeatCompile
    };

    // Turn the expression supplied to the directive:
    //
    //     a in b
    //
    // into `{ value: "a", collection: "b" }`
    function parseRepeatExpression(expression){
      var match = expression.match(/^\s*([\$\w]+)\s+in\s+([\S\s]*)$/);
      if (! match) {
        throw new Error("Expected sfVirtualRepeat in form of '_item_ in _collection_' but got '" +
                        expression + "'.");
      }
      return {
        value: match[1],
        collection: match[2]
      };
    }

    // Utility to filter out elements by tag name
    function isTagNameInList(element, list){
      var t, tag = element.tagName.toUpperCase();
      for( t = 0; t < list.length; t++ ){
        if( list[t] === tag ){
          return true;
        }
      }
      return false;
    }

    // Utility to find the viewport/content elements given the start element:
    function findViewportAndContent(startElement){
      /*jshint eqeqeq:false, curly:false */
      var root = $rootElement[0];
      var e, n;
      // Somewhere between the grandparent and the root node
      for( e = startElement.parent().parent()[0]; e !== root; e = e.parentNode ){
        // is an element
        if( e.nodeType != 1 ) break;
        // that isn't in the blacklist (tables etc.),
        if( isTagNameInList(e, DONT_WORK_AS_VIEWPORTS) ) continue;
        // has a single child element (the content),
        if( e.childElementCount != 1 ) continue;
        // which is not in the blacklist
        if( isTagNameInList(e.firstElementChild, DONT_WORK_AS_CONTENT) ) continue;
        // and no text.
        for( n = e.firstChild; n; n = n.nextSibling ){
          if( n.nodeType == 3 && /\S/g.test(n.textContent) ){
            break;
          }
        }
        if( n == null ){
          // That element should work as a viewport.
          return {
            viewport: angular.element(e),
            content: angular.element(e.firstElementChild)
          };
        }
      }
      throw new Error("No suitable viewport element");
    }

    // Apply explicit height and overflow styles to the viewport element.
    //
    // If the viewport has a max-height (inherited or otherwise), set max-height.
    // Otherwise, set height from the current computed value or use
    // window.innerHeight as a fallback
    //
    function setViewportCss(viewport){
      var viewportCss = {'overflow': 'auto'},
          style = window.getComputedStyle ?
            window.getComputedStyle(viewport[0]) :
            viewport[0].currentStyle,
          maxHeight = style && style.getPropertyValue('max-height'),
          height = style && style.getPropertyValue('height');

      if( maxHeight && maxHeight !== '0px' ){
        viewportCss.maxHeight = maxHeight;
      }else if( height && height !== '0px' ){
        viewportCss.height = height;
      }else{
        viewportCss.height = window.innerHeight;
      }
      viewport.css(viewportCss);
    }

    // Apply explicit styles to the content element to prevent pesky padding
    // or borders messing with our calculations:
    function setContentCss(content){
      var contentCss = {
        margin: 0,
        padding: 0,
        border: 0,
        'box-sizing': 'border-box'
      };
      content.css(contentCss);
    }

    // TODO: compute outerHeight (padding + border unless box-sizing is border)
    function computeRowHeight(element){
      var style = window.getComputedStyle ? window.getComputedStyle(element)
                                          : element.currentStyle,
          maxHeight = style && style.getPropertyValue('max-height'),
          height = style && style.getPropertyValue('height');

      if( height && height !== '0px' && height !== 'auto' ){
        $log.info('Row height is "%s" from css height', height);
      }else if( maxHeight && maxHeight !== '0px' && maxHeight !== 'none' ){
        height = maxHeight;
        $log.info('Row height is "%s" from css max-height', height);
      }else if( element.clientHeight ){
        height = element.clientHeight+'px';
        $log.info('Row height is "%s" from client height', height);
      }else{
        throw new Error("Unable to compute height of row");
      }
      angular.element(element).css('height', height);
      return parseInt(height, 10);
    }

    // The compile gathers information about the declaration. There's not much
    // else we could do in the compile step as we need a viewport parent that
    // is exculsively ours - this is only available at link time.
    function sfVirtualRepeatCompile(element, attr, linker) {
      var ident = parseRepeatExpression(attr.sfVirtualRepeat);

      return {
        post: sfVirtualRepeatPostLink
      };
      // ----

      // Set up the initial value for our watch expression (which is just the
      // start and length of the active rows and the collection length) and
      // adds a listener to handle child scopes based on the active rows.
      function sfVirtualRepeatPostLink(scope, iterStartElement, attrs){

        var rendered = [];
        var rowHeight = 0;
        var sticky = false;
        var dom = findViewportAndContent(iterStartElement);
        // The list structure is controlled by a few simple (visible) variables:
        var state = 'ngModel' in attrs ? scope.$eval(attrs.ngModel) : {};
        //  - The index of the first active element
        state.firstActive = 0;
        //  - The index of the first visible element
        state.firstVisible = 0;
        //  - The number of elements visible in the viewport.
        state.visible = 0;
        // - The number of active elements
        state.active = 0;
        // - The total number of elements
        state.total = 0;
        // - The point at which we add new elements
        state.lowWater = state.lowWater || 100;
        // - The point at which we remove old elements
        state.highWater = state.highWater || 300;
        // TODO: now watch the water marks

        setContentCss(dom.content);
        setViewportCss(dom.viewport);
        // When the user scrolls, we move the `state.firstActive`
        dom.viewport.bind('scroll', sfVirtualRepeatOnScroll);

        // The watch on the collection is just a watch on the length of the
        // collection. We don't care if the content changes.
        scope.$watch(sfVirtualRepeatWatchExpression, sfVirtualRepeatListener, true);

        // and that's the link done! All the action is in the handlers...
        return;
        // ----

        // Apply explicit styles to the item element
        function setElementCss (element) {
          var elementCss = {
            // no margin or it'll screw up the height calculations.
            margin: '0'
          };
          if( !isTagNameInList(element[0], DONT_SET_DISPLAY_BLOCK) ){
            // display: block if it's safe to do so
            elementCss.display = 'block';
          }
          if( rowHeight ){
            elementCss.height = rowHeight+'px';
          }
          element.css(elementCss);
        }

        function makeNewScope (idx, collection, containerScope) {
          var childScope = containerScope.$new();
          childScope[ident.value] = collection[idx];
          childScope.$index = idx;
          childScope.$first = (idx === 0);
          childScope.$last = (idx === (collection.length - 1));
          childScope.$middle = !(childScope.$first || childScope.$last);
          childScope.$watch(function updateChildScopeItem(){
            childScope[ident.value] = collection[idx];
          });
          return childScope;
        }

        function addElements (start, end, collection, containerScope, insPoint) {
          var frag = document.createDocumentFragment();
          var newElements = [], element, idx, childScope;
          for( idx = start; idx !== end; idx ++ ){
            childScope = makeNewScope(idx, collection, containerScope);
            element = linker(childScope, angular.noop);
            setElementCss(element);
            newElements.push(element);
            frag.appendChild(element[0]);
          }
          insPoint.after(frag);
          return newElements;
        }

        function recomputeActive() {
          // We want to set the start to the low water mark unless the current
          // start is already between the low and high water marks.
          var start = clip(state.firstActive, state.firstVisible - state.lowWater, state.firstVisible - state.highWater);
          // Similarly for the end
          var end = clip(state.firstActive + state.active,
                         state.firstVisible + state.visible + state.lowWater,
                         state.firstVisible + state.visible + state.highWater );
          state.firstActive = Math.max(0, start);
          state.active = Math.min(end, state.total) - state.firstActive;
        }

        function sfVirtualRepeatOnScroll(evt){
          if( !rowHeight ){
            return;
          }
          // Enter the angular world for the state change to take effect.
          scope.$apply(function(){
            state.firstVisible = Math.floor(evt.target.scrollTop / rowHeight);
            state.visible = Math.ceil(dom.viewport[0].clientHeight / rowHeight);
            $log.log('scroll to row %o', state.firstVisible);
            sticky = evt.target.scrollTop + evt.target.clientHeight >= evt.target.scrollHeight;
            recomputeActive();
            $log.log(' state is now %o', state);
            $log.log(' sticky = %o', sticky);
          });
        }

        function sfVirtualRepeatWatchExpression(scope){
          var coll = scope.$eval(ident.collection);
          if( coll.length !== state.total ){
            state.total = coll.length;
            recomputeActive();
          }
          return {
            start: state.firstActive,
            active: state.active,
            len: coll.length
          };
        }

        function destroyActiveElements (action, count) {
          var dead, ii, remover = Array.prototype[action];
          for( ii = 0; ii < count; ii++ ){
            dead = remover.call(rendered);
            dead.scope().$destroy();
            dead.remove();
          }
        }

        // When the watch expression for the repeat changes, we may need to add
        // and remove scopes and elements
        function sfVirtualRepeatListener(newValue, oldValue, scope){
          var oldEnd = oldValue.start + oldValue.active,
              collection = scope.$eval(ident.collection),
              newElements;
          if( newValue === oldValue ){
            $log.info('initial listen');
            newElements = addElements(newValue.start, oldEnd, collection, scope, iterStartElement);
            rendered = newElements;
            if( rendered.length ){
              rowHeight = computeRowHeight(newElements[0][0]);
            }
          }else{
            var newEnd = newValue.start + newValue.active;
            var forward = newValue.start >= oldValue.start;
            var delta = forward ? newValue.start - oldValue.start
                                : oldValue.start - newValue.start;
            var endDelta = newEnd >= oldEnd ? newEnd - oldEnd : oldEnd - newEnd;
            var contiguous = delta < (forward ? oldValue.active : newValue.active);
            $log.info('change by %o,%o rows %s', delta, endDelta, forward ? 'forward' : 'backward');
            if( !contiguous ){
              $log.info('non-contiguous change');
              destroyActiveElements('pop', rendered.length);
              rendered = addElements(newValue.start, newEnd, collection, scope, iterStartElement);
            }else{
              if( forward ){
                $log.info('need to remove from the top');
                destroyActiveElements('shift', delta);
              }else if( delta ){
                $log.info('need to add at the top');
                newElements = addElements(
                  newValue.start,
                  oldValue.start,
                  collection, scope, iterStartElement);
                rendered = newElements.concat(rendered);
              }
              if( newEnd < oldEnd ){
                $log.info('need to remove from the bottom');
                destroyActiveElements('pop', oldEnd - newEnd);
              }else if( endDelta ){
                var lastElement = rendered[rendered.length-1];
                $log.info('need to add to the bottom');
                newElements = addElements(
                  oldEnd,
                  newEnd,
                  collection, scope, lastElement);
                rendered = rendered.concat(newElements);
              }
            }
            if( !rowHeight && rendered.length ){
              rowHeight = computeRowHeight(rendered[0][0]);
            }
            dom.content.css({'padding-top': newValue.start * rowHeight + 'px'});
          }
          dom.content.css({'height': newValue.len * rowHeight + 'px'});
          if( sticky ){
            dom.viewport[0].scrollTop = dom.viewport[0].clientHeight + dom.viewport[0].scrollHeight;
          }
        }
      }
    }
  }]);

}());

<|endoftext|>


<commit_before>/**
 * Synchronize zooming and/or selections between a set of dygraphs.
 *
 * Usage:
 *
 *   var g1 = new Dygraph(...),
 *       g2 = new Dygraph(...),
 *       ...;
 *   var sync = Dygraph.synchronize(g1, g2, ...);
 *   // charts are now synchronized
 *   sync.detach();
 *   // charts are no longer synchronized
 *
 * You can set options using the last parameter, for example:
 *
 *   var sync = Dygraph.synchronize(g1, g2, g3, {
 *      selection: true,
 *      zoom: true
 *   });
 *
 * The default is to synchronize both of these.
 *
 * Instead of passing one Dygraph object as each parameter, you may also pass an
 * array of dygraphs:
 *
 *   var sync = Dygraph.synchronize([g1, g2, g3], {
 *      selection: false,
 *      zoom: true
 *   });
 *
 * You may also set `range: false` if you wish to only sync the x-axis.
 * The `range` option has no effect unless `zoom` is true (the default).
 */
(function() {
/* global Dygraph:false */
'use strict';

Dygraph.synchronize = function(/* dygraphs..., opts */) {
  if (arguments.length === 0) {
    throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
  }

  var OPTIONS = ['selection', 'zoom', 'range'];
  var opts = {
    selection: true,
    zoom: true,
    range: true
  };
  var dygraphs = [];

  var parseOpts = function(obj) {
    if (!(obj instanceof Object)) {
      throw 'Last argument must be either Dygraph or Object.';
    } else {
      for (var i = 0; i < OPTIONS.length; i++) {
        var optName = OPTIONS[i];
        if (obj.hasOwnProperty(optName)) opts[optName] = obj[optName];
      }
    }
  };

  if (arguments[0] instanceof Dygraph) {
    // Arguments are Dygraph objects.
    for (var i = 0; i < arguments.length; i++) {
      if (arguments[i] instanceof Dygraph) {
        dygraphs.push(arguments[i]);
      } else {
        break;
      }
    }
    if (i < arguments.length - 1) {
      throw 'Invalid invocation of Dygraph.synchronize(). ' +
            'All but the last argument must be Dygraph objects.';
    } else if (i == arguments.length - 1) {
      parseOpts(arguments[arguments.length - 1]);
    }
  } else if (arguments[0].length) {
    // Invoked w/ list of dygraphs, options
    for (var i = 0; i < arguments[0].length; i++) {
      dygraphs.push(arguments[0][i]);
    }
    if (arguments.length == 2) {
      parseOpts(arguments[1]);
    } else if (arguments.length > 2) {
      throw 'Invalid invocation of Dygraph.synchronize(). ' +
            'Expected two arguments: array and optional options argument.';
    }  // otherwise arguments.length == 1, which is fine.
  } else {
    throw 'Invalid invocation of Dygraph.synchronize(). ' +
          'First parameter must be either Dygraph or list of Dygraphs.';
  }

  if (dygraphs.length < 2) {
    throw 'Invalid invocation of Dygraph.synchronize(). ' +
          'Need two or more dygraphs to synchronize.';
  }

  // Listen for draw, highlight, unhighlight callbacks.
  if (opts.zoom) {
    attachZoomHandlers(dygraphs, opts);
  }

  if (opts.selection) {
    attachSelectionHandlers(dygraphs);
  }

  return {
    detach: function() {
      for (var i = 0; i < dygraphs.length; i++) {
        var g = dygraphs[i];
        if (opts.zoom) {
          g.updateOptions({drawCallback: null});
        }
        if (opts.selection) {
          g.updateOptions({
            highlightCallback: null,
            unhighlightCallback: null
          });
        }
      }
      // release references & make subsequent calls throw.
      dygraphs = null;
      opts = null;
    }
  };
};

// TODO: call any `drawCallback`s that were set before this.
function attachZoomHandlers(gs, syncOpts) {
  var block = false;
  for (var i = 0; i < gs.length; i++) {
    var g = gs[i];
    g.updateOptions({
      drawCallback: function(me, initial) {
        if (block || initial) return;
        block = true;
        var opts = {
          dateWindow: me.xAxisRange()
        };
        if (syncOpts.range) opts.valueRange = me.yAxisRange();

        for (var j = 0; j < gs.length; j++) {
          if (gs[j] == me) continue;
          gs[j].updateOptions(opts);
        }
        block = false;
      }
    }, false /* no need to redraw */);
  }
}

function attachSelectionHandlers(gs) {
  var block = false;
  for (var i = 0; i < gs.length; i++) {
    var g = gs[i];
    g.updateOptions({
      highlightCallback: function(event, x, points, row, seriesName) {
        if (block) return;
        block = true;
        var me = this;
        for (var i = 0; i < gs.length; i++) {
          if (me == gs[i]) continue;
          var idx = dygraphsBinarySearch(gs[i], x);
          if (idx !== null) {
            gs[i].setSelection(idx, seriesName);
          }
        }
        block = false;
      },
      unhighlightCallback: function(event) {
        if (block) return;
        block = true;
        var me = this;
        for (var i = 0; i < gs.length; i++) {
          if (me == gs[i]) continue;
          gs[i].clearSelection();
        }
        block = false;
      }
    });
  }
}

// Returns the index corresponding to xVal, or null if there is none.
function dygraphsBinarySearch(g, xVal) {
  var low = 0,
      high = g.numRows() - 1;

  while (low < high) {
    var idx = (high + low) >> 1;
    var x = g.getValue(idx, 0);
    if (x < xVal) {
      low = idx + 1;
    } else if (x > xVal) {
      high = idx - 1;
    } else {
      return idx;
    }
  }

  // TODO: give an option to find the closest point, i.e. not demand an exact match.
  return null;
}

})();
<commit_msg>Call previously attached callbacks<commit_after>/**
 * Synchronize zooming and/or selections between a set of dygraphs.
 *
 * Usage:
 *
 *   var g1 = new Dygraph(...),
 *       g2 = new Dygraph(...),
 *       ...;
 *   var sync = Dygraph.synchronize(g1, g2, ...);
 *   // charts are now synchronized
 *   sync.detach();
 *   // charts are no longer synchronized
 *
 * You can set options using the last parameter, for example:
 *
 *   var sync = Dygraph.synchronize(g1, g2, g3, {
 *      selection: true,
 *      zoom: true
 *   });
 *
 * The default is to synchronize both of these.
 *
 * Instead of passing one Dygraph object as each parameter, you may also pass an
 * array of dygraphs:
 *
 *   var sync = Dygraph.synchronize([g1, g2, g3], {
 *      selection: false,
 *      zoom: true
 *   });
 *
 * You may also set `range: false` if you wish to only sync the x-axis.
 * The `range` option has no effect unless `zoom` is true (the default).
 */
(function() {
/* global Dygraph:false */
'use strict';

Dygraph.synchronize = function(/* dygraphs..., opts */) {
  if (arguments.length === 0) {
    throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
  }

  var OPTIONS = ['selection', 'zoom', 'range'];
  var opts = {
    selection: true,
    zoom: true,
    range: true
  };
  var dygraphs = [];

  var parseOpts = function(obj) {
    if (!(obj instanceof Object)) {
      throw 'Last argument must be either Dygraph or Object.';
    } else {
      for (var i = 0; i < OPTIONS.length; i++) {
        var optName = OPTIONS[i];
        if (obj.hasOwnProperty(optName)) opts[optName] = obj[optName];
      }
    }
  };

  if (arguments[0] instanceof Dygraph) {
    // Arguments are Dygraph objects.
    for (var i = 0; i < arguments.length; i++) {
      if (arguments[i] instanceof Dygraph) {
        dygraphs.push(arguments[i]);
      } else {
        break;
      }
    }
    if (i < arguments.length - 1) {
      throw 'Invalid invocation of Dygraph.synchronize(). ' +
            'All but the last argument must be Dygraph objects.';
    } else if (i == arguments.length - 1) {
      parseOpts(arguments[arguments.length - 1]);
    }
  } else if (arguments[0].length) {
    // Invoked w/ list of dygraphs, options
    for (var i = 0; i < arguments[0].length; i++) {
      dygraphs.push(arguments[0][i]);
    }
    if (arguments.length == 2) {
      parseOpts(arguments[1]);
    } else if (arguments.length > 2) {
      throw 'Invalid invocation of Dygraph.synchronize(). ' +
            'Expected two arguments: array and optional options argument.';
    }  // otherwise arguments.length == 1, which is fine.
  } else {
    throw 'Invalid invocation of Dygraph.synchronize(). ' +
          'First parameter must be either Dygraph or list of Dygraphs.';
  }

  if (dygraphs.length < 2) {
    throw 'Invalid invocation of Dygraph.synchronize(). ' +
          'Need two or more dygraphs to synchronize.';
  }

  // Listen for draw, highlight, unhighlight callbacks.
  if (opts.zoom) {
    attachZoomHandlers(dygraphs, opts);
  }

  if (opts.selection) {
    attachSelectionHandlers(dygraphs);
  }

  return {
    detach: function() {
      for (var i = 0; i < dygraphs.length; i++) {
        var g = dygraphs[i];
        if (opts.zoom) {
          g.updateOptions({drawCallback: null});
        }
        if (opts.selection) {
          g.updateOptions({
            highlightCallback: null,
            unhighlightCallback: null
          });
        }
      }
      // release references & make subsequent calls throw.
      dygraphs = null;
      opts = null;
    }
  };
};

function attachZoomHandlers(gs, syncOpts) {
  var block = false;
  for (var i = 0; i < gs.length; i++) {
    var g = gs[i];
    var oldDC = g.getFunctionOption('drawCallback');
    g.updateOptions({
      drawCallback: function(me, initial) {
        if (oldDC) oldDC(me, initial);
        if (block || initial) return;
        block = true;
        var opts = {
          dateWindow: me.xAxisRange()
        };
        if (syncOpts.range) opts.valueRange = me.yAxisRange();

        for (var j = 0; j < gs.length; j++) {
          if (gs[j] == me) continue;
          gs[j].updateOptions(opts);
        }
        block = false;
      }
    }, false /* no need to redraw */);
  }
}

function attachSelectionHandlers(gs) {
  var block = false;
  for (var i = 0; i < gs.length; i++) {
    var g = gs[i];
    var oldHC = g.getFunctionOption('highlightCallback');
    var oldUHC = g.getFunctionOption('unhighlightCallback');
    g.updateOptions({
      highlightCallback: function(event, x, points, row, seriesName) {
        if (oldHC) oldHC(event, x, points, row, seriesName);
        if (block) return;
        block = true;
        var me = this;
        for (var i = 0; i < gs.length; i++) {
          if (me == gs[i]) continue;
          var idx = dygraphsBinarySearch(gs[i], x);
          if (idx !== null) {
            gs[i].setSelection(idx, seriesName);
          }
        }
        block = false;
      },
      unhighlightCallback: function(event) {
        if (oldUHC) oldUHC(event);
        if (block) return;
        block = true;
        var me = this;
        for (var i = 0; i < gs.length; i++) {
          if (me == gs[i]) continue;
          gs[i].clearSelection();
        }
        block = false;
      }
    });
  }
}

// Returns the index corresponding to xVal, or null if there is none.
function dygraphsBinarySearch(g, xVal) {
  var low = 0,
      high = g.numRows() - 1;

  while (low < high) {
    var idx = (high + low) >> 1;
    var x = g.getValue(idx, 0);
    if (x < xVal) {
      low = idx + 1;
    } else if (x > xVal) {
      high = idx - 1;
    } else {
      return idx;
    }
  }

  // TODO: give an option to find the closest point, i.e. not demand an exact match.
  return null;
}

})();
<|endoftext|>


<commit_before>// Includes shared functions for ExploreController, SearchController, and
// ContextPanelController - handles thumbnail click, right-click, and paging
// behavior

// ToursController does not use this factory

wwt.app.factory('ThumbList', ['$rootScope', 'Util', 'Places', '$timeout', function ($rootScope, util, places, $timeout) {
  var api = {
    init: init,
    clickThumb: clickThumb,
    calcPageSize: calcPageSize,
    spliceOnePage: spliceOnePage,
    goFwd: goFwd,
    goBack: goBack
  };

  // Each controller calls init and passes in the controller
  // scope
  function init(scope, name) {
    scope.pageCount = 1;
    scope.pageSize = 1;
    scope.currentPage = 0;

    scope.preventClickBubble = function (event) {
      event.stopImmediatePropagation();
    };
    scope.goBack = function () {
      goBack(scope);
    };
    scope.goFwd = function () {
      goFwd(scope);
    };
    scope.showMenu = function (i) {
      var item = scope.collectionPage[i];
      item.contextMenuEvent = true;
      $('.popover-content .close-btn').click();
      if (!item.get_isFolder() && item.get_name() !== 'Up Level') {
        var menuContainer = $((name === 'context' ? '.nearby-objects ' : '.top-panel ') + '#menuContainer' + i);
        if (util.isMobile) {
          menuContainer = $('#' + name + 'Container #menuContainer' + i);
        }
        menuContainer.append($('#researchMenu'));
        setTimeout(function () {
          $('.popover-content .close-btn').click();
          menuContainer.find('#researchMenu')
            .addClass('open')
            .off('click')
            .on('click', function (event) {
              event.stopPropagation();
            });
          menuContainer.find('.drop-toggle').click();
          $timeout(function () {
            if (!util.isMobile) {
              $('.dropdown-backdrop').off('contextmenu');
              $('.dropdown-backdrop').on('contextmenu', function (event) {
                $(this).click();
                event.preventDefault();
              });
            }
            scope.setMenuContextItem(item, true);
            item.contextMenuEvent = false;
          }, 10);

        }, 10);
      }
    };

    // toggles the expanded thumbnail view to show 1 or 5 rows of thumbs
    scope.expandThumbnails = function (flag) {
      $('body').append($('#researchMenu'));
      scope.currentPage = 0;
      scope.expanded = flag != undefined ? flag : !scope.expanded;
      scope.expandTop(scope.expanded, name);
      calcPageSize(scope, name === 'context');
    };
    scope.dropdownClass = name === 'context' && !util.isMobile ? 'dropup menu-container' : 'dropdown menu-container';
    scope.popupPosition = name === 'context' && !util.isMobile ? 'top' : 'bottom';
  }

  function clickThumb(item, scope, outParams, callback) {
    if (item.contextMenuEvent) {
      return outParams;
    }
    if (!outParams) {
      outParams = {};
    }
    scope.activeItem = item.get_thumbnailUrl() + item.get_name();
    scope.setActiveItem(item);
    wwt.wc.clearAnnotations();
    if (item.get_name() === 'Up Level') {
      $('body').append($('#researchMenu'));
      scope.currentPage = 0;
      outParams.depth--;
      outParams.breadCrumb.pop();
      scope.breadCrumb = outParams.breadCrumb;
      outParams.cache.pop();
      scope.collection = outParams.cache[outParams.cache.length - 1];
      calcPageSize(scope, false);
      return outParams;
    }
    if (item.get_url && item.get_url() && item.get_url().indexOf('?wwtfull') !== -1) {
      window.open(item.get_url());
      return outParams;
    }
    if (item.get_isFolder()) {
      $('#folderLoadingModal').modal('show');
      $('body').append($('#researchMenu'));
      scope.currentPage = 0;
      outParams.depth++;
      outParams.breadCrumb.push(item.get_name());
      scope.breadCrumb = outParams.breadCrumb;
      places.getChildren(item).then(function (result) {
        $('#folderLoadingModal').modal('hide');
        if ($.isArray(result[0])) {
          result = result[0];
        }
        var unique = [];
        $.each(result, function (index, el) {
          if ($.inArray(el, unique) === -1) unique.push(el);
        });
        scope.collection = unique;
        calcPageSize(scope, false);
        outParams.cache.push(result);
        if (outParams.openCollection) {
          if (outParams.newCollectionUrl) {
            var i = 0;
            while (result[i].url && result[i].url.indexOf(outParams.newCollectionUrl) === -1) i++;

            scope.clickThumb(result[i]);
            outParams.newCollectionUrl = null;
          } else if (result.length) {
            scope.clickThumb(result[0]);
          }
        }

        if (callback) {
          callback();
        }
      });
      return outParams;
    } else if (outParams.openCollection) {
      outParams.openCollection = false;
    } else if (scope.$hide) {
      scope.$hide();
      $rootScope.searchModal = false;
    } else if (util.isMobile) {
      $('#explorerModal').modal('hide');
    }

    if ((item.isFGImage && item.imageSet && scope.lookAt !== 'Sky') || item.isSurvey) {
      if (item.guid && item.guid.toLowerCase().indexOf('mars.') == -1) {
        scope.setLookAt('Sky', item.get_name(), true, item.isSurvey);
      }
      if (item.isSurvey) {
        scope.setSurveyBg(item.get_name(), item);
      } else {
        scope.setForegroundImage(item);
      }
      if (scope.$hide) {
        scope.$hide();
        $rootScope.searchModal = false;
      }
      return outParams;
    }
    else if (item.isPanorama) {
      scope.setLookAt('Panorama', item.get_name());
    } else if (item.isEarth) {
      scope.setLookAt('Earth', item.get_name());
    } else if (util.getIsPlanet(item)) {

      if (scope.lookAt === 'Sky') {
        item._camParams.zoom = 0.1;

        wwtlib.WWTControl.singleton.gotoTarget3(item._camParams);
        return outParams;

      } else {
        item._camParam.zoom = 360;
        //c.angle = 360;
      }
      if (scope.lookAt !== 'SolarSystem') {

        scope.setLookAt('Planet', item._name || '');
      }
    }
    if ((ss.canCast(item, wwtlib.Place) || item.isEarth) && !item.isSurvey) {
      scope.setForegroundImage(item);
    }
    if (ss.canCast(item, wwtlib.Tour)) {
      scope.playTour(item.get_tourUrl());
    }
    return outParams;

    /*var exUranus = {
      "_camParams": {
        "lat": -4.426167041642662,
        "lng": -351.77882519031607,
        "zoom": 0.1,
        "rotation": 0,
        "angle": 0,
        "raDec": true,
        "opacity": 100,
        "target": 65536,
        "viewTarget": {"x": 0, "y": 0, "z": 0},
        "targetReferenceFrame": ""
      },
      "_location3d": {"x": 0, "y": 0, "z": 0},
      "htmlDescription": "",
      "_constellation": "AQR",
      "_classification": 536870912,
      "_type": 2,
      "_magnitude": 0,
      "_distnace": 0,
      "angularSize": 0.1,
      "_thumbNail": null,
      "_studyImageset": null,
      "_backgroundImageSet": null,
      "_searchDistance": 0,
      "_elevation": 50,
      "_name": "Uranus",
      "guid": "Solar System (Sky).8",
      "thumb": "http://cdn.worldwidetelescope.org/wwtweb/thumbnail.aspx?name=uranus",
      "isPlanet": false,
      "isFGImage": true,
      "$$hashKey": "object:832",
      "imageSet": null
    }*/
  };

  function calcPageSize(scope, isContextPanel) {
    var list = scope.collection;
    var tnWid = 116;
    var winWid = $(window).width();

    if (isContextPanel && (scope.lookAt === 'Sky' || scope.lookAt === 'SolarSystem')) {
      winWid = winWid - 216; //angular.element('body.desktop .fov-panel').width();
    }
    scope.pageSize = util.isMobile ? 99999 : Math.floor(winWid / tnWid);

    if (scope.expanded) {
      scope.pageSize *= 5;
    }
    var listLength = list ? list.length : 2;
    $timeout(function () {
      scope.pageCount = Math.ceil(listLength / scope.pageSize);
      spliceOnePage(scope);
    }, 10);
  };

  function goBack(scope) {
    $('body').append($('#researchMenu'));
    scope.currentPage = scope.currentPage === 0 ? scope.currentPage : scope.currentPage - 1;
    return spliceOnePage(scope);
  };

  function goFwd(scope) {
    $('body').append($('#researchMenu'));
    scope.currentPage = scope.currentPage === scope.pageCount - 1 ? scope.currentPage : scope.currentPage + 1;
    return spliceOnePage(scope);
  };

  function spliceOnePage(scope) {
    if (scope.collection) {
      var start = scope.currentPage * scope.pageSize;
      scope.collectionPage = scope.collection.slice(start, start + scope.pageSize);
    }
  };

  return api;

}]);

<commit_msg>call planet camParams getter<commit_after>// Includes shared functions for ExploreController, SearchController, and
// ContextPanelController - handles thumbnail click, right-click, and paging
// behavior

// ToursController does not use this factory

wwt.app.factory('ThumbList', ['$rootScope', 'Util', 'Places', '$timeout', function ($rootScope, util, places, $timeout) {
  var api = {
    init: init,
    clickThumb: clickThumb,
    calcPageSize: calcPageSize,
    spliceOnePage: spliceOnePage,
    goFwd: goFwd,
    goBack: goBack
  };

  // Each controller calls init and passes in the controller
  // scope
  function init(scope, name) {
    scope.pageCount = 1;
    scope.pageSize = 1;
    scope.currentPage = 0;

    scope.preventClickBubble = function (event) {
      event.stopImmediatePropagation();
    };
    scope.goBack = function () {
      goBack(scope);
    };
    scope.goFwd = function () {
      goFwd(scope);
    };
    scope.showMenu = function (i) {
      var item = scope.collectionPage[i];
      item.contextMenuEvent = true;
      $('.popover-content .close-btn').click();
      if (!item.get_isFolder() && item.get_name() !== 'Up Level') {
        var menuContainer = $((name === 'context' ? '.nearby-objects ' : '.top-panel ') + '#menuContainer' + i);
        if (util.isMobile) {
          menuContainer = $('#' + name + 'Container #menuContainer' + i);
        }
        menuContainer.append($('#researchMenu'));
        setTimeout(function () {
          $('.popover-content .close-btn').click();
          menuContainer.find('#researchMenu')
            .addClass('open')
            .off('click')
            .on('click', function (event) {
              event.stopPropagation();
            });
          menuContainer.find('.drop-toggle').click();
          $timeout(function () {
            if (!util.isMobile) {
              $('.dropdown-backdrop').off('contextmenu');
              $('.dropdown-backdrop').on('contextmenu', function (event) {
                $(this).click();
                event.preventDefault();
              });
            }
            scope.setMenuContextItem(item, true);
            item.contextMenuEvent = false;
          }, 10);

        }, 10);
      }
    };

    // toggles the expanded thumbnail view to show 1 or 5 rows of thumbs
    scope.expandThumbnails = function (flag) {
      $('body').append($('#researchMenu'));
      scope.currentPage = 0;
      scope.expanded = flag != undefined ? flag : !scope.expanded;
      scope.expandTop(scope.expanded, name);
      calcPageSize(scope, name === 'context');
    };
    scope.dropdownClass = name === 'context' && !util.isMobile ? 'dropup menu-container' : 'dropdown menu-container';
    scope.popupPosition = name === 'context' && !util.isMobile ? 'top' : 'bottom';
  }

  function clickThumb(item, scope, outParams, callback) {
    if (item.contextMenuEvent) {
      return outParams;
    }
    if (!outParams) {
      outParams = {};
    }
    scope.activeItem = item.get_thumbnailUrl() + item.get_name();
    scope.setActiveItem(item);
    wwt.wc.clearAnnotations();
    if (item.get_name() === 'Up Level') {
      $('body').append($('#researchMenu'));
      scope.currentPage = 0;
      outParams.depth--;
      outParams.breadCrumb.pop();
      scope.breadCrumb = outParams.breadCrumb;
      outParams.cache.pop();
      scope.collection = outParams.cache[outParams.cache.length - 1];
      calcPageSize(scope, false);
      return outParams;
    }
    if (item.get_url && item.get_url() && item.get_url().indexOf('?wwtfull') !== -1) {
      window.open(item.get_url());
      return outParams;
    }
    if (item.get_isFolder()) {
      $('#folderLoadingModal').modal('show');
      $('body').append($('#researchMenu'));
      scope.currentPage = 0;
      outParams.depth++;
      outParams.breadCrumb.push(item.get_name());
      scope.breadCrumb = outParams.breadCrumb;
      places.getChildren(item).then(function (result) {
        $('#folderLoadingModal').modal('hide');
        if ($.isArray(result[0])) {
          result = result[0];
        }
        var unique = [];
        $.each(result, function (index, el) {
          if ($.inArray(el, unique) === -1) unique.push(el);
        });
        scope.collection = unique;
        calcPageSize(scope, false);
        outParams.cache.push(result);
        if (outParams.openCollection) {
          if (outParams.newCollectionUrl) {
            var i = 0;
            while (result[i].url && result[i].url.indexOf(outParams.newCollectionUrl) === -1) i++;

            scope.clickThumb(result[i]);
            outParams.newCollectionUrl = null;
          } else if (result.length) {
            scope.clickThumb(result[0]);
          }
        }

        if (callback) {
          callback();
        }
      });
      return outParams;
    } else if (outParams.openCollection) {
      outParams.openCollection = false;
    } else if (scope.$hide) {
      scope.$hide();
      $rootScope.searchModal = false;
    } else if (util.isMobile) {
      $('#explorerModal').modal('hide');
    }

    if ((item.isFGImage && item.imageSet && scope.lookAt !== 'Sky') || item.isSurvey) {
      if (item.guid && item.guid.toLowerCase().indexOf('mars.') == -1) {
        scope.setLookAt('Sky', item.get_name(), true, item.isSurvey);
      }
      if (item.isSurvey) {
        scope.setSurveyBg(item.get_name(), item);
      } else {
        scope.setForegroundImage(item);
      }
      if (scope.$hide) {
        scope.$hide();
        $rootScope.searchModal = false;
      }
      return outParams;
    }
    else if (item.isPanorama) {
      scope.setLookAt('Panorama', item.get_name());
    } else if (item.isEarth) {
      scope.setLookAt('Earth', item.get_name());
    } else if (util.getIsPlanet(item)) {

      if (scope.lookAt === 'Sky') {
        //var c = item.get_camParams();
        //c.zoom = 0.1;

        wwtlib.WWTControl.singleton.gotoTarget3(item.get_camParams());
        return outParams;

      } else {
        item._camParam.zoom = 360;
        //c.angle = 360;
      }
      if (scope.lookAt !== 'SolarSystem') {

        scope.setLookAt('Planet', item._name || '');
      }
    }
    if ((ss.canCast(item, wwtlib.Place) || item.isEarth) && !item.isSurvey) {
      scope.setForegroundImage(item);
    }
    if (ss.canCast(item, wwtlib.Tour)) {
      scope.playTour(item.get_tourUrl());
    }
    return outParams;

    /*var exUranus = {
      "_camParams": {
        "lat": -4.426167041642662,
        "lng": -351.77882519031607,
        "zoom": 0.1,
        "rotation": 0,
        "angle": 0,
        "raDec": true,
        "opacity": 100,
        "target": 65536,
        "viewTarget": {"x": 0, "y": 0, "z": 0},
        "targetReferenceFrame": ""
      },
      "_location3d": {"x": 0, "y": 0, "z": 0},
      "htmlDescription": "",
      "_constellation": "AQR",
      "_classification": 536870912,
      "_type": 2,
      "_magnitude": 0,
      "_distnace": 0,
      "angularSize": 0.1,
      "_thumbNail": null,
      "_studyImageset": null,
      "_backgroundImageSet": null,
      "_searchDistance": 0,
      "_elevation": 50,
      "_name": "Uranus",
      "guid": "Solar System (Sky).8",
      "thumb": "http://cdn.worldwidetelescope.org/wwtweb/thumbnail.aspx?name=uranus",
      "isPlanet": false,
      "isFGImage": true,
      "$$hashKey": "object:832",
      "imageSet": null
    }*/
  };

  function calcPageSize(scope, isContextPanel) {
    var list = scope.collection;
    var tnWid = 116;
    var winWid = $(window).width();

    if (isContextPanel && (scope.lookAt === 'Sky' || scope.lookAt === 'SolarSystem')) {
      winWid = winWid - 216; //angular.element('body.desktop .fov-panel').width();
    }
    scope.pageSize = util.isMobile ? 99999 : Math.floor(winWid / tnWid);

    if (scope.expanded) {
      scope.pageSize *= 5;
    }
    var listLength = list ? list.length : 2;
    $timeout(function () {
      scope.pageCount = Math.ceil(listLength / scope.pageSize);
      spliceOnePage(scope);
    }, 10);
  };

  function goBack(scope) {
    $('body').append($('#researchMenu'));
    scope.currentPage = scope.currentPage === 0 ? scope.currentPage : scope.currentPage - 1;
    return spliceOnePage(scope);
  };

  function goFwd(scope) {
    $('body').append($('#researchMenu'));
    scope.currentPage = scope.currentPage === scope.pageCount - 1 ? scope.currentPage : scope.currentPage + 1;
    return spliceOnePage(scope);
  };

  function spliceOnePage(scope) {
    if (scope.collection) {
      var start = scope.currentPage * scope.pageSize;
      scope.collectionPage = scope.collection.slice(start, start + scope.pageSize);
    }
  };

  return api;

}]);

<|endoftext|>


<commit_before>
//Funciones que se ejcutan en el index.html

var subastasDeTerceros = [];
var idUsuario;
var misSubastas = [];

function initialize() {
  $('#modalLogin').modal('show');
}

function renderizarSubastas(){
  $.each(subastasDeTerceros[0],function(index, unaSubasta){
    console.log(unaSubasta);
        renderizarUnaSubasta(unaSubasta);
  });
}

function renderizarUnaSubasta(subasta){
  var vistaSubasta = sprintf("
");
  //  var vistaSubasta = sprintf("
");
  vistaSubasta += sprintf("
");
  vistaSubasta += sprintf("
", subasta.id);
  vistaSubasta += sprintf("
%s

$ %s.

Finalizada: %s.

" +
        "<button class=\"btn btn-primary\" onclick=\"ofertar(%s)\"> Ofertar (+ $2)! </button>
",
        subasta.titulo, subasta.mejor_oferta, subasta.terminada, subasta.id);
  vistaSubasta += "

";
  $('#subastasActuales').append(vistaSubasta);

}

/*Cuando oferto,y sale todo bien, cambiar el valor del monto actual*/
function renderizarOfertaEnSubasta(idOferta){
  console.log("A punto de renderizar el cambio del monto actual de la subasta");

}

function ofertar(idSubasta){

  console.log("A punto de ofertar...");
  var oferta = armarOferta(idSubasta);
  $.ajax({
    headers: { 'id-usuario': idUsuario },
    type: "POST",
    contentType: "application/json",
    data: JSON.stringify(oferta),
    url:   location.origin + "/api/subastas/"+ idSubasta + "/ofertas",
    success: function (data) {
      console.log("Se Oferto Correctamente.");
      renderizarOfertaEnSubasta(oferta.id);
    },
    error: function(jqXHR, textStatus, errorThrown){
      console.log("Ofertar - Hubo un error en el servidor");
      console.log(JSON.stringify(jqXHR) + ". " + JSON.stringify(textStatus) + ". " + JSON.stringify(errorThrown));
    }
  });

}

function armarOferta(idSubasta){
  var subasta = $.grep(subastasDeTerceros, function(elem){ return elem.id === idSubasta; });
  return {precio: subasta.mejor_oferta+2, comprador: idUsuario};
}

function crearNuevaSubasta(){
  console.log("A punto de crear nueva subasta...");
  var subasta = armarSubasta();
  console.log("contenido del json subasta:" + JSON.stringify(subasta));
  $.ajax({
    headers: { 'id-usuario': idUsuario },
    type: "POST",
    contentType: "application/json",
    data: JSON.stringify(subasta),
    url: location.origin + "/api/subastas",
    success: function (data) {
      console.log("Se Creo al Subasta Correctamente.");
      vaciarForm();
      agregarAlDivMisSubastas(subasta);
      misSubastas.push(subasta);
    },
    error: function(jqXHR, textStatus, errorThrown){
      console.log("Crear Subasta - Hubo un error en el servidor");
      console.log(JSON.stringify(jqXHR) + ". " + JSON.stringify(textStatus) + ". " + JSON.stringify(errorThrown));
    }
  });
}

function armarSubasta(){
  /*Armar algo del estilo:
  {"data":{"vendedor":"Juan",
  "titulo":"venta heladera",
  "terminada":false,
  "precio_base":200.0,
  "mejor_oferta":null,
  "id":9,"duracion":10}}
  */
  return {
      subasta:{
         vendedor: idUsuario,
         titulo: $("#tituloNuevaSubasta").val(),
         duracion: $("#duracionNuevaSubasta").val(),
         /*O si queres poner el tiempo final
         var xMinutesLater = new Date();
         xMinutesLater.setMinutes(xMinutesLater.getMinutes() + $("#duracionNuevaSubasta").val());*/
         precio_base: $("#precioNuevaSubasta").val(),
         terminada: false
       }
     };
}

function vaciarForm(){
  titulo: $("#tituloNuevaSubasta").val("");
  duracion: $("#duracionNuevaSubasta").val("");
  precioBase: $("#precioNuevaSubasta").val("");

}

function agregarAlDivMisSubastas(unaSubasta){
  var vistaSubasta = sprintf("
", unaSubasta.titulo);
  vistaSubasta += sprintf("%s", unaSubasta.titulo);
  vistaSubasta += sprintf("
");
  vistaSubasta += sprintf("<button class=\"btn btn-default\" onclick=\"cancelarSubasta('%s')\">Cancelar Subasta</button>",unaSubasta.titulo);
  vistaSubasta += "

";
  $('#misSubastas').append(vistaSubasta);

}

function login(){
  var nombreUsuario = $("#nombreUsuario").val();
  $("#nombreUsuarioLogueado").text($("#nombreUsuario").val());
  idUsuario = $("#nombreUsuarioLogueado").text();
  $('#modalLogin').modal('toggle');

  mostrarContenido();
}

function mostrarContenido(){
 /* subastasDeTerceros.push({id: 1, titulo:"pelota futbol", montoActual: 20.00});
  subastasDeTerceros.push({id: 2, titulo:"remera", montoActual: 50.00});
  /*Cambiar esas subastas de arriba por una peticion ajax al servidor despues de haberse logueado*/
  $.ajax({
    headers: { 'id-usuario': idUsuario },
    type: "GET",
    contentType: "application/json",
    url: "/api/subastas",
    success: function (data) {
      console.log("Se obtuvieron correctamente las subastas.");
      $.each(data,function(index, unaSubasta){
          subastasDeTerceros.push(unaSubasta);
          renderizarSubastas(); //Descomentar este y borrar el otro renderizar que esta de prueba
      });

    },
    error: function(jqXHR, textStatus, errorThrown){
      console.log("Get de subastas - Hubo un error en el servidor");
      console.log(JSON.stringify(jqXHR) + ". " + JSON.stringify(textStatus) + ". " + JSON.stringify(errorThrown));
    }
  })
}

function cancelarSubasta(titulo){

  $.ajax({
    headers: { 'id-usuario': idUsuario },
    type: "POST",
    url: location.origin + "/api/subastas/" + titulo + "/cancelar",
    success: function (data) {
      console.log("Se Cancelo la Subasta Correctamente.");
      eliminarMiSubasta(titulo);
    },
    error: function(jqXHR, textStatus, errorThrown){
      console.log("Crear Subasta - Hubo un error en el servidor");
      console.log(JSON.stringify(jqXHR) + ". " + JSON.stringify(textStatus) + ". " + JSON.stringify(errorThrown));
    }
  });

}

function eliminarMiSubasta(titulo){
  $("#div"+titulo).remove();
  misSubastas = $.grep(misSubastas, function(elem, index) {
   return elem.titulo != titulo;
  });

}

/* Un comprador A se registra.
    un nombre
    una forma de contacto

Un vendedor crea una subasta, con la siguiente información
Un título
Un precio base (que puede ser cero)
La duración máxima de la subasta
*/

function sprintf( format ){
  for( var i=1; i < arguments.length; i++ ) {
    format = format.replace( /%s/, arguments[i] );
  }
  return format;
}
<commit_msg>ahora renderiza bien, corregir los css que queda todo desfasado<commit_after>
//Funciones que se ejcutan en el index.html

var subastasDeTerceros = [];
var idUsuario;
var misSubastas = [];

function initialize() {
  $('#modalLogin').modal('show');
}

function renderizarSubastas(){
  $.each(subastasDeTerceros[0],function(index, unaSubasta){
    console.log(unaSubasta);
        renderizarUnaSubasta(unaSubasta);
  });
}

function renderizarUnaSubasta(subasta){
  var vistaSubasta = sprintf("
");
  //  var vistaSubasta = sprintf("
");
  if (subasta.mejor_oferta == null) subasta.mejor_oferta = subasta.precio_base;
  vistaSubasta += sprintf("
");
  vistaSubasta += sprintf("
", subasta.id);
  vistaSubasta += sprintf("
%s

$ %s.

Finalizada: %s.

" +
        "<button class=\"btn btn-primary\" onclick=\"ofertar(%s)\"> Ofertar (+ $2)! </button>
",
        subasta.titulo, subasta.mejor_oferta, subasta.terminada, subasta.id);
  vistaSubasta += "

";
  $('#subastasActuales').append(vistaSubasta);

}

/*Cuando oferto,y sale todo bien, cambiar el valor del monto actual*/
function renderizarOfertaEnSubasta(idOferta){
  console.log("A punto de renderizar el cambio del monto actual de la subasta");

}

function ofertar(idSubasta){

  console.log("A punto de ofertar...");
  var oferta = armarOferta(idSubasta);
  $.ajax({
    headers: { 'id-usuario': idUsuario },
    type: "POST",
    contentType: "application/json",
    data: JSON.stringify(oferta),
    url:   location.origin + "/api/subastas/"+ idSubasta + "/ofertas",
    success: function (data) {
      console.log("Se Oferto Correctamente.");
      renderizarOfertaEnSubasta(oferta.id);
    },
    error: function(jqXHR, textStatus, errorThrown){
      console.log("Ofertar - Hubo un error en el servidor");
      console.log(JSON.stringify(jqXHR) + ". " + JSON.stringify(textStatus) + ". " + JSON.stringify(errorThrown));
    }
  });

}

function armarOferta(idSubasta){
  var subasta = $.grep(subastasDeTerceros, function(elem){ return elem.id === idSubasta; });
  return {precio: subasta.mejor_oferta+2, comprador: idUsuario};
}

function crearNuevaSubasta(){
  console.log("A punto de crear nueva subasta...");
  var subasta = armarSubasta();
  console.log("contenido del json subasta:" + JSON.stringify(subasta));
  $.ajax({
    headers: { 'id-usuario': idUsuario },
    type: "POST",
    contentType: "application/json",
    data: JSON.stringify(subasta),
    url: location.origin + "/api/subastas",
    success: function (data) {
      console.log("Se Creo al Subasta Correctamente.");
      vaciarForm();
      agregarAlDivMisSubastas(subasta);
      misSubastas.push(subasta);
    },
    error: function(jqXHR, textStatus, errorThrown){
      console.log("Crear Subasta - Hubo un error en el servidor");
      console.log(JSON.stringify(jqXHR) + ". " + JSON.stringify(textStatus) + ". " + JSON.stringify(errorThrown));
    }
  });
}

function armarSubasta(){
  /*Armar algo del estilo:
  {"data":{"vendedor":"Juan",
  "titulo":"venta heladera",
  "terminada":false,
  "precio_base":200.0,
  "mejor_oferta":null,
  "id":9,"duracion":10}}
  */
  return {
      subasta:{
         vendedor: idUsuario,
         titulo: $("#tituloNuevaSubasta").val(),
         duracion: $("#duracionNuevaSubasta").val(),
         /*O si queres poner el tiempo final
         var xMinutesLater = new Date();
         xMinutesLater.setMinutes(xMinutesLater.getMinutes() + $("#duracionNuevaSubasta").val());*/
         precio_base: $("#precioNuevaSubasta").val(),
         terminada: false
       }
     };
}

function vaciarForm(){
  titulo: $("#tituloNuevaSubasta").val("");
  duracion: $("#duracionNuevaSubasta").val("");
  precioBase: $("#precioNuevaSubasta").val("");

}

function agregarAlDivMisSubastas(unaSubasta){
  var vistaSubasta = sprintf("
", unaSubasta.titulo);
  vistaSubasta += sprintf("%s", unaSubasta.titulo);
  vistaSubasta += sprintf("
");
  vistaSubasta += sprintf("<button class=\"btn btn-default\" onclick=\"cancelarSubasta('%s')\">Cancelar Subasta</button>",unaSubasta.titulo);
  vistaSubasta += "

";
  $('#misSubastas').append(vistaSubasta);

}

function login(){
  var nombreUsuario = $("#nombreUsuario").val();
  $("#nombreUsuarioLogueado").text($("#nombreUsuario").val());
  idUsuario = $("#nombreUsuarioLogueado").text();
  $('#modalLogin').modal('toggle');

  mostrarContenido();
}

function mostrarContenido(){
 /* subastasDeTerceros.push({id: 1, titulo:"pelota futbol", montoActual: 20.00});
  subastasDeTerceros.push({id: 2, titulo:"remera", montoActual: 50.00});
  /*Cambiar esas subastas de arriba por una peticion ajax al servidor despues de haberse logueado*/
  $.ajax({
    headers: { 'id-usuario': idUsuario },
    type: "GET",
    contentType: "application/json",
    url: "/api/subastas",
    success: function (data) {
      console.log("Se obtuvieron correctamente las subastas.");
      $.each(data,function(index, unaSubasta){
          subastasDeTerceros.push(unaSubasta);
          renderizarSubastas(); //Descomentar este y borrar el otro renderizar que esta de prueba
      });

    },
    error: function(jqXHR, textStatus, errorThrown){
      console.log("Get de subastas - Hubo un error en el servidor");
      console.log(JSON.stringify(jqXHR) + ". " + JSON.stringify(textStatus) + ". " + JSON.stringify(errorThrown));
    }
  })
}

function cancelarSubasta(titulo){

  $.ajax({
    headers: { 'id-usuario': idUsuario },
    type: "POST",
    url: location.origin + "/api/subastas/" + titulo + "/cancelar",
    success: function (data) {
      console.log("Se Cancelo la Subasta Correctamente.");
      eliminarMiSubasta(titulo);
    },
    error: function(jqXHR, textStatus, errorThrown){
      console.log("Crear Subasta - Hubo un error en el servidor");
      console.log(JSON.stringify(jqXHR) + ". " + JSON.stringify(textStatus) + ". " + JSON.stringify(errorThrown));
    }
  });

}

function eliminarMiSubasta(titulo){
  $("#div"+titulo).remove();
  misSubastas = $.grep(misSubastas, function(elem, index) {
   return elem.titulo != titulo;
  });

}

/* Un comprador A se registra.
    un nombre
    una forma de contacto

Un vendedor crea una subasta, con la siguiente información
Un título
Un precio base (que puede ser cero)
La duración máxima de la subasta
*/

function sprintf( format ){
  for( var i=1; i < arguments.length; i++ ) {
    format = format.replace( /%s/, arguments[i] );
  }
  return format;
}
<|endoftext|>


<commit_before>var Backbone = require('backbone');
var Legends = require('../../../../../src/geo/map/legends/legends');

describe('geo/map/legends/legends', function () {
  beforeEach(function () {
    this.visModel = new Backbone.Model();
    this.legends = new Legends([
      {
        type: 'bubble',
        title: 'Bubble Legend',
        pre_html: '
PRE Bubble Legend
',
        post_html: '
POST Bubble Legend
',
        definition: {
          color: '#FABADA'
        }
      },
      {
        type: 'category',
        title: 'Categories Legend',
        pre_html: '
PRE Category Legend
',
        post_html: '
POST Category Legend
',
        definition: {
          prefix: 'prefix',
          suffix: 'suffix'
        }
      },
      {
        type: 'choropleth',
        title: 'Choropleth Legend',
        pre_html: '
PRE Choropleth Legend
',
        post_html: '
POST Choropleth Legend
',
        definition: {
          prefix: 'prefix',
          suffix: 'suffix'
        }
      },
      {
        type: 'custom',
        title: 'Custom Legend',
        pre_html: '
PRE Custom Legend
',
        post_html: '
POST Custom Legend
',
        definition: {
          categories: [
            { type: 'color', name: 'Item 1', color: '#CACACA;' },
            { type: 'color', name: 'Item 2', color: '#FABADA;' }
          ]
        }
      }
    ], {
      visModel: this.visModel
    });
  });

  it('should initialize bubble legends correctly', function () {
    expect(this.legends.bubble.get('title')).toEqual('Bubble Legend');
    expect(this.legends.bubble.get('preHTMLSnippet')).toEqual('
PRE Bubble Legend
');
    expect(this.legends.bubble.get('postHTMLSnippet')).toEqual('
POST Bubble Legend
');
    expect(this.legends.bubble.get('fillColor')).toEqual('#FABADA');
  });

  it('should initialize category legends correctly', function () {
    expect(this.legends.category.get('title')).toEqual('Categories Legend');
    expect(this.legends.category.get('preHTMLSnippet')).toEqual('
PRE Category Legend
');
    expect(this.legends.category.get('postHTMLSnippet')).toEqual('
POST Category Legend
');
    expect(this.legends.category.get('prefix')).toEqual('prefix');
    expect(this.legends.category.get('suffix')).toEqual('suffix');
  });

  it('should initialize choropleth legends correctly', function () {
    expect(this.legends.choropleth.get('title')).toEqual('Choropleth Legend');
    expect(this.legends.choropleth.get('preHTMLSnippet')).toEqual('
PRE Choropleth Legend
');
    expect(this.legends.choropleth.get('postHTMLSnippet')).toEqual('
POST Choropleth Legend
');
    expect(this.legends.choropleth.get('prefix')).toEqual('prefix');
    expect(this.legends.choropleth.get('suffix')).toEqual('suffix');
  });

  it('should initialize custom legends correctly', function () {
    expect(this.legends.custom.get('title')).toEqual('Custom Legend');
    expect(this.legends.custom.get('preHTMLSnippet')).toEqual('
PRE Custom Legend
');
    expect(this.legends.custom.get('postHTMLSnippet')).toEqual('
POST Custom Legend
');
    expect(this.legends.custom.get('items').length).toEqual(2);
  });

  it("it should 'show' all legends that have data", function () {
    this.legends = new Legends([
      {
        type: 'bubble',
        title: 'Bubble Legend',
        pre_html: '
PRE Bubble Legend
',
        post_html: '
POST Bubble Legend
',
        definition: {
          color: '#FABADA'
        }
      }
    ], {
      visModel: this.visModel
    });

    expect(this.legends.bubble.isVisible()).toBeTruthy();
    expect(this.legends.category.isVisible()).toBeFalsy();

    this.legends = new Legends([
      {
        type: 'category',
        title: 'Categories Legend',
        pre_html: '
PRE Category Legend
',
        post_html: '
POST Category Legend
',
        definition: {
          prefix: 'prefix',
          suffix: 'suffix'
        }
      }
    ], {
      visModel: this.visModel
    });

    expect(this.legends.bubble.isVisible()).toBeFalsy();
    expect(this.legends.category.isVisible()).toBeTruthy();
  });
});
<commit_msg>Fix legends tests<commit_after>var Backbone = require('backbone');
var Legends = require('../../../../../src/geo/map/legends/legends');
var fakeFactory = require('../../../../helpers/fakeFactory');

describe('geo/map/legends/legends', function () {
  var engineMock;
  beforeEach(function () {
    engineMock = fakeFactory.createEngine();
    this.legends = new Legends([
      {
        type: 'bubble',
        title: 'Bubble Legend',
        pre_html: '
PRE Bubble Legend
',
        post_html: '
POST Bubble Legend
',
        definition: {
          color: '#FABADA'
        }
      },
      {
        type: 'category',
        title: 'Categories Legend',
        pre_html: '
PRE Category Legend
',
        post_html: '
POST Category Legend
',
        definition: {
          prefix: 'prefix',
          suffix: 'suffix'
        }
      },
      {
        type: 'choropleth',
        title: 'Choropleth Legend',
        pre_html: '
PRE Choropleth Legend
',
        post_html: '
POST Choropleth Legend
',
        definition: {
          prefix: 'prefix',
          suffix: 'suffix'
        }
      },
      {
        type: 'custom',
        title: 'Custom Legend',
        pre_html: '
PRE Custom Legend
',
        post_html: '
POST Custom Legend
',
        definition: {
          categories: [
            { type: 'color', name: 'Item 1', color: '#CACACA;' },
            { type: 'color', name: 'Item 2', color: '#FABADA;' }
          ]
        }
      }
    ], {
      engine: engineMock
    });
  });

  it('should initialize bubble legends correctly', function () {
    expect(this.legends.bubble.get('title')).toEqual('Bubble Legend');
    expect(this.legends.bubble.get('preHTMLSnippet')).toEqual('
PRE Bubble Legend
');
    expect(this.legends.bubble.get('postHTMLSnippet')).toEqual('
POST Bubble Legend
');
    expect(this.legends.bubble.get('fillColor')).toEqual('#FABADA');
  });

  it('should initialize category legends correctly', function () {
    expect(this.legends.category.get('title')).toEqual('Categories Legend');
    expect(this.legends.category.get('preHTMLSnippet')).toEqual('
PRE Category Legend
');
    expect(this.legends.category.get('postHTMLSnippet')).toEqual('
POST Category Legend
');
    expect(this.legends.category.get('prefix')).toEqual('prefix');
    expect(this.legends.category.get('suffix')).toEqual('suffix');
  });

  it('should initialize choropleth legends correctly', function () {
    expect(this.legends.choropleth.get('title')).toEqual('Choropleth Legend');
    expect(this.legends.choropleth.get('preHTMLSnippet')).toEqual('
PRE Choropleth Legend
');
    expect(this.legends.choropleth.get('postHTMLSnippet')).toEqual('
POST Choropleth Legend
');
    expect(this.legends.choropleth.get('prefix')).toEqual('prefix');
    expect(this.legends.choropleth.get('suffix')).toEqual('suffix');
  });

  it('should initialize custom legends correctly', function () {
    expect(this.legends.custom.get('title')).toEqual('Custom Legend');
    expect(this.legends.custom.get('preHTMLSnippet')).toEqual('
PRE Custom Legend
');
    expect(this.legends.custom.get('postHTMLSnippet')).toEqual('
POST Custom Legend
');
    expect(this.legends.custom.get('items').length).toEqual(2);
  });

  it("it should 'show' all legends that have data", function () {
    this.legends = new Legends([
      {
        type: 'bubble',
        title: 'Bubble Legend',
        pre_html: '
PRE Bubble Legend
',
        post_html: '
POST Bubble Legend
',
        definition: {
          color: '#FABADA'
        }
      }
    ], {
      engine: engineMock
    });

    expect(this.legends.bubble.isVisible()).toBeTruthy();
    expect(this.legends.category.isVisible()).toBeFalsy();

    this.legends = new Legends([
      {
        type: 'category',
        title: 'Categories Legend',
        pre_html: '
PRE Category Legend
',
        post_html: '
POST Category Legend
',
        definition: {
          prefix: 'prefix',
          suffix: 'suffix'
        }
      }
    ], {
      engine: engineMock
    });

    expect(this.legends.bubble.isVisible()).toBeFalsy();
    expect(this.legends.category.isVisible()).toBeTruthy();
  });
});
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Steam Fast Farm Card
// @namespace    https://github.com/Kasp42/SteamSummer2016
// @version      1.2
// @description  Fast Farm Card From Steam Summer 2016
// @author       Kasper (Telegram: https://telegram.me/kasp42)
// @match        http://store.steampowered.com/explore*
// @grant        none
// @updateURL    https://raw.githubusercontent.com/Kasp42/SteamSummer2016/master/fast-farm-card.js
// @downloadURL  https://raw.githubusercontent.com/Kasp42/SteamSummer2016/master/fast-farm-card.js
// ==/UserScript==

(function() {
    $J('.discovery_queue_apps h2').append('Get Cards');
	$J('#AutoFarmCard').on('click',function(){
		ViewList(0);
		$J('#AutoFarmCard span').text('Wait(0/3)...');
	});
})();

var ViewList = function(queueNumber){
	$J('#AutoFarmCard span').text('Wait('+(++queueNumber)+'/3)...');
    jQuery.post('http://store.steampowered.com/explore/generatenewdiscoveryqueue', {sessionid: g_sessionID, queuetype: 0}).done(function(data){
        var requests = [];
        for(var i = 0; i < data.queue.length; i++)
            requests.push( jQuery.post( 'http://store.steampowered.com/app/'+data.queue[i], { appid_to_clear_from_queue: data.queue[i], sessionid: g_sessionID } ) );
        jQuery.when.apply(jQuery, requests).done(function(){
            if(queueNumber < 3) ViewList(queueNumber);
            else {
            	$J('#AutoFarmCard span').text('Done!');
            	setTimeout(function(){window.location.reload();},1500);
            }
        }).fail(function(){ViewList(queueNumber)});
    }).fail(function(){ViewList(queueNumber)});;
};
<commit_msg>Update fast-farm-card.user.js<commit_after>// ==UserScript==
// @name         Steam Fast Farm Card
// @namespace    https://github.com/Kasp42/SteamSummer2016
// @version      1.2
// @description  Fast Farm Card From Steam Summer 2016
// @author       Kasper (Telegram: https://telegram.me/kasp42)
// @match        http://store.steampowered.com/explore*
// @grant        none
// @updateURL    https://raw.githubusercontent.com/Kasp42/SteamSummer2016/master/fast-farm-card.user.js
// @downloadURL  https://raw.githubusercontent.com/Kasp42/SteamSummer2016/master/fast-farm-card.user.js
// ==/UserScript==

(function() {
    $J('.discovery_queue_apps h2').append('Get Cards');
	$J('#AutoFarmCard').on('click',function(){
		ViewList(0);
		$J('#AutoFarmCard span').text('Wait(0/3)...');
	});
})();

var ViewList = function(queueNumber){
	$J('#AutoFarmCard span').text('Wait('+(++queueNumber)+'/3)...');
    jQuery.post('http://store.steampowered.com/explore/generatenewdiscoveryqueue', {sessionid: g_sessionID, queuetype: 0}).done(function(data){
        var requests = [];
        for(var i = 0; i < data.queue.length; i++)
            requests.push( jQuery.post( 'http://store.steampowered.com/app/'+data.queue[i], { appid_to_clear_from_queue: data.queue[i], sessionid: g_sessionID } ) );
        jQuery.when.apply(jQuery, requests).done(function(){
            if(queueNumber < 3) ViewList(queueNumber);
            else {
            	$J('#AutoFarmCard span').text('Done!');
            	setTimeout(function(){window.location.reload();},1500);
            }
        }).fail(function(){ViewList(queueNumber)});
    }).fail(function(){ViewList(queueNumber)});;
};
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Fastmail Interceptor
// @namespace    http://cepp.at/projects/PGE
// @version      0.1
// @description  Pretty Good Email -- A Pretty Good Email Encryptor based on Pretty Good Protction (PGP).
// @author       Put some names that sound important here.
// @match        https://www.fastmail.com/*
// @grant        none
// ==/UserScript==
chrome.permissions.request({
  permissions: [
    "https://*.keybase.io",
    "http://*.keybase.io",
    "http://keybase.io"
  ]
});
var oldXMLHttpRequest = XMLHttpRequest;
XMLHttpRequest = function() {
  var request = new oldXMLHttpRequest();
  var facade = {
    addEventListener: function(type, listener) {
      return request.addEventListener(type, listener);
    },
    setRequestHeader: function() {
      return request.setRequestHeader.apply(request, arguments);
    },
    getAllResponseHeaders: function() {
      return request.getAllResponseHeaders.apply(request, arguments);
    },
    getResponseHeader: function() {
      return request.getResponseHeader.apply(request, arguments);
    },
    abort: function() {
      return request.abort.apply(request, arguments);
    },
    open: function(method, url, async, user, password) {
      facade.__url = url;
      request.open(method, url, async, user, password);
    },
    send: function(data) {
      request.onreadystatechange = function(event) {
        facade.readyState = request.readyState;
        facade.status = request.status;
        //                facade.response = request.response;
        if (request.responseText) {
          facade.responseText = transform(request.responseText);
        }
        facade.responseType = request.responseType;
        if (facade.onreadystatechange) {
          return facade.onreadystatechange(event);
        }
      };
      return request.send(data);
    }
  };
  return facade;
};

//Components.utils.import("resource://app/window.openpgp");

var transform = function(data) {
  var resp = JSON.parse(data);
  for (var i = resp.length; i--;) {
    if (resp[i][0] === "messageDetails") {
      var detailsList = resp[i][1].detailsList;
      for (var j = detailsList.length; j--;) {
        detailsList[j].body = decrypt(detailsList[j].body);
      }
    }
  }
  return JSON.stringify(resp);
};

//Everything up to here works most certainly.

var getUser = function(user) {
  return user;
}

var getRecipient = function(to){
  return to;
}

var decrypt = function(s) {
  var openpgp = require('openpgp');

  /*Import a keymanager from a Public Key. */
  var user_pgp_key = "-----BEGIN PGP PUBLIC ... etc.";

  kbpgp.KeyManager.import_from_armored_pgp({
    armored: user_pgp_key
  }, function(err, user) {
    if (!err) {
      console.log(user + " is loaded");
    }
  });
  /* end import. */

  /* Import a keymangeer from a Private Key */
  var user_public_key = "-----BEGIN PGP PUBLIC ... etc.";
  var user_private_key = "-----BEGIN PGP PRIVATE ... etc.";
  var user_passphrase = "ovarian fred savage ";

  kbpgp.KeyManager.import_from_armored_pgp({
    armored: user_public_key
  }, function(err, user) {
    if (!err) {
      user.merge_pgp_private({
        armored: user_private_key
      }, function(err) {
        if (!err) {
          if (user.is_pgp_locked()) {
            user.unlock_pgp({
              passphrase: user_passphrase
            }, function(err) {
              if (!err) {
                console.log("Loaded private key with passphrase");
              }
            });
          } else {
            console.log("Loaded private key w/o passphrase");
          }
        }
      });
    }
  });
  /*End addition of new Private Key from the public key. */

  /*Straight KBPGP.js code to interface with the Kayebase API */
  var params = {
    msg: 'resp',
    encrypt_for: getRecipient,
    sign_with: getUser
  };

  kbpgp.box(params, function(err, result_string, result_buffer) {
    console.log(err, result_string, result_buffer);
  });
  /*End KBPGP.js code implementation. */

  var keypair = openpgp.generateKeyPair(1, 1024, "testUser");
  var privKeys = openpgp.key.readArmored(keypair.privateKeyArmored);
  var publicKeys = openpgp.key.readArmored(keypair.publicKeyArmored);
  var privKey = privKeys.keys[0];
  var pubKey = publicKeys.keys[0];
  privKey.decrypt();
  var clearSignedArmor = openpgp.signClearMessage(privKey, "test text");
  openpgp.encryptMessage(publicKey.keys, 'body').then(function(pgpMessage));
  console.log("clearSignedArmor " + clearSignedArmor);
  console.log("verify " + openpgp.verifyClearSignedMessage([pubKey], clearSignedArmor));

  /*var options = {
      numBits: 2048,
      function generateTrueUser = (user) {
        var keybaseCaller = new XMLHttpRequest();
        keybaseCaller("GET", chrome.extension.getURL('https://keybase.io/_/api/1.0/user/lookup.json' + 'user'), true);
        keybaseCaller.onreadystatechange = function(){
        if (keybaseCaller.readyState == 4){
        var resp = JSON.parse(keybaseCaller.responseText);
      }
      }
        keybaseCaller.send();
        )
        var actualUser = them.username;
      }
      userId: actualUser,
      passphrase: password,
    };
*/

  //Decrypting the body.
  var key = '-----BEGIN PGP PRIVATE KEY BLOCK ... END PGP PRIVATE KEY BLOCK-----';
  var privateKey = openpgp.key.readArmored(key).keys[0];
  privateKey.decrypt('passphrase');

  var pgpMessage = '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----';
  pgpMessage = openpgp.message.readArmored(pgpMessage);

  return openpgp.decryptMessage(privateKey, pgpMessage).then(function(plaintext) {
    // success
  }).catch(function(error) {
    // failure
  });
};
/*
var oldWindowEventSource = window.EventSource;
window.EventSource = function(url) {
    console.log(url);
    var source = new oldWindowEventSource(url);
    return {
        addEventListener: function(n, f, b) {
            source.addEventListener(n, new function(e) {
                console.log(arguments);
            }, b);
        }
    };
};
*/
<commit_msg>Correcting some errors in comments.<commit_after>// ==UserScript==
// @name         Fastmail Interceptor
// @namespace    http://cepp.at/projects/PGE
// @version      0.1
// @description  Pretty Good Email -- A Pretty Good Email Encryptor based on Pretty Good Protction (PGP).
// @author       Put some names that sound important here.
// @match        https://www.fastmail.com/*
// @grant        none
// ==/UserScript==
chrome.permissions.request({
  permissions: [
    "https://*.keybase.io",
    "http://*.keybase.io",
    "http://keybase.io"
  ]
});
var oldXMLHttpRequest = XMLHttpRequest;
XMLHttpRequest = function() {
  var request = new oldXMLHttpRequest();
  var facade = {
    addEventListener: function(type, listener) {
      return request.addEventListener(type, listener);
    },
    setRequestHeader: function() {
      return request.setRequestHeader.apply(request, arguments);
    },
    getAllResponseHeaders: function() {
      return request.getAllResponseHeaders.apply(request, arguments);
    },
    getResponseHeader: function() {
      return request.getResponseHeader.apply(request, arguments);
    },
    abort: function() {
      return request.abort.apply(request, arguments);
    },
    open: function(method, url, async, user, password) {
      facade.__url = url;
      request.open(method, url, async, user, password);
    },
    send: function(data) {
      request.onreadystatechange = function(event) {
        facade.readyState = request.readyState;
        facade.status = request.status;
        //                facade.response = request.response;
        if (request.responseText) {
          facade.responseText = transform(request.responseText);
        }
        facade.responseType = request.responseType;
        if (facade.onreadystatechange) {
          return facade.onreadystatechange(event);
        }
      };
      return request.send(data);
    }
  };
  return facade;
};

//Components.utils.import("resource://app/window.openpgp");

var transform = function(data) {
  var resp = JSON.parse(data);
  for (var i = resp.length; i--;) {
    if (resp[i][0] === "messageDetails") {
      var detailsList = resp[i][1].detailsList;
      for (var j = detailsList.length; j--;) {
        detailsList[j].body = decrypt(detailsList[j].body);
      }
    }
  }
  return JSON.stringify(resp);
};

//Everything up to here works most certainly.

var getUser = function(user) {
  return user;
}

var getRecipient = function(to){
  return to;
}

var decrypt = function(s) {
  var openpgp = require('openpgp');

  /*Import a keymanager from a Public Key. Uses Keybase API caller. */
  var user_pgp_key = "-----BEGIN PGP PUBLIC ... etc.";

  kbpgp.KeyManager.import_from_armored_pgp({
    armored: user_pgp_key
  }, function(err, user) {
    if (!err) {
      console.log(user + " is loaded");
    }
  });
  /* end import. */

  /* Import a keymanger from a Private Key using Keybase API caller.*/
  var user_public_key = "-----BEGIN PGP PUBLIC ... etc.";
  var user_private_key = "-----BEGIN PGP PRIVATE ... etc.";
  var user_passphrase = "ovarian fred savage ";

  kbpgp.KeyManager.import_from_armored_pgp({
    armored: user_public_key
  }, function(err, user) {
    if (!err) {
      user.merge_pgp_private({
        armored: user_private_key
      }, function(err) {
        if (!err) {
          if (user.is_pgp_locked()) {
            user.unlock_pgp({
              passphrase: user_passphrase
            }, function(err) {
              if (!err) {
                console.log("Loaded private key with passphrase");
              }
            });
          } else {
            console.log("Loaded private key w/o passphrase");
          }
        }
      });
    }
  });
  /*End addition of new Private Key from the public key. */

  /*Straight KBPGP.js code to interface with the Kayebase API */
  var params = {
    msg: 'resp',
    encrypt_for: getRecipient,
    sign_with: getUser
  };

  kbpgp.box(params, function(err, result_string, result_buffer) {
    console.log(err, result_string, result_buffer);
  });
  /*End KBPGP.js code implementation. */

  var keypair = openpgp.generateKeyPair(1, 1024, "testUser");
  var privKeys = openpgp.key.readArmored(keypair.privateKeyArmored);
  var publicKeys = openpgp.key.readArmored(keypair.publicKeyArmored);
  var privKey = privKeys.keys[0];
  var pubKey = publicKeys.keys[0];
  privKey.decrypt();
  var clearSignedArmor = openpgp.signClearMessage(privKey, "test text");
  openpgp.encryptMessage(publicKey.keys, 'body').then(function(pgpMessage));
  console.log("clearSignedArmor " + clearSignedArmor);
  console.log("verify " + openpgp.verifyClearSignedMessage([pubKey], clearSignedArmor));

  /*var options = {
      numBits: 2048,
      function generateTrueUser = (user) {
        var keybaseCaller = new XMLHttpRequest();
        keybaseCaller("GET", chrome.extension.getURL('https://keybase.io/_/api/1.0/user/lookup.json' + 'user'), true);
        keybaseCaller.onreadystatechange = function(){
        if (keybaseCaller.readyState == 4){
        var resp = JSON.parse(keybaseCaller.responseText);
      }
      }
        keybaseCaller.send();
        )
        var actualUser = them.username;
      }
      userId: actualUser,
      passphrase: password,
    };
*/

  //Decrypting the body.
  var key = '-----BEGIN PGP PRIVATE KEY BLOCK ... END PGP PRIVATE KEY BLOCK-----';
  var privateKey = openpgp.key.readArmored(key).keys[0];
  privateKey.decrypt('passphrase');

  var pgpMessage = '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----';
  pgpMessage = openpgp.message.readArmored(pgpMessage);

  return openpgp.decryptMessage(privateKey, pgpMessage).then(function(plaintext) {
    // success
  }).catch(function(error) {
    // failure
  });
};
/*
var oldWindowEventSource = window.EventSource;
window.EventSource = function(url) {
    console.log(url);
    var source = new oldWindowEventSource(url);
    return {
        addEventListener: function(n, f, b) {
            source.addEventListener(n, new function(e) {
                console.log(arguments);
            }, b);
        }
    };
};
*/
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     248
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v249 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     249
// ==/UserScript==
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     87
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v88 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     88
// ==/UserScript==
<|endoftext|>