<commit_before>IonicModule
.controller('$ionicNavView', [
  '$scope',
  '$element',
  '$attrs',
  '$compile',
  '$controller',
  '$ionicNavBarDelegate',
  '$ionicNavViewDelegate',
  '$ionicHistory',
  '$ionicViewSwitcher',
  '$ionicConfig',
  '$ionicScrollDelegate',
function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher, $ionicConfig, $ionicScrollDelegate) {

  var DATA_ELE_IDENTIFIER = '$eleId';
  var DATA_DESTROY_ELE = '$destroyEle';
  var DATA_NO_CACHE = '$noCache';
  var VIEW_STATUS_ACTIVE = 'active';
  var VIEW_STATUS_CACHED = 'cached';

  var self = this;
  var direction;
  var isPrimary = false;
  var navBarDelegate;
  var activeEleId;
  var navViewAttr = $ionicViewSwitcher.navViewAttr;
  var disableRenderStartViewId, disableAnimation;

  self.scope = $scope;
  self.element = $element;

  self.init = function() {
    var navViewName = $attrs.name || '';

    // Find the details of the parent view directive (if any) and use it
    // to derive our own qualified view name, then hang our own details
    // off the DOM so child directives can find it.
    var parent = $element.parent().inheritedData('$uiView');
    var parentViewName = ((parent && parent.state) ? parent.state.name : '');
    if (navViewName.indexOf('@') < 0) navViewName = navViewName + '@' + parentViewName;

    var viewData = { name: navViewName, state: null };
    $element.data('$uiView', viewData);

    var deregisterInstance = $ionicNavViewDelegate._registerInstance(self, $attrs.delegateHandle);
    $scope.$on('$destroy', function() {
      deregisterInstance();

      // ensure no scrolls have been left frozen
      if (self.isSwipeFreeze) {
        $ionicScrollDelegate.freezeAllScrolls(false);
      }
    });

    $scope.$on('$ionicHistory.deselect', self.cacheCleanup);
    $scope.$on('$ionicTabs.top', onTabsTop);
    $scope.$on('$ionicSubheader', onBarSubheader);

    $scope.$on('$ionicTabs.beforeLeave', onTabsLeave);
    $scope.$on('$ionicTabs.afterLeave', onTabsLeave);
    $scope.$on('$ionicTabs.leave', onTabsLeave);

    ionic.Platform.ready(function() {
      self.initSwipeBack();
    });

    return viewData;
  };

  self.register = function(viewLocals) {
    var leavingView = extend({}, $ionicHistory.currentView());

    // register that a view is coming in and get info on how it should transition
    var registerData = $ionicHistory.register($scope, viewLocals);

    // update which direction
    self.update(registerData);

    // begin rendering and transitioning
    var enteringView = $ionicHistory.getViewById(registerData.viewId) || {};

    var renderStart = (disableRenderStartViewId !== registerData.viewId);
    self.render(registerData, viewLocals, enteringView, leavingView, renderStart, true);
  };

  self.update = function(registerData) {
    // always reset that this is the primary navView
    isPrimary = true;

    // remember what direction this navView should use
    // this may get updated later by a child navView
    direction = registerData.direction;

    var parentNavViewCtrl = $element.parent().inheritedData('$ionNavViewController');
    if (parentNavViewCtrl) {
      // this navView is nested inside another one
      // update the parent to use this direction and not
      // the other it originally was set to

      // inform the parent navView that it is not the primary navView
      parentNavViewCtrl.isPrimary(false);

      if (direction === 'enter' || direction === 'exit') {
        // they're entering/exiting a history
        // find parent navViewController
        parentNavViewCtrl.direction(direction);

        if (direction === 'enter') {
          // reset the direction so this navView doesn't animate
          // because it's parent will
          direction = 'none';
        }
      }
    }
  };

  self.render = function(registerData, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
    // register the view and figure out where it lives in the various
    // histories and nav stacks, along with how views should enter/leave
    var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView, renderStart, renderEnd);

    // init the rendering of views for this navView directive
    switcher.init(registerData, function() {
      // the view is now compiled, in the dom and linked, now lets transition the views.
      // this uses a callback incase THIS nav-view has a nested nav-view, and after the NESTED
      // nav-view links, the NESTED nav-view would update which direction THIS nav-view should use

      // kick off the transition of views
      switcher.transition(self.direction(), registerData.enableBack, !disableAnimation);

      // reset private vars for next time
      disableRenderStartViewId = disableAnimation = null;
    });

  };

  self.beforeEnter = function(transitionData) {
    if (isPrimary) {
      // only update this nav-view's nav-bar if this is the primary nav-view
      navBarDelegate = transitionData.navBarDelegate;
      var associatedNavBarCtrl = getAssociatedNavBarCtrl();
      associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData);
      navSwipeAttr('');
    }
  };

  self.activeEleId = function(eleId) {
    if (arguments.length) {
      activeEleId = eleId;
    }
    return activeEleId;
  };

  self.transitionEnd = function() {
    var viewElements = $element.children();
    var x, l, viewElement;

    for (x = 0, l = viewElements.length; x < l; x++) {
      viewElement = viewElements.eq(x);

      if (viewElement.data(DATA_ELE_IDENTIFIER) === activeEleId) {
        // this is the active element
        navViewAttr(viewElement, VIEW_STATUS_ACTIVE);

      } else if (navViewAttr(viewElement) === 'leaving' || navViewAttr(viewElement) === VIEW_STATUS_ACTIVE || navViewAttr(viewElement) === VIEW_STATUS_CACHED) {
        // this is a leaving element or was the former active element, or is an cached element
        if (viewElement.data(DATA_DESTROY_ELE) || viewElement.data(DATA_NO_CACHE)) {
          // this element shouldn't stay cached
          $ionicViewSwitcher.destroyViewEle(viewElement);

        } else {
          // keep in the DOM, mark as cached
          navViewAttr(viewElement, VIEW_STATUS_CACHED);

          // disconnect the leaving scope
          ionic.Utils.disconnectScope(viewElement.scope());
        }
      }
    }

    navSwipeAttr('');

    // ensure no scrolls have been left frozen
    if (self.isSwipeFreeze) {
      $ionicScrollDelegate.freezeAllScrolls(false);
    }
  };

  function onTabsLeave(ev, data) {
    var viewElements = $element.children();
    var viewElement, viewScope;

    for (var x = 0, l = viewElements.length; x < l; x++) {
      viewElement = viewElements.eq(x);
      if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
        viewScope = viewElement.scope();
        viewScope && viewScope.$emit(ev.name.replace('Tabs', 'View'), data);
        break;
      }
    }
  }

  self.cacheCleanup = function() {
    var viewElements = $element.children();
    for (var x = 0, l = viewElements.length; x < l; x++) {
      if (viewElements.eq(x).data(DATA_DESTROY_ELE)) {
        $ionicViewSwitcher.destroyViewEle(viewElements.eq(x));
      }
    }
  };

  self.clearCache = function(stateIds) {
    var viewElements = $element.children();
    var viewElement, viewScope, x, l, y, eleIdentifier;

    for (x = 0, l = viewElements.length; x < l; x++) {
      viewElement = viewElements.eq(x);

      if (stateIds) {
        eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER);

        for (y = 0; y < stateIds.length; y++) {
          if (eleIdentifier === stateIds[y]) {
            $ionicViewSwitcher.destroyViewEle(viewElement);
          }
        }
        continue;
      }

      if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {
        $ionicViewSwitcher.destroyViewEle(viewElement);

      } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
        viewScope = viewElement.scope();
        viewScope && viewScope.$broadcast('$ionicView.clearCache');
      }

    }
  };

  self.getViewElements = function() {
    return $element.children();
  };

  self.appendViewElement = function(viewEle, viewLocals) {
    // compile the entering element and get the link function
    var linkFn = $compile(viewEle);

    $element.append(viewEle);

    var viewScope = $scope.$new();

    if (viewLocals && viewLocals.$$controller) {
      viewLocals.$scope = viewScope;
      var controller = $controller(viewLocals.$$controller, viewLocals);
      if (viewLocals.$$controllerAs) {
        viewScope[viewLocals.$$controllerAs] = controller;
      }
      $element.children().data('$ngControllerController', controller);
    }

    linkFn(viewScope);

    return viewScope;
  };

  self.title = function(val) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    associatedNavBarCtrl && associatedNavBarCtrl.title(val);
  };

  /**
   * @ngdoc method
   * @name $ionicNavView#enableBackButton
   * @description Enable/disable if the back button can be shown or not. For
   * example, the very first view in the navigation stack would not have a
   * back view, so the back button would be disabled.
   */
  self.enableBackButton = function(shouldEnable) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    associatedNavBarCtrl && associatedNavBarCtrl.enableBackButton(shouldEnable);
  };

  /**
   * @ngdoc method
   * @name $ionicNavView#showBackButton
   * @description Show/hide the nav bar active back button. If the back button
   * is not possible this will not force the back button to show. The
   * `enableBackButton()` method handles if a back button is even possible or not.
   */
  self.showBackButton = function(shouldShow) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    if (associatedNavBarCtrl) {
      if (arguments.length) {
        return associatedNavBarCtrl.showActiveBackButton(shouldShow);
      }
      return associatedNavBarCtrl.showActiveBackButton();
    }
    return true;
  };

  self.showBar = function(val) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    if (associatedNavBarCtrl) {
      if (arguments.length) {
        return associatedNavBarCtrl.showBar(val);
      }
      return associatedNavBarCtrl.showBar();
    }
    return true;
  };

  self.isPrimary = function(val) {
    if (arguments.length) {
      isPrimary = val;
    }
    return isPrimary;
  };

  self.direction = function(val) {
    if (arguments.length) {
      direction = val;
    }
    return direction;
  };

  self.initSwipeBack = function() {
    var swipeBackHitWidth = $ionicConfig.views.swipeBackHitWidth();
    var viewTransition, associatedNavBarCtrl, backView;
    var deregDragStart, deregDrag, deregRelease;
    var windowWidth, startDragX, dragPoints;
    var cancelData = {};

    function onDragStart(ev) {
      if (!isPrimary) return;

      if ( !$ionicConfig.views.swipeBackEnabled() || ! ionic.Platform.isIOS() ){
        return;
      }

      startDragX = getDragX(ev);
      if (startDragX > swipeBackHitWidth) return;

      backView = $ionicHistory.backView();

      var currentView = $ionicHistory.currentView();

      if (!backView || backView.historyId !== currentView.historyId || currentView.canSwipeBack === false) return;

      if (!windowWidth) windowWidth = window.innerWidth;

      self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(true);

      var registerData = {
        direction: 'back'
      };

      dragPoints = [];

      cancelData = {
        showBar: self.showBar(),
        showBackButton: self.showBackButton()
      };

      var switcher = $ionicViewSwitcher.create(self, registerData, backView, currentView, true, false);
      switcher.loadViewElements(registerData);
      switcher.render(registerData);

      viewTransition = switcher.transition('back', $ionicHistory.enabledBack(backView), true);

      associatedNavBarCtrl = getAssociatedNavBarCtrl();

      deregDrag = ionic.onGesture('drag', onDrag, $element[0]);
      deregRelease = ionic.onGesture('release', onRelease, $element[0]);
    }

    function onDrag(ev) {
      if (isPrimary && viewTransition) {
        var dragX = getDragX(ev);

        dragPoints.push({
          t: Date.now(),
          x: dragX
        });

        if (dragX >= windowWidth - 15) {
          onRelease(ev);

        } else {
          var step = Math.min(Math.max(getSwipeCompletion(dragX), 0), 1);
          viewTransition.run(step);
          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.run(step);
        }

      }
    }

    function onRelease(ev) {
      if (isPrimary && viewTransition && dragPoints && dragPoints.length > 1) {

        var now = Date.now();
        var releaseX = getDragX(ev);
        var startDrag = dragPoints[dragPoints.length - 1];

        for (var x = dragPoints.length - 2; x >= 0; x--) {
          if (now - startDrag.t > 200) {
            break;
          }
          startDrag = dragPoints[x];
        }

        var isSwipingRight = (releaseX >= dragPoints[dragPoints.length - 2].x);
        var releaseSwipeCompletion = getSwipeCompletion(releaseX);
        var velocity = Math.abs(startDrag.x - releaseX) / (now - startDrag.t);

        // private variables because ui-router has no way to pass custom data using $state.go
        disableRenderStartViewId = backView.viewId;
        disableAnimation = (releaseSwipeCompletion < 0.03 || releaseSwipeCompletion > 0.97);

        if (isSwipingRight && (releaseSwipeCompletion > 0.5 || velocity > 0.1)) {
          // complete view transition on release
          var speed = (velocity > 0.5 || velocity < 0.05 || releaseX > windowWidth - 45) ? 'fast' : 'slow';
          navSwipeAttr(disableAnimation ? '' : speed);
          backView.go();
          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.complete(!disableAnimation, speed);

        } else {
          // cancel view transition on release
          navSwipeAttr(disableAnimation ? '' : 'fast');
          disableRenderStartViewId = null;
          viewTransition.cancel(!disableAnimation);
          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast', cancelData);
          disableAnimation = null;
        }

      }

      ionic.offGesture(deregDrag, 'drag', onDrag);
      ionic.offGesture(deregRelease, 'release', onRelease);

      windowWidth = viewTransition = dragPoints = null;

      self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(false);
    }

    function getDragX(ev) {
      return ionic.tap.pointerCoord(ev.gesture.srcEvent).x;
    }

    function getSwipeCompletion(dragX) {
      return (dragX - startDragX) / windowWidth;
    }

    deregDragStart = ionic.onGesture('dragstart', onDragStart, $element[0]);

    $scope.$on('$destroy', function() {
      ionic.offGesture(deregDragStart, 'dragstart', onDragStart);
      ionic.offGesture(deregDrag, 'drag', onDrag);
      ionic.offGesture(deregRelease, 'release', onRelease);
      self.element = viewTransition = associatedNavBarCtrl = null;
    });
  };

  function navSwipeAttr(val) {
    ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
  }

  function onTabsTop(ev, isTabsTop) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    associatedNavBarCtrl && associatedNavBarCtrl.hasTabsTop(isTabsTop);
  }

  function onBarSubheader(ev, isBarSubheader) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    associatedNavBarCtrl && associatedNavBarCtrl.hasBarSubheader(isBarSubheader);
  }

  function getAssociatedNavBarCtrl() {
    if (navBarDelegate) {
      for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
        if ($ionicNavBarDelegate._instances[x].$$delegateHandle == navBarDelegate) {
          return $ionicNavBarDelegate._instances[x];
        }
      }
    }
    return $element.inheritedData('$ionNavBarController');
  }

}]);
<commit_msg>jscs and eslint fixes<commit_after>IonicModule
.controller('$ionicNavView', [
  '$scope',
  '$element',
  '$attrs',
  '$compile',
  '$controller',
  '$ionicNavBarDelegate',
  '$ionicNavViewDelegate',
  '$ionicHistory',
  '$ionicViewSwitcher',
  '$ionicConfig',
  '$ionicScrollDelegate',
function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher, $ionicConfig, $ionicScrollDelegate) {

  var DATA_ELE_IDENTIFIER = '$eleId';
  var DATA_DESTROY_ELE = '$destroyEle';
  var DATA_NO_CACHE = '$noCache';
  var VIEW_STATUS_ACTIVE = 'active';
  var VIEW_STATUS_CACHED = 'cached';

  var self = this;
  var direction;
  var isPrimary = false;
  var navBarDelegate;
  var activeEleId;
  var navViewAttr = $ionicViewSwitcher.navViewAttr;
  var disableRenderStartViewId, disableAnimation;

  self.scope = $scope;
  self.element = $element;

  self.init = function() {
    var navViewName = $attrs.name || '';

    // Find the details of the parent view directive (if any) and use it
    // to derive our own qualified view name, then hang our own details
    // off the DOM so child directives can find it.
    var parent = $element.parent().inheritedData('$uiView');
    var parentViewName = ((parent && parent.state) ? parent.state.name : '');
    if (navViewName.indexOf('@') < 0) navViewName = navViewName + '@' + parentViewName;

    var viewData = { name: navViewName, state: null };
    $element.data('$uiView', viewData);

    var deregisterInstance = $ionicNavViewDelegate._registerInstance(self, $attrs.delegateHandle);
    $scope.$on('$destroy', function() {
      deregisterInstance();

      // ensure no scrolls have been left frozen
      if (self.isSwipeFreeze) {
        $ionicScrollDelegate.freezeAllScrolls(false);
      }
    });

    $scope.$on('$ionicHistory.deselect', self.cacheCleanup);
    $scope.$on('$ionicTabs.top', onTabsTop);
    $scope.$on('$ionicSubheader', onBarSubheader);

    $scope.$on('$ionicTabs.beforeLeave', onTabsLeave);
    $scope.$on('$ionicTabs.afterLeave', onTabsLeave);
    $scope.$on('$ionicTabs.leave', onTabsLeave);

    ionic.Platform.ready(function() {
      self.initSwipeBack();
    });

    return viewData;
  };

  self.register = function(viewLocals) {
    var leavingView = extend({}, $ionicHistory.currentView());

    // register that a view is coming in and get info on how it should transition
    var registerData = $ionicHistory.register($scope, viewLocals);

    // update which direction
    self.update(registerData);

    // begin rendering and transitioning
    var enteringView = $ionicHistory.getViewById(registerData.viewId) || {};

    var renderStart = (disableRenderStartViewId !== registerData.viewId);
    self.render(registerData, viewLocals, enteringView, leavingView, renderStart, true);
  };

  self.update = function(registerData) {
    // always reset that this is the primary navView
    isPrimary = true;

    // remember what direction this navView should use
    // this may get updated later by a child navView
    direction = registerData.direction;

    var parentNavViewCtrl = $element.parent().inheritedData('$ionNavViewController');
    if (parentNavViewCtrl) {
      // this navView is nested inside another one
      // update the parent to use this direction and not
      // the other it originally was set to

      // inform the parent navView that it is not the primary navView
      parentNavViewCtrl.isPrimary(false);

      if (direction === 'enter' || direction === 'exit') {
        // they're entering/exiting a history
        // find parent navViewController
        parentNavViewCtrl.direction(direction);

        if (direction === 'enter') {
          // reset the direction so this navView doesn't animate
          // because it's parent will
          direction = 'none';
        }
      }
    }
  };

  self.render = function(registerData, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
    // register the view and figure out where it lives in the various
    // histories and nav stacks, along with how views should enter/leave
    var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView, renderStart, renderEnd);

    // init the rendering of views for this navView directive
    switcher.init(registerData, function() {
      // the view is now compiled, in the dom and linked, now lets transition the views.
      // this uses a callback incase THIS nav-view has a nested nav-view, and after the NESTED
      // nav-view links, the NESTED nav-view would update which direction THIS nav-view should use

      // kick off the transition of views
      switcher.transition(self.direction(), registerData.enableBack, !disableAnimation);

      // reset private vars for next time
      disableRenderStartViewId = disableAnimation = null;
    });

  };

  self.beforeEnter = function(transitionData) {
    if (isPrimary) {
      // only update this nav-view's nav-bar if this is the primary nav-view
      navBarDelegate = transitionData.navBarDelegate;
      var associatedNavBarCtrl = getAssociatedNavBarCtrl();
      associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData);
      navSwipeAttr('');
    }
  };

  self.activeEleId = function(eleId) {
    if (arguments.length) {
      activeEleId = eleId;
    }
    return activeEleId;
  };

  self.transitionEnd = function() {
    var viewElements = $element.children();
    var x, l, viewElement;

    for (x = 0, l = viewElements.length; x < l; x++) {
      viewElement = viewElements.eq(x);

      if (viewElement.data(DATA_ELE_IDENTIFIER) === activeEleId) {
        // this is the active element
        navViewAttr(viewElement, VIEW_STATUS_ACTIVE);

      } else if (navViewAttr(viewElement) === 'leaving' || navViewAttr(viewElement) === VIEW_STATUS_ACTIVE || navViewAttr(viewElement) === VIEW_STATUS_CACHED) {
        // this is a leaving element or was the former active element, or is an cached element
        if (viewElement.data(DATA_DESTROY_ELE) || viewElement.data(DATA_NO_CACHE)) {
          // this element shouldn't stay cached
          $ionicViewSwitcher.destroyViewEle(viewElement);

        } else {
          // keep in the DOM, mark as cached
          navViewAttr(viewElement, VIEW_STATUS_CACHED);

          // disconnect the leaving scope
          ionic.Utils.disconnectScope(viewElement.scope());
        }
      }
    }

    navSwipeAttr('');

    // ensure no scrolls have been left frozen
    if (self.isSwipeFreeze) {
      $ionicScrollDelegate.freezeAllScrolls(false);
    }
  };

  function onTabsLeave(ev, data) {
    var viewElements = $element.children();
    var viewElement, viewScope;

    for (var x = 0, l = viewElements.length; x < l; x++) {
      viewElement = viewElements.eq(x);
      if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
        viewScope = viewElement.scope();
        viewScope && viewScope.$emit(ev.name.replace('Tabs', 'View'), data);
        break;
      }
    }
  }

  self.cacheCleanup = function() {
    var viewElements = $element.children();
    for (var x = 0, l = viewElements.length; x < l; x++) {
      if (viewElements.eq(x).data(DATA_DESTROY_ELE)) {
        $ionicViewSwitcher.destroyViewEle(viewElements.eq(x));
      }
    }
  };

  self.clearCache = function(stateIds) {
    var viewElements = $element.children();
    var viewElement, viewScope, x, l, y, eleIdentifier;

    for (x = 0, l = viewElements.length; x < l; x++) {
      viewElement = viewElements.eq(x);

      if (stateIds) {
        eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER);

        for (y = 0; y < stateIds.length; y++) {
          if (eleIdentifier === stateIds[y]) {
            $ionicViewSwitcher.destroyViewEle(viewElement);
          }
        }
        continue;
      }

      if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {
        $ionicViewSwitcher.destroyViewEle(viewElement);

      } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
        viewScope = viewElement.scope();
        viewScope && viewScope.$broadcast('$ionicView.clearCache');
      }

    }
  };

  self.getViewElements = function() {
    return $element.children();
  };

  self.appendViewElement = function(viewEle, viewLocals) {
    // compile the entering element and get the link function
    var linkFn = $compile(viewEle);

    $element.append(viewEle);

    var viewScope = $scope.$new();

    if (viewLocals && viewLocals.$$controller) {
      viewLocals.$scope = viewScope;
      var controller = $controller(viewLocals.$$controller, viewLocals);
      if (viewLocals.$$controllerAs) {
        viewScope[viewLocals.$$controllerAs] = controller;
      }
      $element.children().data('$ngControllerController', controller);
    }

    linkFn(viewScope);

    return viewScope;
  };

  self.title = function(val) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    associatedNavBarCtrl && associatedNavBarCtrl.title(val);
  };

  /**
   * @ngdoc method
   * @name $ionicNavView#enableBackButton
   * @description Enable/disable if the back button can be shown or not. For
   * example, the very first view in the navigation stack would not have a
   * back view, so the back button would be disabled.
   */
  self.enableBackButton = function(shouldEnable) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    associatedNavBarCtrl && associatedNavBarCtrl.enableBackButton(shouldEnable);
  };

  /**
   * @ngdoc method
   * @name $ionicNavView#showBackButton
   * @description Show/hide the nav bar active back button. If the back button
   * is not possible this will not force the back button to show. The
   * `enableBackButton()` method handles if a back button is even possible or not.
   */
  self.showBackButton = function(shouldShow) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    if (associatedNavBarCtrl) {
      if (arguments.length) {
        return associatedNavBarCtrl.showActiveBackButton(shouldShow);
      }
      return associatedNavBarCtrl.showActiveBackButton();
    }
    return true;
  };

  self.showBar = function(val) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    if (associatedNavBarCtrl) {
      if (arguments.length) {
        return associatedNavBarCtrl.showBar(val);
      }
      return associatedNavBarCtrl.showBar();
    }
    return true;
  };

  self.isPrimary = function(val) {
    if (arguments.length) {
      isPrimary = val;
    }
    return isPrimary;
  };

  self.direction = function(val) {
    if (arguments.length) {
      direction = val;
    }
    return direction;
  };

  self.initSwipeBack = function() {
    var swipeBackHitWidth = $ionicConfig.views.swipeBackHitWidth();
    var viewTransition, associatedNavBarCtrl, backView;
    var deregDragStart, deregDrag, deregRelease;
    var windowWidth, startDragX, dragPoints;
    var cancelData = {};

    function onDragStart(ev) {
      if (!isPrimary) return;

      if (!$ionicConfig.views.swipeBackEnabled() || !ionic.Platform.isIOS() ) {
        return;
      }

      startDragX = getDragX(ev);
      if (startDragX > swipeBackHitWidth) return;

      backView = $ionicHistory.backView();

      var currentView = $ionicHistory.currentView();

      if (!backView || backView.historyId !== currentView.historyId || currentView.canSwipeBack === false) return;

      if (!windowWidth) windowWidth = window.innerWidth;

      self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(true);

      var registerData = {
        direction: 'back'
      };

      dragPoints = [];

      cancelData = {
        showBar: self.showBar(),
        showBackButton: self.showBackButton()
      };

      var switcher = $ionicViewSwitcher.create(self, registerData, backView, currentView, true, false);
      switcher.loadViewElements(registerData);
      switcher.render(registerData);

      viewTransition = switcher.transition('back', $ionicHistory.enabledBack(backView), true);

      associatedNavBarCtrl = getAssociatedNavBarCtrl();

      deregDrag = ionic.onGesture('drag', onDrag, $element[0]);
      deregRelease = ionic.onGesture('release', onRelease, $element[0]);
    }

    function onDrag(ev) {
      if (isPrimary && viewTransition) {
        var dragX = getDragX(ev);

        dragPoints.push({
          t: Date.now(),
          x: dragX
        });

        if (dragX >= windowWidth - 15) {
          onRelease(ev);

        } else {
          var step = Math.min(Math.max(getSwipeCompletion(dragX), 0), 1);
          viewTransition.run(step);
          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.run(step);
        }

      }
    }

    function onRelease(ev) {
      if (isPrimary && viewTransition && dragPoints && dragPoints.length > 1) {

        var now = Date.now();
        var releaseX = getDragX(ev);
        var startDrag = dragPoints[dragPoints.length - 1];

        for (var x = dragPoints.length - 2; x >= 0; x--) {
          if (now - startDrag.t > 200) {
            break;
          }
          startDrag = dragPoints[x];
        }

        var isSwipingRight = (releaseX >= dragPoints[dragPoints.length - 2].x);
        var releaseSwipeCompletion = getSwipeCompletion(releaseX);
        var velocity = Math.abs(startDrag.x - releaseX) / (now - startDrag.t);

        // private variables because ui-router has no way to pass custom data using $state.go
        disableRenderStartViewId = backView.viewId;
        disableAnimation = (releaseSwipeCompletion < 0.03 || releaseSwipeCompletion > 0.97);

        if (isSwipingRight && (releaseSwipeCompletion > 0.5 || velocity > 0.1)) {
          // complete view transition on release
          var speed = (velocity > 0.5 || velocity < 0.05 || releaseX > windowWidth - 45) ? 'fast' : 'slow';
          navSwipeAttr(disableAnimation ? '' : speed);
          backView.go();
          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.complete(!disableAnimation, speed);

        } else {
          // cancel view transition on release
          navSwipeAttr(disableAnimation ? '' : 'fast');
          disableRenderStartViewId = null;
          viewTransition.cancel(!disableAnimation);
          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast', cancelData);
          disableAnimation = null;
        }

      }

      ionic.offGesture(deregDrag, 'drag', onDrag);
      ionic.offGesture(deregRelease, 'release', onRelease);

      windowWidth = viewTransition = dragPoints = null;

      self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(false);
    }

    function getDragX(ev) {
      return ionic.tap.pointerCoord(ev.gesture.srcEvent).x;
    }

    function getSwipeCompletion(dragX) {
      return (dragX - startDragX) / windowWidth;
    }

    deregDragStart = ionic.onGesture('dragstart', onDragStart, $element[0]);

    $scope.$on('$destroy', function() {
      ionic.offGesture(deregDragStart, 'dragstart', onDragStart);
      ionic.offGesture(deregDrag, 'drag', onDrag);
      ionic.offGesture(deregRelease, 'release', onRelease);
      self.element = viewTransition = associatedNavBarCtrl = null;
    });
  };

  function navSwipeAttr(val) {
    ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
  }

  function onTabsTop(ev, isTabsTop) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    associatedNavBarCtrl && associatedNavBarCtrl.hasTabsTop(isTabsTop);
  }

  function onBarSubheader(ev, isBarSubheader) {
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
    associatedNavBarCtrl && associatedNavBarCtrl.hasBarSubheader(isBarSubheader);
  }

  function getAssociatedNavBarCtrl() {
    if (navBarDelegate) {
      for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
        if ($ionicNavBarDelegate._instances[x].$$delegateHandle == navBarDelegate) {
          return $ionicNavBarDelegate._instances[x];
        }
      }
    }
    return $element.inheritedData('$ionNavBarController');
  }

}]);
<|endoftext|>


<commit_before>(function () {
    var ns = $.namespace("pskl.controller");
    ns.AnimatedPreviewController = function (framesheet, container, dpi) {
        this.framesheet = framesheet;
        this.container = container;

        this.elapsedTime = 0;
        this.currentIndex = 0;

        this.fps = parseInt($("#preview-fps")[0].value, 10);

        var renderingOptions = {
            "dpi": this.calculateDPI_()
        };
        this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions);

        $.subscribe(Events.FRAME_SIZE_CHANGED, this.updateDPI_.bind(this));
    };

    ns.AnimatedPreviewController.prototype.init = function () {
        // the oninput event won't work on IE10 unfortunately, but at least will provide a
        // consistent behavior across all other browsers that support the input type range
        // see https://bugzilla.mozilla.org/show_bug.cgi?id=853670
        $("#preview-fps")[0].addEventListener('input', this.onFPSSliderChange.bind(this));
    };

    ns.AnimatedPreviewController.prototype.onFPSSliderChange = function (evt) {
        this.setFPS(parseInt($("#preview-fps")[0].value, 10));
    };

    ns.AnimatedPreviewController.prototype.setFPS = function (fps) {
        this.fps = fps;
        $("#preview-fps").val(this.fps);
        $("#display-fps").html(this.fps + " FPS");
    };

    ns.AnimatedPreviewController.prototype.render = function (delta) {
        this.elapsedTime += delta;
        var index = Math.floor(this.elapsedTime / (1000/this.fps));
        if (index != this.currentIndex) {
            this.currentIndex = index;
            if (!this.framesheet.hasFrameAtIndex(this.currentIndex)) {
                this.currentIndex = 0;
                this.elapsedTime = 0;
            }
            this.renderer.render(this.framesheet.getFrameByIndex(this.currentIndex));
        }
    };

    /**
     * Calculate the preview DPI depending on the framesheet size
     */
    ns.AnimatedPreviewController.prototype.calculateDPI_ = function () {
        var previewSize = 200,
            framePixelHeight = this.framesheet.getCurrentFrame().getHeight(),
            framePixelWidth = this.framesheet.getCurrentFrame().getWidth();
        // TODO (julz) : should have a utility to get a Size from framesheet easily (what about empty framesheets though ?)

        //return pskl.PixelUtils.calculateDPIForContainer($(".preview-container"), framePixelHeight, framePixelWidth);
        return pskl.PixelUtils.calculateDPI(previewSize, previewSize, framePixelHeight, framePixelWidth);
    };

    ns.AnimatedPreviewController.prototype.updateDPI_ = function () {
        this.dpi = this.calculateDPI_();
        this.renderer.updateDPI(this.dpi);
    };
})();<commit_msg>Reverted back to onchange due to lack of support of input range in firefox 21<commit_after>(function () {
    var ns = $.namespace("pskl.controller");
    ns.AnimatedPreviewController = function (framesheet, container, dpi) {
        this.framesheet = framesheet;
        this.container = container;

        this.elapsedTime = 0;
        this.currentIndex = 0;

        this.fps = parseInt($("#preview-fps")[0].value, 10);

        var renderingOptions = {
            "dpi": this.calculateDPI_()
        };
        this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions);

        $.subscribe(Events.FRAME_SIZE_CHANGED, this.updateDPI_.bind(this));
    };

    ns.AnimatedPreviewController.prototype.init = function () {
        // the oninput event won't work on IE10 unfortunately, but at least will provide a
        // consistent behavior across all other browsers that support the input type range
        // see https://bugzilla.mozilla.org/show_bug.cgi?id=853670
        $("#preview-fps")[0].addEventListener('change', this.onFPSSliderChange.bind(this));
    };

    ns.AnimatedPreviewController.prototype.onFPSSliderChange = function (evt) {
        this.setFPS(parseInt($("#preview-fps")[0].value, 10));
    };

    ns.AnimatedPreviewController.prototype.setFPS = function (fps) {
        this.fps = fps;
        $("#preview-fps").val(this.fps);
        $("#display-fps").html(this.fps + " FPS");
    };

    ns.AnimatedPreviewController.prototype.render = function (delta) {
        this.elapsedTime += delta;
        var index = Math.floor(this.elapsedTime / (1000/this.fps));
        if (index != this.currentIndex) {
            this.currentIndex = index;
            if (!this.framesheet.hasFrameAtIndex(this.currentIndex)) {
                this.currentIndex = 0;
                this.elapsedTime = 0;
            }
            this.renderer.render(this.framesheet.getFrameByIndex(this.currentIndex));
        }
    };

    /**
     * Calculate the preview DPI depending on the framesheet size
     */
    ns.AnimatedPreviewController.prototype.calculateDPI_ = function () {
        var previewSize = 200,
            framePixelHeight = this.framesheet.getCurrentFrame().getHeight(),
            framePixelWidth = this.framesheet.getCurrentFrame().getWidth();
        // TODO (julz) : should have a utility to get a Size from framesheet easily (what about empty framesheets though ?)

        //return pskl.PixelUtils.calculateDPIForContainer($(".preview-container"), framePixelHeight, framePixelWidth);
        return pskl.PixelUtils.calculateDPI(previewSize, previewSize, framePixelHeight, framePixelWidth);
    };

    ns.AnimatedPreviewController.prototype.updateDPI_ = function () {
        this.dpi = this.calculateDPI_();
        this.renderer.updateDPI(this.dpi);
    };
})();<|endoftext|>


<commit_before>/*   RSence
 *   Copyright 2008 Riassence Inc.
 *   http://riassence.com/
 *
 *   You should have received a copy of the GNU General Public License along
 *   with this software package. If not, contact licensing@riassence.com
 */

HValidatorView = HControl.extend({

  constructor: function(_rect, _parent, _options) {
    if(_options){
      if(_options.valueField && _options.valueField.componentBehaviour[1] === 'control'){
        _rect.offsetTo(
          _options.valueField.rect.right,
          _options.valueField.rect.top
        );
      }
    }
    this.base(_rect, _parent, _options);
  },

/** = Description
  * Ensures the value set is a Boolean.
  *
  * = Parameters
  * +_flag+::   True to set the status to selected, false to set to unselected.
  *
  **/
  setValue: function(_flag) {
    if (!_flag && _flag !== false) {
      _flag = false;
    }
    this.base(_flag);
  },

/** = Description
  * Updates the graphics to match the Boolean value.
  *
  **/
  refreshValue: function(){
    var _this = this,
        _value = _this.value,
        _trueValue = _value === true,
        _x = _trueValue?-21:0,
        _y = _this.enabled?0:-21,
        _title = _trueValue?'':_value,
        _elemId = _this.elemId;
    ELEM.setStyle(_elemId,'background-image',"url("+_this.getThemeGfxFile('validator.png')+")");
    ELEM.setStyle(_elemId,'background-repeat','no-repeat');
    ELEM.setStyle(_elemId,'background-position',_x+'px '+_y+'px');
  }

});
<commit_msg>Added default value: false back as it had been accidentally removed<commit_after>/*   RSence
 *   Copyright 2008 Riassence Inc.
 *   http://riassence.com/
 *
 *   You should have received a copy of the GNU General Public License along
 *   with this software package. If not, contact licensing@riassence.com
 */

HValidatorView = HControl.extend({

  controlDefaults: (HControlDefaults.extend({
    value: false
  })),

  constructor: function(_rect, _parent, _options) {
    if(_options){
      if(_options.valueField && _options.valueField.componentBehaviour[1] === 'control'){
        _rect.offsetTo(
          _options.valueField.rect.right,
          _options.valueField.rect.top
        );
      }
    }
    this.base(_rect, _parent, _options);
  },

/** = Description
  * Ensures the value set is a Boolean.
  *
  * = Parameters
  * +_flag+::   True to set the status to selected, false to set to unselected.
  *
  **/
  setValue: function(_flag) {
    if (!_flag && _flag !== false) {
      _flag = false;
    }
    this.base(_flag);
  },

/** = Description
  * Updates the graphics to match the Boolean value.
  *
  **/
  refreshValue: function(){
    var _this = this,
        _value = _this.value,
        _trueValue = _value === true,
        _x = _trueValue?-21:0,
        _y = _this.enabled?0:-21,
        _title = _trueValue?'':_value,
        _elemId = _this.elemId;
    ELEM.setStyle(_elemId,'background-image',"url("+_this.getThemeGfxFile('validator.png')+")");
    ELEM.setStyle(_elemId,'background-repeat','no-repeat');
    ELEM.setStyle(_elemId,'background-position',_x+'px '+_y+'px');
  }

});
<|endoftext|>


<commit_before>import Component from 'flarum/component';
import ItemList from 'flarum/utils/item-list';
import PostStream from 'flarum/utils/post-stream';
import DiscussionList from 'flarum/components/discussion-list';
import DiscussionHero from 'flarum/components/discussion-hero';
import StreamContent from 'flarum/components/stream-content';
import StreamScrubber from 'flarum/components/stream-scrubber';
import ComposerReply from 'flarum/components/composer-reply';
import ActionButton from 'flarum/components/action-button';
import LoadingIndicator from 'flarum/components/loading-indicator';
import DropdownSplit from 'flarum/components/dropdown-split';
import Separator from 'flarum/components/separator';
import listItems from 'flarum/helpers/list-items';

export default class DiscussionPage extends Component {
  /**

   */
  constructor(props) {
    super(props);

    this.discussion = m.prop();

    // Set up the stream. The stream is an object that represents the posts in
    // a discussion, as they're displayed on the screen (i.e. missing posts
    // are condensed into "load more" gaps).
    this.stream = m.prop();

    // Get the discussion. We may already have a copy of it in our store, so
    // we'll start off with that. If we do have a copy of the discussion, and
    // its posts relationship has been loaded (i.e. we've viewed this
    // discussion before), then we can proceed with displaying it immediately.
    // If not, we'll make an API request first.
    this.currentNear = m.route.param('near');
    var params = this.params();
    params.include = params.include.join(',');
    app.store.find('discussions', m.route.param('id'), params).then(this.setupDiscussion.bind(this));

    if (app.cache.discussionList) {
      if (!(app.current instanceof DiscussionPage)) {
        app.cache.discussionList.subtrees.map(subtree => subtree.invalidate());
      }
      app.pane.enable();
      app.pane.hide();
      m.redraw.strategy('diff'); // otherwise pane redraws and mouseenter even is triggered so it doesn't hide
    }

    app.history.push('discussion');
    app.current = this;
    app.composer.minimize();
  }

  params() {
    return {
      near: this.currentNear,
      include: ['posts', 'posts.user']
    };
  }

  /*

   */
  setupDiscussion(discussion) {
    this.discussion(discussion);

    var includedPosts = [];
    discussion.payload.included && discussion.payload.included.forEach(record => {
      if (record.type === 'posts') {
        includedPosts.push(record.id);
      }
    });

    // Set up the post stream for this discussion, and add all of the posts we
    // have loaded so far.
    this.stream(new PostStream(discussion));
    this.stream().addPosts(discussion.posts().filter(value => value && includedPosts.indexOf(value.id()) !== -1));
    this.streamContent = new StreamContent({
      stream: this.stream(),
      className: 'discussion-posts posts',
      positionChanged: this.positionChanged.bind(this)
    });

    // Hold up there skippy! If the slug in the URL doesn't match up, we'll
    // redirect so we have the correct one.
    if (m.route.param('id') === discussion.id() && m.route.param('slug') !== discussion.slug()) {
      var params = m.route.param();
      params.slug = discussion.slug();
      params.near = params.near || '';
      m.route(app.route('discussion.near', params), null, true);
      return;
    }

    this.streamContent.goToNumber(this.currentNear, true);
  }

  onload(element, isInitialized, context) {
    if (isInitialized) { return; }

    context.retain = true;

    $('body').addClass('discussion-page');
    context.onunload = function() {
      $('body').removeClass('discussion-page');
    }
  }

  /**

   */
  onunload(e) {
    // If we have routed to the same discussion as we were viewing previously,
    // cancel the unloading of this controller and instead prompt the post
    // stream to jump to the new 'near' param.
    var discussion = this.discussion();
    if (discussion) {
      var discussionRoute = app.route('discussion', { id: discussion.id(), slug: discussion.slug() });
      if (m.route().substr(0, discussionRoute.length) === discussionRoute) {
        e.preventDefault();
        if (m.route.param('near') != this.currentNear) {
          this.streamContent.goToNumber(m.route.param('near'));
        }
        return;
      }
    }

    app.pane.disable();
  }

  /**

   */
  view() {
    var discussion = this.discussion();

    return m('div', {config: this.onload.bind(this)}, [
      app.cache.discussionList ? m('div.index-area.paned', {config: this.configIndex.bind(this)}, app.cache.discussionList.view()) : '',
      m('div.discussion-area', discussion ? [
        DiscussionHero.component({discussion}),
        m('div.container', [
          m('nav.discussion-nav', [
            m('ul', listItems(this.sidebarItems().toArray()))
          ]),
          this.streamContent.view()
        ])
      ] : LoadingIndicator.component({className: 'loading-indicator-block'}))
    ]);
  }

  /**

   */
  configIndex(element, isInitialized, context) {
    if (isInitialized) { return; }

    context.retain = true;

    var $index = $(element);

    // When viewing a discussion (for which the discussions route is the
    // parent,) the discussion list is still rendered but it becomes a
    // pane hidden on the side of the screen. When the mouse enters and
    // leaves the discussions pane, we want to show and hide the pane
    // respectively. We also create a 10px 'hot edge' on the left of the
    // screen to activate the pane.
    var pane = app.pane;
    $index.hover(pane.show.bind(pane), pane.onmouseleave.bind(pane));

    var hotEdge = function(e) {
      if (e.pageX < 10) { pane.show(); }
    };
    $(document).on('mousemove', hotEdge);
    context.onunload = function() {
      $(document).off('mousemove', hotEdge);
    };

    var $discussion = $index.find('.discussion-summary.active');
    if ($discussion.length) {
      var indexTop = $index.offset().top;
      var discussionTop = $discussion.offset().top;
      if (discussionTop < indexTop || discussionTop + $discussion.outerHeight() > indexTop + $index.outerHeight()) {
        $index.scrollTop($index.scrollTop() - indexTop + discussionTop);
      }
    }
  }

  /**

   */
  sidebarItems() {
    var items = new ItemList();

    items.add('controls',
      DropdownSplit.component({
        items: this.discussion().controls(this).toArray(),
        icon: 'reply',
        buttonClass: 'btn btn-primary',
        wrapperClass: 'primary-control'
      })
    );

    items.add('scrubber',
      StreamScrubber.component({
        streamContent: this.streamContent,
        wrapperClass: 'title-control'
      })
    );

    return items;
  }

  /**

   */
  positionChanged(startNumber, endNumber) {
    var discussion = this.discussion();

    var url = app.route('discussion.near', {
      id: discussion.id(),
      slug: discussion.slug(),
      near: this.currentNear = startNumber
    });

    // https://github.com/lhorie/mithril.js/issues/559
    m.route(url, true);
    window.history.replaceState(null, document.title, (m.route.mode === 'hash' ? '#' : '')+url);

    app.history.push('discussion');

    if (app.session.user() && endNumber > discussion.readNumber()) {
      discussion.save({readNumber: endNumber});
      m.redraw();
    }
  }
}
<commit_msg>Prevent JS error for now<commit_after>import Component from 'flarum/component';
import ItemList from 'flarum/utils/item-list';
import PostStream from 'flarum/utils/post-stream';
import DiscussionList from 'flarum/components/discussion-list';
import DiscussionHero from 'flarum/components/discussion-hero';
import StreamContent from 'flarum/components/stream-content';
import StreamScrubber from 'flarum/components/stream-scrubber';
import ComposerReply from 'flarum/components/composer-reply';
import ActionButton from 'flarum/components/action-button';
import LoadingIndicator from 'flarum/components/loading-indicator';
import DropdownSplit from 'flarum/components/dropdown-split';
import Separator from 'flarum/components/separator';
import listItems from 'flarum/helpers/list-items';

export default class DiscussionPage extends Component {
  /**

   */
  constructor(props) {
    super(props);

    this.discussion = m.prop();

    // Set up the stream. The stream is an object that represents the posts in
    // a discussion, as they're displayed on the screen (i.e. missing posts
    // are condensed into "load more" gaps).
    this.stream = m.prop();

    // Get the discussion. We may already have a copy of it in our store, so
    // we'll start off with that. If we do have a copy of the discussion, and
    // its posts relationship has been loaded (i.e. we've viewed this
    // discussion before), then we can proceed with displaying it immediately.
    // If not, we'll make an API request first.
    this.currentNear = m.route.param('near');
    var params = this.params();
    params.include = params.include.join(',');
    app.store.find('discussions', m.route.param('id'), params).then(this.setupDiscussion.bind(this));

    if (app.cache.discussionList) {
      if (!(app.current instanceof DiscussionPage)) {
        app.cache.discussionList.subtrees.map(subtree => subtree.invalidate());
      }
      app.pane.enable();
      app.pane.hide();
      m.redraw.strategy('diff'); // otherwise pane redraws and mouseenter even is triggered so it doesn't hide
    }

    app.history.push('discussion');
    app.current = this;
    app.composer.minimize();
  }

  params() {
    return {
      near: this.currentNear,
      include: ['posts', 'posts.user']
    };
  }

  /*

   */
  setupDiscussion(discussion) {
    this.discussion(discussion);

    var includedPosts = [];
    discussion.payload.included && discussion.payload.included.forEach(record => {
      if (record.type === 'posts') {
        includedPosts.push(record.id);
      }
    });

    // Set up the post stream for this discussion, and add all of the posts we
    // have loaded so far.
    this.stream(new PostStream(discussion));
    this.stream().addPosts(discussion.posts().filter(value => value && includedPosts.indexOf(value.id()) !== -1));
    this.streamContent = new StreamContent({
      stream: this.stream(),
      className: 'discussion-posts posts',
      positionChanged: this.positionChanged.bind(this)
    });

    // Hold up there skippy! If the slug in the URL doesn't match up, we'll
    // redirect so we have the correct one.
    // Waiting on https://github.com/lhorie/mithril.js/issues/539
    // if (m.route.param('id') === discussion.id() && m.route.param('slug') !== discussion.slug()) {
    //   var params = m.route.param();
    //   params.slug = discussion.slug();
    //   params.near = params.near || '';
    //   m.route(app.route('discussion.near', params), null, true);
    //   return;
    // }

    this.streamContent.goToNumber(this.currentNear, true);
  }

  onload(element, isInitialized, context) {
    if (isInitialized) { return; }

    context.retain = true;

    $('body').addClass('discussion-page');
    context.onunload = function() {
      $('body').removeClass('discussion-page');
    }
  }

  /**

   */
  onunload(e) {
    // If we have routed to the same discussion as we were viewing previously,
    // cancel the unloading of this controller and instead prompt the post
    // stream to jump to the new 'near' param.
    var discussion = this.discussion();
    if (discussion) {
      var discussionRoute = app.route('discussion', { id: discussion.id(), slug: discussion.slug() });
      if (m.route().substr(0, discussionRoute.length) === discussionRoute) {
        e.preventDefault();
        if (m.route.param('near') != this.currentNear) {
          this.streamContent.goToNumber(m.route.param('near'));
        }
        return;
      }
    }

    app.pane.disable();
  }

  /**

   */
  view() {
    var discussion = this.discussion();

    return m('div', {config: this.onload.bind(this)}, [
      app.cache.discussionList ? m('div.index-area.paned', {config: this.configIndex.bind(this)}, app.cache.discussionList.view()) : '',
      m('div.discussion-area', discussion ? [
        DiscussionHero.component({discussion}),
        m('div.container', [
          m('nav.discussion-nav', [
            m('ul', listItems(this.sidebarItems().toArray()))
          ]),
          this.streamContent.view()
        ])
      ] : LoadingIndicator.component({className: 'loading-indicator-block'}))
    ]);
  }

  /**

   */
  configIndex(element, isInitialized, context) {
    if (isInitialized) { return; }

    context.retain = true;

    var $index = $(element);

    // When viewing a discussion (for which the discussions route is the
    // parent,) the discussion list is still rendered but it becomes a
    // pane hidden on the side of the screen. When the mouse enters and
    // leaves the discussions pane, we want to show and hide the pane
    // respectively. We also create a 10px 'hot edge' on the left of the
    // screen to activate the pane.
    var pane = app.pane;
    $index.hover(pane.show.bind(pane), pane.onmouseleave.bind(pane));

    var hotEdge = function(e) {
      if (e.pageX < 10) { pane.show(); }
    };
    $(document).on('mousemove', hotEdge);
    context.onunload = function() {
      $(document).off('mousemove', hotEdge);
    };

    var $discussion = $index.find('.discussion-summary.active');
    if ($discussion.length) {
      var indexTop = $index.offset().top;
      var discussionTop = $discussion.offset().top;
      if (discussionTop < indexTop || discussionTop + $discussion.outerHeight() > indexTop + $index.outerHeight()) {
        $index.scrollTop($index.scrollTop() - indexTop + discussionTop);
      }
    }
  }

  /**

   */
  sidebarItems() {
    var items = new ItemList();

    items.add('controls',
      DropdownSplit.component({
        items: this.discussion().controls(this).toArray(),
        icon: 'reply',
        buttonClass: 'btn btn-primary',
        wrapperClass: 'primary-control'
      })
    );

    items.add('scrubber',
      StreamScrubber.component({
        streamContent: this.streamContent,
        wrapperClass: 'title-control'
      })
    );

    return items;
  }

  /**

   */
  positionChanged(startNumber, endNumber) {
    var discussion = this.discussion();

    var url = app.route('discussion.near', {
      id: discussion.id(),
      slug: discussion.slug(),
      near: this.currentNear = startNumber
    });

    // https://github.com/lhorie/mithril.js/issues/559
    m.route(url, true);
    window.history.replaceState(null, document.title, (m.route.mode === 'hash' ? '#' : '')+url);

    app.history.push('discussion');

    if (app.session.user() && endNumber > discussion.readNumber()) {
      discussion.save({readNumber: endNumber});
      m.redraw();
    }
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react'

export default function SpeechRecognition(options) {
  const SpeechRecognitionInner = function (WrappedComponent) {
    const BrowserSpeechRecognition =
      typeof window !== 'undefined' &&
      (window.SpeechRecognition ||
        window.webkitSpeechRecognition ||
        window.mozSpeechRecognition ||
        window.msSpeechRecognition ||
        window.oSpeechRecognition)
    const recognition = BrowserSpeechRecognition
      ? new BrowserSpeechRecognition()
      : null
    const browserSupportsSpeechRecognition = recognition !== null
    let listening
    if (
      !browserSupportsSpeechRecognition ||
      (options && options.autoStart === false)
    ) {
      listening = false
    } else {
      recognition.start()
      listening = true
    }
    let pauseAfterDisconnect = false
    let interimTranscript = ''
    let finalTranscript = ''

    return class SpeechRecognitionContainer extends Component {
      constructor(props) {
        super(props)

        this.state = {
          interimTranscript,
          finalTranscript,
          listening: false
        }
      }

      componentWillMount() {
        if (recognition) {
          recognition.continuous = options.continuous || true
          recognition.interimResults = true
          recognition.onresult = this.updateTranscript.bind(this)
          recognition.onend = this.onRecognitionDisconnect.bind(this)
          this.setState({ listening })
        }
      }

      disconnect = disconnectType => {
        if (recognition) {
          switch (disconnectType) {
            case 'ABORT':
              pauseAfterDisconnect = true
              recognition.abort()
              break
            case 'RESET':
              pauseAfterDisconnect = false
              recognition.abort()
              break
            case 'STOP':
            default:
              pauseAfterDisconnect = true
              recognition.stop()
          }
        }
      }

      onRecognitionDisconnect() {
        listening = false
        if (pauseAfterDisconnect) {
          this.setState({ listening })
        } else if (typeof options.continuous === 'undefined') {
          this.startListening()
        }
        pauseAfterDisconnect = false
      }

      updateTranscript(event) {
        interimTranscript = ''
        for (let i = event.resultIndex; i < event.results.length; ++i) {
          if (event.results[i].isFinal) {
            finalTranscript = this.concatTranscripts(
              finalTranscript,
              event.results[i][0].transcript
            )
          } else {
            interimTranscript = this.concatTranscripts(
              interimTranscript,
              event.results[i][0].transcript
            )
          }
        }
        this.setState({ finalTranscript, interimTranscript })
      }

      concatTranscripts(...transcriptParts) {
        return transcriptParts.map(t => t.trim()).join(' ').trim()
      }

      resetTranscript = () => {
        interimTranscript = ''
        finalTranscript = ''
        this.disconnect('RESET')
        this.setState({ interimTranscript, finalTranscript })
      }

      startListening = () => {
        if (recognition && !listening) {
          try {
            recognition.start()
          } catch (DOMException) {
            // Tried to start recognition after it has already started - safe to swallow this error
          }
          listening = true
          this.setState({ listening })
        }
      }

      abortListening = () => {
        listening = false
        this.setState({ listening })
        this.disconnect('ABORT')
      }

      stopListening = () => {
        listening = false
        this.setState({ listening })
        this.disconnect('STOP')
      }

      render() {
        const transcript = this.concatTranscripts(
          finalTranscript,
          interimTranscript
        )

        return (
          <WrappedComponent
            resetTranscript={this.resetTranscript}
            startListening={this.startListening}
            abortListening={this.abortListening}
            stopListening={this.stopListening}
            transcript={transcript}
            recognition={recognition}
            browserSupportsSpeechRecognition={browserSupportsSpeechRecognition}
            {...this.state}
            {...this.props} />
        )
      }
    }
  }

  if (typeof options === 'function') {
    return SpeechRecognitionInner(options)
  } else {
    return SpeechRecognitionInner
  }
}
<commit_msg>fixed a bug in the discontinuous feat<commit_after>import React, { Component } from 'react'

export default function SpeechRecognition(options) {
  const SpeechRecognitionInner = function (WrappedComponent) {
    const BrowserSpeechRecognition =
      typeof window !== 'undefined' &&
      (window.SpeechRecognition ||
        window.webkitSpeechRecognition ||
        window.mozSpeechRecognition ||
        window.msSpeechRecognition ||
        window.oSpeechRecognition)
    const recognition = BrowserSpeechRecognition
      ? new BrowserSpeechRecognition()
      : null
    const browserSupportsSpeechRecognition = recognition !== null
    let listening
    if (
      !browserSupportsSpeechRecognition ||
      (options && options.autoStart === false)
    ) {
      listening = false
    } else {
      recognition.start()
      listening = true
    }
    let pauseAfterDisconnect = false
    let interimTranscript = ''
    let finalTranscript = ''

    return class SpeechRecognitionContainer extends Component {
      constructor(props) {
        super(props)

        this.state = {
          interimTranscript,
          finalTranscript,
          listening: false
        }
      }

      componentWillMount() {
        if (recognition) {
          recognition.continuous = recognition.continuous = options.continuous ? true : false
          recognition.interimResults = true
          recognition.onresult = this.updateTranscript.bind(this)
          recognition.onend = this.onRecognitionDisconnect.bind(this)
          this.setState({ listening })
        }
      }

      disconnect = disconnectType => {
        if (recognition) {
          switch (disconnectType) {
            case 'ABORT':
              pauseAfterDisconnect = true
              recognition.abort()
              break
            case 'RESET':
              pauseAfterDisconnect = false
              recognition.abort()
              break
            case 'STOP':
            default:
              pauseAfterDisconnect = true
              recognition.stop()
          }
        }
      }

      onRecognitionDisconnect() {
        listening = false
        if (pauseAfterDisconnect) {
          this.setState({ listening })
        } else if (typeof options.continuous === 'undefined') {
          this.startListening()
        }
        pauseAfterDisconnect = false
      }

      updateTranscript(event) {
        interimTranscript = ''
        for (let i = event.resultIndex; i < event.results.length; ++i) {
          if (event.results[i].isFinal) {
            finalTranscript = this.concatTranscripts(
              finalTranscript,
              event.results[i][0].transcript
            )
          } else {
            interimTranscript = this.concatTranscripts(
              interimTranscript,
              event.results[i][0].transcript
            )
          }
        }
        this.setState({ finalTranscript, interimTranscript })
      }

      concatTranscripts(...transcriptParts) {
        return transcriptParts.map(t => t.trim()).join(' ').trim()
      }

      resetTranscript = () => {
        interimTranscript = ''
        finalTranscript = ''
        this.disconnect('RESET')
        this.setState({ interimTranscript, finalTranscript })
      }

      startListening = () => {
        if (recognition && !listening) {
          try {
            recognition.start()
          } catch (DOMException) {
            // Tried to start recognition after it has already started - safe to swallow this error
          }
          listening = true
          this.setState({ listening })
        }
      }

      abortListening = () => {
        listening = false
        this.setState({ listening })
        this.disconnect('ABORT')
      }

      stopListening = () => {
        listening = false
        this.setState({ listening })
        this.disconnect('STOP')
      }

      render() {
        const transcript = this.concatTranscripts(
          finalTranscript,
          interimTranscript
        )

        return (
          <WrappedComponent
            resetTranscript={this.resetTranscript}
            startListening={this.startListening}
            abortListening={this.abortListening}
            stopListening={this.stopListening}
            transcript={transcript}
            recognition={recognition}
            browserSupportsSpeechRecognition={browserSupportsSpeechRecognition}
            {...this.state}
            {...this.props} />
        )
      }
    }
  }

  if (typeof options === 'function') {
    return SpeechRecognitionInner(options)
  } else {
    return SpeechRecognitionInner
  }
}
<|endoftext|>


<commit_before>export { OwnerArray };

import * as Pool from "../Pool.js";
import { Array as Recyclable_Array } from "./Recyclable_Array.js";

/**
 * Similar to Recyclable_Array but it owns its contents (which are instances of Recyclable.Base). It will release its contents
 * (by calling their .disposeResources_and_recycleToPool()) in .disposeResources().
 *
 *
 */
class OwnerArray extends Recyclable_Array {

  /**
   * Used as default Recyclable.OwnerArray provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "Recyclable.OwnerArray.Pool", OwnerArray, OwnerArray.setAsConstructor );

  /**
   */
  constructor( ...restArgs ) {
    super( ...restArgs );
    OwnerArray.setAsConstructor_self.call( this );
  }

  /** @override */
  static setAsConstructor( ...restArgs ) {
    super.setAsConstructor( ...restArgs );
    OwnerArray.setAsConstructor_self.call( this );
    return this;
  }

  /** @override */
  static setAsConstructor_self() {
    // Do nothing.
  }

  /** @override */
  disposeResources() {

    // Release all contents since they are owned by this OwnerArray.
    for ( let i = 0; i < this.length; ++i ) {
      let object = this[ i ];
      if ( object instanceof Recyclable.Base ) {
        object.disposeResources_and_recycleToPool();
        this[ i ] = null; // So that it will not become dangling object (since it has already been recycled).
      }
    }

    super.disposeResources();
  }

}

<commit_msg>Update Recyclable_OwnerArray.js<commit_after>export { OwnerArray };

import * as Pool from "../Pool.js";
import { Array as Recyclable_Array } from "./Recyclable_Array.js";

/**
 * Similar to Recyclable_Array but it owns its contents (which are instances of Recyclable.Base). It will release its contents
 * (by calling their .disposeResources_and_recycleToPool()) in .disposeResources().
 *
 *
 * Note: The behavior of this class's constructor (and .setAsConstructor()) is different from original Array (and Recyclable.Array).
 *
 *   - Original Array (and Recyclable.Array):
 *
 *     - If there is only one argument, it is viewed as the length of the newly created array.
 *
 *   - This Recyclable.OwnerArray:
 *
 *     - Even if there is only one argument, all arguments always are viewed as the contents the newly created array.
 *         The reason is for convenient and for avoiding un-initialized element object.
 *
 *
 */
class OwnerArray extends Recyclable_Array {

  /**
   * Used as default Recyclable.OwnerArray provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "Recyclable.OwnerArray.Pool", OwnerArray, OwnerArray.setAsConstructor );

  /**
   * Every element of restArgs should be instance of ChannelPartInfo (even if restArgs has only one element).
   *
   * Note: This behavior is different from original Array which will views the argement is length (not element) if only one argument
   *       is given.
   */
  constructor( ...restArgs ) {
    super( restArgs.length );
    OwnerArray.setAsConstructor_self.call( this, restArgs );
  }

  /**
   * Every element of restArgs should be instance of ChannelPartInfo (even if restArgs has only one element).
   *
   * Note: This behavior is different from original Array which will views the argement is length (not element) if only one argument
   *       is given.
   *
   * @override
   */
  static setAsConstructor( ...restArgs ) {
    super.setAsConstructor( restArgs.length );
    OwnerArray.setAsConstructor_self.call( this, restArgs );
    return this;
  }

  /** @override */
  static setAsConstructor( objectArray ) {
    for ( let i = 0; i < objectArray.length; ++i ) {
      this[ i ] = objectArray[ i ];
    }
  }

//!!!

  /** @override */
  disposeResources() {

    // Release all contents since they are owned by this OwnerArray.
    for ( let i = 0; i < this.length; ++i ) {
      let object = this[ i ];
      if ( object instanceof Recyclable.Base ) {
        object.disposeResources_and_recycleToPool();
        this[ i ] = null; // So that it will not become dangling object (since it has already been recycled).
      }
    }

    super.disposeResources();
  }

}

<|endoftext|>


<commit_before>var React    = require('react'),
  Router     = require('react-router'),
  config     = require('../common/config'),
  core       = require('../common/core'),
  dataSource = config.baseUrl();

 // variables for THREE
 var camera, scene, renderer;

 // used to store the sparse blocks handle, so we can remove it if needed
 var blocks;

 // allow images to be loaded from other domains.
 THREE.ImageUtils.crossOrigin = '';

var SparseVolViewer = React.createClass({

  getInitialState: function() {
    return {
      'uuid': null,
      'label': null,
      'labelType': null,
      'bodies': null,
      'x': 0,
      'y': 0,
      'z': 0,
      'axis': 'xy',
      'voxRatio': 1
    };
  },

  componentDidMount: function() {
    var self = this;
    // set the uuid on initial load as this wont change during the lifetime
    // of this page.
    var update = 0;
    var new_state = {};
    // foreach property value
    for (var key in this.props) {
       if (this.props.hasOwnProperty(key)) {
         // if the value is not null, then proceed.
         if(this.props[key]) {
           // convert props to state if they are defined and different
          if(this.props[key] !== this.state[key]) {
            new_state[key] = this.props[key];
            update++;
          }
        }
      }
    }

    $.get(config.datatypeInfoUrl(this.props.uuid, this.props.labelType), function(labelInfo) {

      // set the ratio we need to scale the z-axis so that it matches the dimensions of the xy plane.
      // typically this will be a ratio of one for iostropic data, but will be larger when the z
      // dimension has been stretched.
      new_state['voxRatio'] = labelInfo.Extended.VoxelSize[2] / labelInfo.Extended.VoxelSize[0];

      new_state['bodies'] = labelInfo.Base.Syncs[0];
      // trigger an update to the canvas if any of the properties are different
      if (update > 0) {
        self.setState(new_state, function() {
          init(self.state);
          animate();
        });
      }
    });
  },

  componentWillUnmount: function() {
  },

  comsponentWillUpdate: function() {
  },

  componentDidUpdate: function(prevProps, prevState) {
  },

  componentWillReceiveProps: function (props) {
    var update = 0;
    var new_state = {};

    if (!this.state.bodies) {
      return;
    }

    // foreach property value
    for (var key in props) {
       if (props.hasOwnProperty(key)) {
         // if the value is not null, then proceed.
         if(props[key]) {
           // convert props to state if they are defined and different
          if(props[key] !== this.state[key]) {
            new_state[key] = props[key];
            update++;
          }
        }
      }
    }

    // trigger an update to the canvas if any of the properties are different
    if (update > 0) {
      this.setState(new_state, function() {
        init(this.state);
        animate();
      });
    }
  },

  closeHandler: function() {
    console.log('should be implemented by the parent container');
    // http://stackoverflow.com/questions/27227792/react-js-removing-a-component
  },

  render: function () {
    return (

Body ID: {this.state.label}

 Close [x]

    );
  }

});

module.exports = SparseVolViewer;

function init(state) {
  renderer = new THREE.WebGLRenderer();
  renderer.setClearColor( 0x000000 );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize(500, 500 );
  document.getElementById('volume_viewer').appendChild( renderer.domElement );

  camera = new THREE.PerspectiveCamera( 70, 500 / 500, 1, 20000 );
  camera.position.z = 8000;

  scene = new THREE.Scene();

  // add lighting
  //var light = new THREE.DirectionalLight( 0xffffff, 1 );
  //light.position.set( 20, 20, 20 ).normalize();
  //scene.add( light );

  // LIGHTS
  var ambientLight = new THREE.AmbientLight( 0x222222 );

  var light = new THREE.DirectionalLight( 0xFFFFFF, 1.0 );
  light.position.set( 200, 400, 500 );

  var light2 = new THREE.DirectionalLight( 0xFFFFFF, 1.0 );
  light2.position.set( -500, 250, -200 );

  scene.add(ambientLight);
  scene.add(light);
  scene.add(light2);

  var plane = {};

  for (key in state) {
    if (state.hasOwnProperty(key)) {
      plane[key] = state[key];
    }
  }

  compose_scene(plane);

  // lastly setup the controls.
  controls = new THREE.TrackballControls(camera, renderer.domElement);
}

function animate() {
  requestAnimationFrame( animate );
  controls.update();
  renderer.render( scene, camera );
}

function compose_scene(plane) {
  var side = new THREE.MeshBasicMaterial({
    color: 0x00ffff,
    side: THREE.DoubleSide,
    opacity: 0.5
  });

  // grab the data from DVID at this point.
  var label = plane.label;

  add_cut_planes(plane.uuid,scene, plane);

  if (label) {
    add_sparse_blocks(plane.uuid, scene, label, plane);
    // adding in the more complete sparse volume really kills the browser
    // this is probably not viable until either memory or cpu requirements
    // can be figured out.
    //
    add_sparse(plane.uuid, scene, label, plane);
  }
  add_axis(scene);
};

function add_cut_planes(uuid, scene, plane) {
  var cross_cut = new THREE.Object3D();
  plane.axis = 'xy';
  cross_cut.add(cut_plane(plane, uuid));
  plane.axis = 'yz';
  cross_cut.add(cut_plane(plane, uuid));
  plane.axis = 'xz';
  cross_cut.add(cut_plane(plane, uuid));
  cross_cut.translateX(-plane.x);
  cross_cut.translateY(-plane.y);
  cross_cut.translateZ(-plane.z);
  scene.add(cross_cut);
};

function add_sparse(uuid, scene, label, plane, color) {
  var url = dataSource + '/api/node/' + uuid + '/' + plane.bodies + '/sparsevol/' + label;

  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'arraybuffer';

  xhr.onload = function(e) {
    // response is unsigned 8 bit integer
    var responseArray   = new Uint32Array(this.response);
    var dimensions      = new Uint8Array(this.response,1,1);
    var dimensionsOfRun = new Uint8Array(this.response,2,1);
    var spans           = responseArray[2];
    var geometry        = new THREE.BufferGeometry();

    // how many points would we need to draw to get the RLE volume filled.
    var points = 0;
    for (var i = 0; i < spans; i++) {
      var offset = i * 4;
      var span = responseArray[offset + 6];
      points += span;
    }

    var positions = new Float32Array(spans * 3 * 2); //*2 to capture two points per span (startx,endx)
    var colors    = new Float32Array(spans * 3 * 2);
    var color     = new THREE.Color();

    // loop over the spans and work out the dimensions
    var counter = 0; //so we can add two points per span
    for (var i = 0; i < spans; i++) {
      var offset = i * 4;
      var x = (responseArray[offset + 3]);
      var y = (responseArray[offset + 4]);
      var z = responseArray[offset + 5]; //Original Z from server
      //Modify z to handle anisotropic data (stretching)
      z = Math.round( (z - plane.z) * plane.voxRatio ) + plane.z;
      var span = responseArray[offset + 6];

      // this is where we would add all the additional points if we
      // wanted to fill in the RLE gaps.
      //for (j = 0; j < span; j++) {
        // do some work
      //}

      positions[counter * 3] = x;
      positions[(counter * 3) + 1] = y;
      positions[(counter * 3) + 2] = z;

      colors[counter * 3] = 255;
      colors[(counter * 3) + 1] = 255;
      colors[(counter * 3) + 2] = 0;
      counter++;

      //Add in an endpoint for each point
      positions[counter * 3] = x + span;
      positions[(counter * 3) + 1] = y;
      positions[(counter * 3) + 2] = z;

      colors[counter * 3] = 255;
      colors[(counter * 3) + 1] = 255;
      colors[(counter * 3) + 2] = 0;
      counter++;
    }

    geometry.addAttribute( 'position', new  THREE.BufferAttribute( positions, 3 ) );
    geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );

    geometry.computeBoundingSphere();

    var material = new THREE.PointCloudMaterial( { size: 2, vertexColors: THREE.VertexColors } );
    particleSystem = new THREE.PointCloud( geometry, material );

    particleSystem.translateX(-plane.x);
    particleSystem.translateY(-plane.y);
    particleSystem.translateZ(-plane.z);

    scene.add( particleSystem );
    scene.remove(blocks);

  };
  xhr.send();

};

function add_sparse_blocks(uuid, scene, label, plane, color) {
  var url = dataSource + '/api/node/' + uuid + '/' + plane.bodies + '/sparsevol-coarse/' + label;
  var cube_size = 32; // need to get this from dvid
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'arraybuffer';

  xhr.onload = function(e) {
    // response is unsigned 8 bit integer
    var responseArray   = new Uint32Array(this.response);
    var dimensions      = new Uint8Array(this.response,1,1);
    var dimensionsOfRun = new Uint8Array(this.response,2,1);
    var spans           = responseArray[2];
    var geometry        = new THREE.BoxGeometry( cube_size, cube_size, cube_size);
    color = color ? color : 0xffff00;
    var mesh            = new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: 0.4 } );
    group               = new THREE.Object3D();

    // loop over the spans and work out the dimensions
    for (var i = 0; i < spans; i++) {
      var offset = i * 4;
      var x = (responseArray[offset + 3] * 32) + 16;
      var y = (responseArray[offset + 4] * 32) + 16;
      var z = (responseArray[offset + 5] * 32) + 16;
      var span = responseArray[offset + 6];

      for (var j = 0; j < span; j++) {
        var cube = new THREE.Mesh(geometry,mesh);

        cube.position.x = parseInt(x) + (j * 32);
        cube.position.y = y;
        cube.position.z = z;

        group.add(cube);
      }
    }

    // places the cut plane at the center of the scene, so that rotation
    // is centered around it.
    group.translateX(-plane.x);
    group.translateY(-plane.y);
    group.translateZ(-plane.z);

    scene.add(group);

    blocks = group;

  };
  xhr.send();

};

// Create the cut plane to show context from the original
// 2D image location
function cut_plane(plane, uuid) {
  var size = 512;

  var imgSrc = null;
  if (plane.axis === 'yz') {
    imgSrc = THREE.ImageUtils.loadTexture(dataSource + '/api/node/' + uuid + '/' + plane.tileSource + '/isotropic/' + plane.axis + '/' + size + '_' + size + '/'+ Math.round(plane.x) + '_' + (Math.round(plane.y) - (size / 2)) + '_' + (plane.z - Math.round((size/8.75) / 2)) +'/jpg');

    // imgSrc = THREE.ImageUtils.loadTexture('http://localhost:8021/test-square.jpg');
  }
  else if (plane.axis === 'xz') {
    imgSrc = THREE.ImageUtils.loadTexture(dataSource + '/api/node/' + uuid + '/' + plane.tileSource + '/isotropic/' + plane.axis + '/' + size + '_' + size + '/'+ (Math.round(plane.x) - (size / 2)) + '_' + Math.round(plane.y) + '_' + (plane.z - Math.round((size/8.75) / 2)) +'/jpg');
    // imgSrc = THREE.ImageUtils.loadTexture('http://localhost:8021/test-square.jpg');
  }
  else {
    imgSrc = THREE.ImageUtils.loadTexture(dataSource + '/api/node/' + uuid + '/' + plane.tileSource + '/isotropic/' + plane.axis + '/' + size + '_' + size + '/'+ (Math.round(plane.x) - (size / 2)) + '_' + (Math.round(plane.y) - (size / 2)) + '_' + plane.z +'/jpg');
    //imgSrc = THREE.ImageUtils.loadTexture('http://localhost:8021/test-square.jpg');
  }

  var planeGeo = new THREE.PlaneGeometry(size,size);
  var cutPlane = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({
    color: 0xffffff,
    side: THREE.DoubleSide,
    transparent: false,
    map: imgSrc,
    opacity: 0.7
  }));
  cutPlane.position.x = parseInt(plane.x);
  cutPlane.position.y = parseInt(plane.y);
  cutPlane.position.z = parseInt(plane.z);
  // Rotate the plane so that it is on the correct axis if it
  // was not cut along the xy.
  if (plane.axis === 'yz') {
    cutPlane.rotation.y = -Math.PI / 2;
    cutPlane.rotation.x = -Math.PI / 2;
  }
  else if (plane.axis === 'xz') {
    cutPlane.rotation.x = -Math.PI / 2;
  }
  else {
    cutPlane.rotation.x = -Math.PI;
  }
  return cutPlane;
};

function add_axis(scene) {
  var geometry        = new THREE.BoxGeometry( 2,2,2 );

  var color           = 0xff0000;
  var mesh            = new THREE.MeshBasicMaterial({ color: color } );
  var x_line = new THREE.Mesh(geometry,mesh);
  x_line.scale.x = 5000;
  x_line.position.x = 5000;
  scene.add(x_line);

  color           = 0x00ff00;
  mesh            = new THREE.MeshBasicMaterial({ color: color } );
  var y_line = new THREE.Mesh(geometry,mesh);
  y_line.scale.y = 5000;
  y_line.position.y = 5000;
  scene.add(y_line);

  color           = 0x0000ff;
  mesh            = new THREE.MeshBasicMaterial({ color: color } );
  var z_line = new THREE.Mesh(geometry,mesh);
  z_line.scale.z = 5000;
  z_line.position.z = 5000;
  scene.add(z_line);
};
<commit_msg>Missed the voxel ratio hard coded in tile fetch<commit_after>var React    = require('react'),
  Router     = require('react-router'),
  config     = require('../common/config'),
  core       = require('../common/core'),
  dataSource = config.baseUrl();

 // variables for THREE
 var camera, scene, renderer;

 // used to store the sparse blocks handle, so we can remove it if needed
 var blocks;

 // allow images to be loaded from other domains.
 THREE.ImageUtils.crossOrigin = '';

var SparseVolViewer = React.createClass({

  getInitialState: function() {
    return {
      'uuid': null,
      'label': null,
      'labelType': null,
      'bodies': null,
      'x': 0,
      'y': 0,
      'z': 0,
      'axis': 'xy',
      'voxRatio': 1
    };
  },

  componentDidMount: function() {
    var self = this;
    // set the uuid on initial load as this wont change during the lifetime
    // of this page.
    var update = 0;
    var new_state = {};
    // foreach property value
    for (var key in this.props) {
       if (this.props.hasOwnProperty(key)) {
         // if the value is not null, then proceed.
         if(this.props[key]) {
           // convert props to state if they are defined and different
          if(this.props[key] !== this.state[key]) {
            new_state[key] = this.props[key];
            update++;
          }
        }
      }
    }

    $.get(config.datatypeInfoUrl(this.props.uuid, this.props.labelType), function(labelInfo) {

      // set the ratio we need to scale the z-axis so that it matches the dimensions of the xy plane.
      // typically this will be a ratio of one for iostropic data, but will be larger when the z
      // dimension has been stretched.
      new_state['voxRatio'] = labelInfo.Extended.VoxelSize[2] / labelInfo.Extended.VoxelSize[0];

      new_state['bodies'] = labelInfo.Base.Syncs[0];
      // trigger an update to the canvas if any of the properties are different
      if (update > 0) {
        self.setState(new_state, function() {
          init(self.state);
          animate();
        });
      }
    });
  },

  componentWillUnmount: function() {
  },

  comsponentWillUpdate: function() {
  },

  componentDidUpdate: function(prevProps, prevState) {
  },

  componentWillReceiveProps: function (props) {
    var update = 0;
    var new_state = {};

    if (!this.state.bodies) {
      return;
    }

    // foreach property value
    for (var key in props) {
       if (props.hasOwnProperty(key)) {
         // if the value is not null, then proceed.
         if(props[key]) {
           // convert props to state if they are defined and different
          if(props[key] !== this.state[key]) {
            new_state[key] = props[key];
            update++;
          }
        }
      }
    }

    // trigger an update to the canvas if any of the properties are different
    if (update > 0) {
      this.setState(new_state, function() {
        init(this.state);
        animate();
      });
    }
  },

  closeHandler: function() {
    console.log('should be implemented by the parent container');
    // http://stackoverflow.com/questions/27227792/react-js-removing-a-component
  },

  render: function () {
    return (

Body ID: {this.state.label}

 Close [x]

    );
  }

});

module.exports = SparseVolViewer;

function init(state) {
  renderer = new THREE.WebGLRenderer();
  renderer.setClearColor( 0x000000 );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize(500, 500 );
  document.getElementById('volume_viewer').appendChild( renderer.domElement );

  camera = new THREE.PerspectiveCamera( 70, 500 / 500, 1, 20000 );
  camera.position.z = 8000;

  scene = new THREE.Scene();

  // add lighting
  //var light = new THREE.DirectionalLight( 0xffffff, 1 );
  //light.position.set( 20, 20, 20 ).normalize();
  //scene.add( light );

  // LIGHTS
  var ambientLight = new THREE.AmbientLight( 0x222222 );

  var light = new THREE.DirectionalLight( 0xFFFFFF, 1.0 );
  light.position.set( 200, 400, 500 );

  var light2 = new THREE.DirectionalLight( 0xFFFFFF, 1.0 );
  light2.position.set( -500, 250, -200 );

  scene.add(ambientLight);
  scene.add(light);
  scene.add(light2);

  var plane = {};

  for (key in state) {
    if (state.hasOwnProperty(key)) {
      plane[key] = state[key];
    }
  }

  compose_scene(plane);

  // lastly setup the controls.
  controls = new THREE.TrackballControls(camera, renderer.domElement);
}

function animate() {
  requestAnimationFrame( animate );
  controls.update();
  renderer.render( scene, camera );
}

function compose_scene(plane) {
  var side = new THREE.MeshBasicMaterial({
    color: 0x00ffff,
    side: THREE.DoubleSide,
    opacity: 0.5
  });

  // grab the data from DVID at this point.
  var label = plane.label;

  add_cut_planes(plane.uuid,scene, plane);

  if (label) {
    add_sparse_blocks(plane.uuid, scene, label, plane);
    // adding in the more complete sparse volume really kills the browser
    // this is probably not viable until either memory or cpu requirements
    // can be figured out.
    //
    add_sparse(plane.uuid, scene, label, plane);
  }
  add_axis(scene);
};

function add_cut_planes(uuid, scene, plane) {
  var cross_cut = new THREE.Object3D();
  plane.axis = 'xy';
  cross_cut.add(cut_plane(plane, uuid));
  plane.axis = 'yz';
  cross_cut.add(cut_plane(plane, uuid));
  plane.axis = 'xz';
  cross_cut.add(cut_plane(plane, uuid));
  cross_cut.translateX(-plane.x);
  cross_cut.translateY(-plane.y);
  cross_cut.translateZ(-plane.z);
  scene.add(cross_cut);
};

function add_sparse(uuid, scene, label, plane, color) {
  var url = dataSource + '/api/node/' + uuid + '/' + plane.bodies + '/sparsevol/' + label;

  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'arraybuffer';

  xhr.onload = function(e) {
    // response is unsigned 8 bit integer
    var responseArray   = new Uint32Array(this.response);
    var dimensions      = new Uint8Array(this.response,1,1);
    var dimensionsOfRun = new Uint8Array(this.response,2,1);
    var spans           = responseArray[2];
    var geometry        = new THREE.BufferGeometry();

    // how many points would we need to draw to get the RLE volume filled.
    var points = 0;
    for (var i = 0; i < spans; i++) {
      var offset = i * 4;
      var span = responseArray[offset + 6];
      points += span;
    }

    var positions = new Float32Array(spans * 3 * 2); //*2 to capture two points per span (startx,endx)
    var colors    = new Float32Array(spans * 3 * 2);
    var color     = new THREE.Color();

    // loop over the spans and work out the dimensions
    var counter = 0; //so we can add two points per span
    for (var i = 0; i < spans; i++) {
      var offset = i * 4;
      var x = (responseArray[offset + 3]);
      var y = (responseArray[offset + 4]);
      var z = responseArray[offset + 5]; //Original Z from server
      //Modify z to handle anisotropic data (stretching)
      z = Math.round( (z - plane.z) * plane.voxRatio ) + plane.z;
      var span = responseArray[offset + 6];

      // this is where we would add all the additional points if we
      // wanted to fill in the RLE gaps.
      //for (j = 0; j < span; j++) {
        // do some work
      //}

      positions[counter * 3] = x;
      positions[(counter * 3) + 1] = y;
      positions[(counter * 3) + 2] = z;

      colors[counter * 3] = 255;
      colors[(counter * 3) + 1] = 255;
      colors[(counter * 3) + 2] = 0;
      counter++;

      //Add in an endpoint for each point
      positions[counter * 3] = x + span;
      positions[(counter * 3) + 1] = y;
      positions[(counter * 3) + 2] = z;

      colors[counter * 3] = 255;
      colors[(counter * 3) + 1] = 255;
      colors[(counter * 3) + 2] = 0;
      counter++;
    }

    geometry.addAttribute( 'position', new  THREE.BufferAttribute( positions, 3 ) );
    geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );

    geometry.computeBoundingSphere();

    var material = new THREE.PointCloudMaterial( { size: 2, vertexColors: THREE.VertexColors } );
    particleSystem = new THREE.PointCloud( geometry, material );

    particleSystem.translateX(-plane.x);
    particleSystem.translateY(-plane.y);
    particleSystem.translateZ(-plane.z);

    scene.add( particleSystem );
    scene.remove(blocks);

  };
  xhr.send();

};

function add_sparse_blocks(uuid, scene, label, plane, color) {
  var url = dataSource + '/api/node/' + uuid + '/' + plane.bodies + '/sparsevol-coarse/' + label;
  var cube_size = 32; // need to get this from dvid
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'arraybuffer';

  xhr.onload = function(e) {
    // response is unsigned 8 bit integer
    var responseArray   = new Uint32Array(this.response);
    var dimensions      = new Uint8Array(this.response,1,1);
    var dimensionsOfRun = new Uint8Array(this.response,2,1);
    var spans           = responseArray[2];
    var geometry        = new THREE.BoxGeometry( cube_size, cube_size, cube_size);
    color = color ? color : 0xffff00;
    var mesh            = new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: 0.4 } );
    group               = new THREE.Object3D();

    // loop over the spans and work out the dimensions
    for (var i = 0; i < spans; i++) {
      var offset = i * 4;
      var x = (responseArray[offset + 3] * 32) + 16;
      var y = (responseArray[offset + 4] * 32) + 16;
      var z = (responseArray[offset + 5] * 32) + 16;
      var span = responseArray[offset + 6];

      for (var j = 0; j < span; j++) {
        var cube = new THREE.Mesh(geometry,mesh);

        cube.position.x = parseInt(x) + (j * 32);
        cube.position.y = y;
        cube.position.z = z;

        group.add(cube);
      }
    }

    // places the cut plane at the center of the scene, so that rotation
    // is centered around it.
    group.translateX(-plane.x);
    group.translateY(-plane.y);
    group.translateZ(-plane.z);

    scene.add(group);

    blocks = group;

  };
  xhr.send();

};

// Create the cut plane to show context from the original
// 2D image location
function cut_plane(plane, uuid) {
  var size = 512;

  var imgSrc = null;
  if (plane.axis === 'yz') {
    imgSrc = THREE.ImageUtils.loadTexture(dataSource + '/api/node/' + uuid + '/' + plane.tileSource + '/isotropic/' + plane.axis + '/' + size + '_' + size + '/'+ Math.round(plane.x) + '_' + (Math.round(plane.y) - (size / 2)) + '_' + (plane.z - Math.round( ( size / plane.voxRatio ) / 2) ) +'/jpg');

    // imgSrc = THREE.ImageUtils.loadTexture('http://localhost:8021/test-square.jpg');
  }
  else if (plane.axis === 'xz') {
    imgSrc = THREE.ImageUtils.loadTexture(dataSource + '/api/node/' + uuid + '/' + plane.tileSource + '/isotropic/' + plane.axis + '/' + size + '_' + size + '/'+ (Math.round(plane.x) - (size / 2)) + '_' + Math.round(plane.y) + '_' + (plane.z - Math.round( ( size / plane.voxRatio ) / 2) ) +'/jpg');
    // imgSrc = THREE.ImageUtils.loadTexture('http://localhost:8021/test-square.jpg');
  }
  else {
    imgSrc = THREE.ImageUtils.loadTexture(dataSource + '/api/node/' + uuid + '/' + plane.tileSource + '/isotropic/' + plane.axis + '/' + size + '_' + size + '/'+ (Math.round(plane.x) - (size / 2)) + '_' + (Math.round(plane.y) - (size / 2)) + '_' + plane.z +'/jpg');
    //imgSrc = THREE.ImageUtils.loadTexture('http://localhost:8021/test-square.jpg');
  }

  var planeGeo = new THREE.PlaneGeometry(size,size);
  var cutPlane = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({
    color: 0xffffff,
    side: THREE.DoubleSide,
    transparent: false,
    map: imgSrc,
    opacity: 0.7
  }));
  cutPlane.position.x = parseInt(plane.x);
  cutPlane.position.y = parseInt(plane.y);
  cutPlane.position.z = parseInt(plane.z);
  // Rotate the plane so that it is on the correct axis if it
  // was not cut along the xy.
  if (plane.axis === 'yz') {
    cutPlane.rotation.y = -Math.PI / 2;
    cutPlane.rotation.x = -Math.PI / 2;
  }
  else if (plane.axis === 'xz') {
    cutPlane.rotation.x = -Math.PI / 2;
  }
  else {
    cutPlane.rotation.x = -Math.PI;
  }
  return cutPlane;
};

function add_axis(scene) {
  var geometry        = new THREE.BoxGeometry( 2,2,2 );

  var color           = 0xff0000;
  var mesh            = new THREE.MeshBasicMaterial({ color: color } );
  var x_line = new THREE.Mesh(geometry,mesh);
  x_line.scale.x = 5000;
  x_line.position.x = 5000;
  scene.add(x_line);

  color           = 0x00ff00;
  mesh            = new THREE.MeshBasicMaterial({ color: color } );
  var y_line = new THREE.Mesh(geometry,mesh);
  y_line.scale.y = 5000;
  y_line.position.y = 5000;
  scene.add(y_line);

  color           = 0x0000ff;
  mesh            = new THREE.MeshBasicMaterial({ color: color } );
  var z_line = new THREE.Mesh(geometry,mesh);
  z_line.scale.z = 5000;
  z_line.position.z = 5000;
  scene.add(z_line);
};
<|endoftext|>