<commit_before>import times from 'lodash/times';
import range from 'lodash/range';
import has from 'lodash/has';

import PropTypes from 'prop-types';

import React, { Component } from 'react';
import Rheostat from 'rheostat';
import cx from 'classnames';

import Pit from './Pit.js';

import autoHideContainerHOC from '../../decorators/autoHideContainer.js';
import headerFooterHOC from '../../decorators/headerFooter.js';

class Slider extends Component {
  static propTypes = {
    refine: PropTypes.func.isRequired,
    min: PropTypes.number.isRequired,
    max: PropTypes.number.isRequired,
    values: PropTypes.arrayOf(PropTypes.number).isRequired,
    pips: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
    step: PropTypes.number.isRequired,
    tooltips: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.shape({ format: PropTypes.func.isRequired }),
    ]),
  };

  get isDisabled() {
    return this.props.min === this.props.max;
  }

  handleChange = ({ min, max, values }) => {
    // when Slider is disabled, we alter `min, max` values
    // in order to render a "disabled state" Slider. Since we alter
    // theses values, Rheostat trigger a "change" event which trigger a new
    // search to Algolia with wrong values.
    if (!this.isDisabled) {
      const { refine } = this.props;
      refine([
        min === values[0] ? undefined : values[0],
        max === values[1] ? undefined : values[1],
      ]);
    }
  };

  // creates an array number where to display a pit point on the slider
  computeDefaultPitPoints({ min, max }) {
    const totalLength = max - min;
    const steps = 34;
    const stepsLength = totalLength / steps;

    const pitPoints = [
      min,
      ...times(steps - 1, step => min + stepsLength * (step + 1)),
      max,
    ]
      // bug with `key={ 0 }` and preact, see https://github.com/developit/preact/issues/642
      .map(pitPoint => (pitPoint === 0 ? 0.000001 : pitPoint));

    return pitPoints;
  }

  // creates an array of values where the slider should snap to
  computeSnapPoints({ min, max, step }) {
    return [...range(min, max, step), max];
  }

  createHandleComponent = tooltips => props => {
    // display only two decimals after comma,
    // and apply `tooltips.format()` if any`
    const roundedValue =
      Math.round(parseFloat(props['aria-valuenow']) * 100) / 100;
    const value = has(tooltips, 'format')
      ? tooltips.format(roundedValue)
      : roundedValue;

    const className = cx('ais-range-slider--handle', props.className, {
      'ais-range-slider--handle-lower': props['data-handle-key'] === 0,
      'ais-range-slider--handle-upper': props['data-handle-key'] === 1,
    });

    return (

        {tooltips
          ?

              {value}

          : null}

    );
  };

  render() {
    const { tooltips, step, pips, values } = this.props;

    const { min, max } = this.isDisabled
      ? { min: this.props.min, max: this.props.max + 0.001 }
      : this.props;

    const snapPoints = this.computeSnapPoints({ min, max, step });
    const pitPoints =
      pips === true || pips === undefined || pips === false
        ? this.computeDefaultPitPoints({ min, max })
        : pips;

    return (

        <Rheostat
          handle={this.createHandleComponent(tooltips)}
          onChange={this.handleChange}
          min={min}
          max={max}
          pitComponent={Pit}
          pitPoints={pitPoints}
          snap={true}
          snapPoints={snapPoints}
          values={this.isDisabled ? [min, max] : values}
          disabled={this.isDisabled}
        />

    );
  }
}

export default autoHideContainerHOC(headerFooterHOC(Slider));
<commit_msg>fix(slider): edge case when min > max (#2336)<commit_after>import times from 'lodash/times';
import range from 'lodash/range';
import has from 'lodash/has';

import PropTypes from 'prop-types';

import React, { Component } from 'react';
import Rheostat from 'rheostat';
import cx from 'classnames';

import Pit from './Pit.js';

import autoHideContainerHOC from '../../decorators/autoHideContainer.js';
import headerFooterHOC from '../../decorators/headerFooter.js';

class Slider extends Component {
  static propTypes = {
    refine: PropTypes.func.isRequired,
    min: PropTypes.number.isRequired,
    max: PropTypes.number.isRequired,
    values: PropTypes.arrayOf(PropTypes.number).isRequired,
    pips: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
    step: PropTypes.number.isRequired,
    tooltips: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.shape({ format: PropTypes.func.isRequired }),
    ]),
  };

  get isDisabled() {
    return this.props.min >= this.props.max;
  }

  handleChange = ({ min, max, values }) => {
    // when Slider is disabled, we alter `min, max` values
    // in order to render a "disabled state" Slider. Since we alter
    // theses values, Rheostat trigger a "change" event which trigger a new
    // search to Algolia with wrong values.
    if (!this.isDisabled) {
      const { refine } = this.props;
      refine([
        min === values[0] ? undefined : values[0],
        max === values[1] ? undefined : values[1],
      ]);
    }
  };

  // creates an array number where to display a pit point on the slider
  computeDefaultPitPoints({ min, max }) {
    const totalLength = max - min;
    const steps = 34;
    const stepsLength = totalLength / steps;

    const pitPoints = [
      min,
      ...times(steps - 1, step => min + stepsLength * (step + 1)),
      max,
    ]
      // bug with `key={ 0 }` and preact, see https://github.com/developit/preact/issues/642
      .map(pitPoint => (pitPoint === 0 ? 0.000001 : pitPoint));

    return pitPoints;
  }

  // creates an array of values where the slider should snap to
  computeSnapPoints({ min, max, step }) {
    return [...range(min, max, step), max];
  }

  createHandleComponent = tooltips => props => {
    // display only two decimals after comma,
    // and apply `tooltips.format()` if any`
    const roundedValue =
      Math.round(parseFloat(props['aria-valuenow']) * 100) / 100;
    const value = has(tooltips, 'format')
      ? tooltips.format(roundedValue)
      : roundedValue;

    const className = cx('ais-range-slider--handle', props.className, {
      'ais-range-slider--handle-lower': props['data-handle-key'] === 0,
      'ais-range-slider--handle-upper': props['data-handle-key'] === 1,
    });

    return (

        {tooltips
          ?

              {value}

          : null}

    );
  };

  render() {
    const { tooltips, step, pips, values } = this.props;

    const { min, max } = this.isDisabled
      ? { min: this.props.min, max: this.props.max + 0.001 }
      : this.props;

    const snapPoints = this.computeSnapPoints({ min, max, step });
    const pitPoints =
      pips === true || pips === undefined || pips === false
        ? this.computeDefaultPitPoints({ min, max })
        : pips;

    return (

        <Rheostat
          handle={this.createHandleComponent(tooltips)}
          onChange={this.handleChange}
          min={min}
          max={max}
          pitComponent={Pit}
          pitPoints={pitPoints}
          snap={true}
          snapPoints={snapPoints}
          values={this.isDisabled ? [min, max] : values}
          disabled={this.isDisabled}
        />

    );
  }
}

export default autoHideContainerHOC(headerFooterHOC(Slider));
<|endoftext|>


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

function HelloMessage(person) {
  return (

Hello {person.name}

  );
}

var WhitHouse = React.createClass({
  getIntialState: function () {
    return {
      representative: {
        title: 'Prez',
        name: 'Obama',
      },
    };
  },

  render: function () {
    return (

        White House Peeps
        <HelloMessage name={this.state.representative} />

    );
  },
});
<commit_msg>erased test file<commit_after><|endoftext|>


<commit_before>import Path from 'path';
import WebPack from 'webpack';

function getOutputFileName(filePath, hash) {
  return `${Path.basename(filePath, Path.extname(filePath))}.${hash}${Path.extname(filePath)}`;
}

export default function () {
  this.cacheable && this.cacheable();

  const loaderOpts = this.options.htmlAsset;

  const callback = this.async();
  const filePath = this.resource;
  const context = loaderOpts.output.context;
  const entryName = Path.relative(
    Path.join(process.cwd(), context),
    filePath
  );
  const outputFileDir = Path.dirname(entryName);
  const outputFileName = getOutputFileName(
    filePath,
    '[hash]'
  );

  const entry = {
    [entryName]: filePath
  };

  const compiler = WebPack({
    ...loaderOpts,
    entry,
    output: {
      ...loaderOpts.output,
      filename: Path.join(
        outputFileDir,
        outputFileName
      )
    }
  });

  // TODO: file system.

  compiler.run(function (err, stats) {
    const outputFilePath = Path.sep +
      Path.join(
        outputFileDir,
        getOutputFileName(
          stats.toJson().modules[0].name,
          stats.compilation.hash
        )
      );

    callback(
      err,
      `module.exports = ${JSON.stringify(outputFilePath)}`
    );
  });
}
<commit_msg>chore(HTMLAsset) Add an FS related TODO.<commit_after>import Path from 'path';
import WebPack from 'webpack';

function getOutputFileName(filePath, hash) {
  return `${Path.basename(filePath, Path.extname(filePath))}.${hash}${Path.extname(filePath)}`;
}

export default function () {
  this.cacheable && this.cacheable();

  const loaderOpts = this.options.htmlAsset;

  const callback = this.async();
  const filePath = this.resource;
  const context = loaderOpts.output.context;
  const entryName = Path.relative(
    Path.join(process.cwd(), context),
    filePath
  );
  const outputFileDir = Path.dirname(entryName);
  const outputFileName = getOutputFileName(
    filePath,
    '[hash]'
  );

  const entry = {
    [entryName]: filePath
  };

  const compiler = WebPack({
    ...loaderOpts,
    entry,
    output: {
      ...loaderOpts.output,
      filename: Path.join(
        outputFileDir,
        outputFileName
      )
    }
  });

  // TRICKY: Make file system compatible with WDS.
  // TODO: Pass in the MemoryFS data object from the parent compiler.

  compiler.run(function (err, stats) {
    const outputFilePath = Path.sep +
      Path.join(
        outputFileDir,
        getOutputFileName(
          stats.toJson().modules[0].name,
          stats.compilation.hash
        )
      );

    callback(
      err,
      `module.exports = ${JSON.stringify(outputFilePath)}`
    );
  });
}
<|endoftext|>


<commit_before>import React from 'react';
import Logo from './logo';
import Helmet from 'react-helmet';

export default function Header(props) {
  const colours = props.colours.random;
  const variables = props.colours.named;

  return

        <Logo image={colours[0].logo}/>

          A 24 hour Hackathon from the University of Nottingham, brought to you by HackSoc.

          November 28th and 29th 2020

        <tito-button style={{background: variables.hnRed}}event="hacknotts/2020">Register</tito-button>

    <Helmet>

    </Helmet>
  ;
}<commit_msg>Forgot a title (#77)<commit_after>import React from 'react';
import Logo from './logo';
import Helmet from 'react-helmet';

export default function Header(props) {
  const colours = props.colours.random;
  const variables = props.colours.named;

  return

        <Logo image={colours[0].logo}/>

          A 24 hour Hackathon from the University of Nottingham, brought to you by HackSoc.

          November 28th and 29th 2020

        <tito-button style={{background: variables.hnRed}}event="hacknotts/2020">Register</tito-button>

    <Helmet>

      <title>HackNotts</title>
    </Helmet>
  ;
}
<|endoftext|>


<commit_before>import React from 'react';
import { Menu, Icon, Popover } from 'antd';
import styles from './Header.less';
import { fun, model, action, urlMap, config, updateLog } from '../../common';

const Header = ( { dispatch, user } ) => {
  const recharge = () => {
    fun.showNotification( '充值演示', '替换我吧！' );
  }
  const userInfo = (

当前账户余额：3000

      朕要充值

  );
  /**
   * 退出登录
   */
  const logout = () => {
    dispatch( { type: fun.fuse( model.app, action.logout ) } );
  }

  return (

        <Popover content={ userInfo } title="用户信息" placement="leftTop">
          hi~ { user.doctorName }
        </Popover>

          <Popover content={ updateLog.getLog( config.ver ) } title={ '更新日志 (' + config.ver + ')' } placement="bottomRight">
            ver{ config.ver }
          </Popover>

          首页  退出登录

  );
};

export default Header;
<commit_msg>重装后测试<commit_after>import React from 'react';
import { Menu, Icon, Popover } from 'antd';
import styles from './Header.less';
import { fun, model, action, urlMap, config, updateLog } from '../../common';

const Header = ( { dispatch, user } ) => {
  const recharge = () => {
    fun.showNotification( '充值演示', '替换我吧！' );
  }
  const userInfo = (

当前账户余额：3000

      我要充值

  );
  /**
   * 退出登录
   */
  const logout = () => {
    dispatch( { type: fun.fuse( model.app, action.logout ) } );
  }

  return (

        <Popover content={ userInfo } title="用户信息" placement="leftTop">
          hi~ { user.doctorName }
        </Popover>

          <Popover content={ updateLog.getLog( config.ver ) } title={ '更新日志 (' + config.ver + ')' } placement="bottomRight">
            ver{ config.ver }
          </Popover>

          首页  退出登录

  );
};

export default Header;
<|endoftext|>


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

export default class Footer extends Component{
  getBuiltWith(){
    return [
      { name: "Bulma", link: "http://bulma.io/" },
      { name: "pokeapi", link: "http://pokeapi.co/" },
      { name: "Pokestadium", link: "http://www.pokestadium.com/" }
    ];
  }
  render(){
    let technologies = this.getBuiltWith();
    let builtWithNodes = technologies.map(function(technology, index){
      return(
        {technology.name}
      );
    });
    return(

              react-examples by Mike Zrimsek

              View Source

              Built With: {builtWithNodes}

    );
  }
}
<commit_msg>add google analytics to footer links<commit_after>import React, {Component} from 'react';

export default class Footer extends Component{
  constructor(props){
    super(props);
    this.trackClick = this.trackClick.bind(this);
  }
  trackClick(category){
    ga('send', 'event', category, 'click', 'Footer');
  }
  getBuiltWith(){
    return [
      { name: "Bulma", link: "http://bulma.io/" },
      { name: "pokeapi", link: "http://pokeapi.co/" },
      { name: "Pokestadium", link: "http://www.pokestadium.com/" }
    ];
  }
  render(){
    let technologies = this.getBuiltWith();
    let builtWithNodes = technologies.map(function(technology, index){
      return(
        {technology.name}
      );
    });
    return(

              react-examples by Mike Zrimsek

              View Source

              Built With: {builtWithNodes}

    );
  }
}
<|endoftext|>


<commit_before>// Copyright 2014 Globo.com Player authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

var BaseObject = require('../../base/base_object');
var _ = require('underscore');
var PlayerInfo = require('../player_info')

/* Playback Plugins */
var HTML5VideoPlayback = require('../../playbacks/html5_video');
var FlashVideoPlayback = require('../../playbacks/flash');
var HTML5AudioPlayback = require('../../playbacks/html5_audio');
var HLSVideoPlayback = require('../../playbacks/hls');
var NoOp = require('../../playbacks/no_op');

/* Container Plugins */
var SpinnerThreeBouncePlugin = require('../../plugins/spinner_three_bounce');
var StatsPlugin = require('../../plugins/stats');
var WaterMarkPlugin = require('../../plugins/watermark');
var PosterPlugin = require('../../plugins/poster');

/* Core Plugins */
var BackgroundButton = require('../../plugins/background_button');
var SeekTime = require('../../plugins/seek_time');

class Loader extends BaseObject {
  constructor(externalPlugins) {
    super()
    this.playerInfo = PlayerInfo.getInstance()
    this.playbackPlugins = [FlashVideoPlayback, HTML5VideoPlayback, HTML5AudioPlayback, HLSVideoPlayback, NoOp]
    this.containerPlugins = [SpinnerThreeBouncePlugin, WaterMarkPlugin, PosterPlugin, StatsPlugin]
    this.globalPlugins = [BackgroundButton, SeekTime]
    if (externalPlugins) {
      this.addExternalPlugins(externalPlugins)
    }
  }

  addExternalPlugins(plugins) {
    if (plugins.playback) { this.playbackPlugins = plugins.playback.concat(this.playbackPlugins) }
    if (plugins.container) { this.containerPlugins = plugins.container.concat(this.containerPlugins) }
    if (plugins.core) { this.globalPlugins = plugins.core.concat(this.globalPlugins) }
    this.playerInfo.playbackPlugins = this.playbackPlugins
  }

  getPlugin(name) {
    var allPlugins = _.union(this.containerPlugins, this.playbackPlugins, this.globalPlugins)
    return _.find(allPlugins, function(plugin) { return plugin.prototype.name === name })
  }
}

module.exports = Loader;
<commit_msg>loader: s/globalPlugins/corePlugins<commit_after>// Copyright 2014 Globo.com Player authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

var BaseObject = require('../../base/base_object');
var _ = require('underscore');
var PlayerInfo = require('../player_info')

/* Playback Plugins */
var HTML5VideoPlayback = require('../../playbacks/html5_video');
var FlashVideoPlayback = require('../../playbacks/flash');
var HTML5AudioPlayback = require('../../playbacks/html5_audio');
var HLSVideoPlayback = require('../../playbacks/hls');
var NoOp = require('../../playbacks/no_op');

/* Container Plugins */
var SpinnerThreeBouncePlugin = require('../../plugins/spinner_three_bounce');
var StatsPlugin = require('../../plugins/stats');
var WaterMarkPlugin = require('../../plugins/watermark');
var PosterPlugin = require('../../plugins/poster');

/* Core Plugins */
var BackgroundButton = require('../../plugins/background_button');
var SeekTime = require('../../plugins/seek_time');

class Loader extends BaseObject {
  constructor(externalPlugins) {
    super()
    this.playerInfo = PlayerInfo.getInstance()
    this.playbackPlugins = [FlashVideoPlayback, HTML5VideoPlayback, HTML5AudioPlayback, HLSVideoPlayback, NoOp]
    this.containerPlugins = [SpinnerThreeBouncePlugin, WaterMarkPlugin, PosterPlugin, StatsPlugin]
    this.corePlugins = [BackgroundButton, SeekTime]
    if (externalPlugins) {
      this.addExternalPlugins(externalPlugins)
    }
  }

  addExternalPlugins(plugins) {
    if (plugins.playback) { this.playbackPlugins = plugins.playback.concat(this.playbackPlugins) }
    if (plugins.container) { this.containerPlugins = plugins.container.concat(this.containerPlugins) }
    if (plugins.core) { this.corePlugins = plugins.core.concat(this.corePlugins) }
    this.playerInfo.playbackPlugins = this.playbackPlugins
  }

  getPlugin(name) {
    var allPlugins = _.union(this.containerPlugins, this.playbackPlugins, this.corePlugins)
    return _.find(allPlugins, function(plugin) { return plugin.prototype.name === name })
  }
}

module.exports = Loader;
<|endoftext|>


<commit_before>var registerComponent = require('../core/register-component').registerComponent;
var THREE = require('../../lib/three');

// To avoid recalculation at every mouse movement tick
var PI_2 = Math.PI / 2;

module.exports.Component = registerComponent('look-controls', {
  defaults: {
    value: {
      enabled: true
    }
  },

  init: {
    value: function () {
      var scene = this.el.sceneEl;
      this.setupMouseControls();
      this.setupHMDControls();
      this.attachEventListeners();
      scene.addBehavior(this);
    }
  },

  setupMouseControls: {
    value: function () {
      var object3D = this.el.object3D;
      this.canvasEl = document.querySelector('vr-scene').canvas;
      // The canvas where the scene is painted
      this.mouseDown = false;
      // To avoid gimbal lock
      object3D.rotation.order = 'YXZ';
      this.pitchObject = new THREE.Object3D();
      this.yawObject = new THREE.Object3D();
      this.yawObject.position.y = 10;
      this.yawObject.add(this.pitchObject);
    }
  },

  setupHMDControls: {
    value: function () {
      this.dolly = new THREE.Object3D();
      this.euler = new THREE.Euler();
      this.controls = new THREE.VRControls(this.dolly);
      this.zeroQuaternion = new THREE.Quaternion();
    }
  },

  attachEventListeners: {
    value: function () {
      var canvasEl = document.querySelector('vr-scene').canvas;
      // Mouse Events
      canvasEl.addEventListener('mousedown', this.onMouseDown.bind(this), true);
      canvasEl.addEventListener('mousemove', this.onMouseMove.bind(this), true);
      canvasEl.addEventListener('mouseup', this.releaseMouse.bind(this), true);
      canvasEl.addEventListener('mouseout', this.releaseMouse.bind(this), true);

      // Touch events
      canvasEl.addEventListener('touchstart', this.onTouchStart.bind(this));
      canvasEl.addEventListener('touchmove', this.onTouchMove.bind(this));
      canvasEl.addEventListener('touchend', this.onTouchEnd.bind(this));
    }
  },

  update: {
    value: (function () {
      var hmdEuler = new THREE.Euler();
      return function () {
        var hmdQuaternion;
        var pitchObject = this.pitchObject;
        var yawObject = this.yawObject;
        if (!this.data.enabled) { return; }
        hmdQuaternion = this.updateHMDQuaternion();
        hmdEuler.setFromQuaternion(hmdQuaternion);
        this.el.setAttribute('rotation', {
          x: THREE.Math.radToDeg(hmdEuler.x) + THREE.Math.radToDeg(pitchObject.rotation.x),
          y: THREE.Math.radToDeg(hmdEuler.y) + THREE.Math.radToDeg(yawObject.rotation.y),
          z: THREE.Math.radToDeg(hmdEuler.z)
        });
      };
    })()
  },

  updateHMDQuaternion: {
    value: (function () {
      var hmdQuaternion = new THREE.Quaternion();
      return function () {
        var dolly = this.dolly;
        this.controls.update();
        if (!this.zeroed && !dolly.quaternion.equals(this.zeroQuaternion)) {
          this.zeroOrientation();
          this.zeroed = true;
        }
        hmdQuaternion.copy(this.zeroQuaternion).multiply(dolly.quaternion);
        return hmdQuaternion;
      };
    })()
  },

  zeroOrientation: {
    value: function () {
      var euler = new THREE.Euler();
      euler.setFromQuaternion(this.dolly.quaternion.clone().inverse());
      // Cancel out roll and pitch. We want to only reset yaw
      euler.z = 0;
      euler.x = 0;
      this.zeroQuaternion.setFromEuler(euler);
    }
  },

  onMouseMove: {
    value: function (event) {
      var pitchObject = this.pitchObject;
      var yawObject = this.yawObject;

      if (!this.mouseDown || !this.data.enabled) { return; }

      var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
      var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;

      yawObject.rotation.y -= movementX * 0.002;
      pitchObject.rotation.x -= movementY * 0.002;
      pitchObject.rotation.x = Math.max(-PI_2, Math.min(PI_2, pitchObject.rotation.x));
    }
  },

  onMouseDown: {
    value: function (event) {
      this.mouseDown = true;
    }
  },

  releaseMouse: {
    value: function () {
      this.mouseDown = false;
    }
  },

  onTouchStart: {
    value: function (e) {
      if (e.touches.length !== 1) { return; }
      this.touchStart = {
        x: e.touches[0].pageX,
        y: e.touches[0].pageY
      };
      this.touchStarted = true;
    }
  },

  onTouchMove: {
    value: function (e) {
      var deltaY;
      var yawObject = this.yawObject;
      if (!this.touchStarted) { return; }
      deltaY = 2 * Math.PI * (e.touches[0].pageX - this.touchStart.x) / this.canvasEl.clientWidth;
      // Limits touch orientaion to to yaw (y axis)
      yawObject.rotation.y -= deltaY * 0.5;
      this.touchStart = {
        x: e.touches[0].pageX,
        y: e.touches[0].pageY
      };
    }
  },

  onTouchEnd: {
    value: function () {
      this.touchStarted = false;
    }
  }

});
<commit_msg>Adding angles order to hmdQuaternion to prevent gimbal lock<commit_after>var registerComponent = require('../core/register-component').registerComponent;
var THREE = require('../../lib/three');

// To avoid recalculation at every mouse movement tick
var PI_2 = Math.PI / 2;

module.exports.Component = registerComponent('look-controls', {
  defaults: {
    value: {
      enabled: true
    }
  },

  init: {
    value: function () {
      var scene = this.el.sceneEl;
      this.setupMouseControls();
      this.setupHMDControls();
      this.attachEventListeners();
      scene.addBehavior(this);
    }
  },

  setupMouseControls: {
    value: function () {
      this.canvasEl = document.querySelector('vr-scene').canvas;
      // The canvas where the scene is painted
      this.mouseDown = false;
      this.pitchObject = new THREE.Object3D();
      this.yawObject = new THREE.Object3D();
      this.yawObject.position.y = 10;
      this.yawObject.add(this.pitchObject);
    }
  },

  setupHMDControls: {
    value: function () {
      this.dolly = new THREE.Object3D();
      this.euler = new THREE.Euler();
      this.controls = new THREE.VRControls(this.dolly);
      this.zeroQuaternion = new THREE.Quaternion();
    }
  },

  attachEventListeners: {
    value: function () {
      var canvasEl = document.querySelector('vr-scene').canvas;
      // Mouse Events
      canvasEl.addEventListener('mousedown', this.onMouseDown.bind(this), true);
      canvasEl.addEventListener('mousemove', this.onMouseMove.bind(this), true);
      canvasEl.addEventListener('mouseup', this.releaseMouse.bind(this), true);
      canvasEl.addEventListener('mouseout', this.releaseMouse.bind(this), true);

      // Touch events
      canvasEl.addEventListener('touchstart', this.onTouchStart.bind(this));
      canvasEl.addEventListener('touchmove', this.onTouchMove.bind(this));
      canvasEl.addEventListener('touchend', this.onTouchEnd.bind(this));
    }
  },

  update: {
    value: (function () {
      var hmdEuler = new THREE.Euler();
      hmdEuler.order = 'YXZ';
      return function () {
        var hmdQuaternion;
        var pitchObject = this.pitchObject;
        var yawObject = this.yawObject;
        if (!this.data.enabled) { return; }
        hmdQuaternion = this.updateHMDQuaternion();
        hmdEuler.setFromQuaternion(hmdQuaternion);
        this.el.setAttribute('rotation', {
          x: THREE.Math.radToDeg(hmdEuler.x) + THREE.Math.radToDeg(pitchObject.rotation.x),
          y: THREE.Math.radToDeg(hmdEuler.y) + THREE.Math.radToDeg(yawObject.rotation.y),
          z: THREE.Math.radToDeg(hmdEuler.z)
        });
      };
    })()
  },

  updateHMDQuaternion: {
    value: (function () {
      var hmdQuaternion = new THREE.Quaternion();
      return function () {
        var dolly = this.dolly;
        this.controls.update();
        if (!this.zeroed && !dolly.quaternion.equals(this.zeroQuaternion)) {
          this.zeroOrientation();
          this.zeroed = true;
        }
        hmdQuaternion.copy(this.zeroQuaternion).multiply(dolly.quaternion);
        return hmdQuaternion;
      };
    })()
  },

  zeroOrientation: {
    value: function () {
      var euler = new THREE.Euler();
      euler.setFromQuaternion(this.dolly.quaternion.clone().inverse());
      // Cancel out roll and pitch. We want to only reset yaw
      euler.z = 0;
      euler.x = 0;
      this.zeroQuaternion.setFromEuler(euler);
    }
  },

  onMouseMove: {
    value: function (event) {
      var pitchObject = this.pitchObject;
      var yawObject = this.yawObject;

      if (!this.mouseDown || !this.data.enabled) { return; }

      var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
      var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;

      yawObject.rotation.y -= movementX * 0.002;
      pitchObject.rotation.x -= movementY * 0.002;
      pitchObject.rotation.x = Math.max(-PI_2, Math.min(PI_2, pitchObject.rotation.x));
    }
  },

  onMouseDown: {
    value: function (event) {
      this.mouseDown = true;
    }
  },

  releaseMouse: {
    value: function () {
      this.mouseDown = false;
    }
  },

  onTouchStart: {
    value: function (e) {
      if (e.touches.length !== 1) { return; }
      this.touchStart = {
        x: e.touches[0].pageX,
        y: e.touches[0].pageY
      };
      this.touchStarted = true;
    }
  },

  onTouchMove: {
    value: function (e) {
      var deltaY;
      var yawObject = this.yawObject;
      if (!this.touchStarted) { return; }
      deltaY = 2 * Math.PI * (e.touches[0].pageX - this.touchStart.x) / this.canvasEl.clientWidth;
      // Limits touch orientaion to to yaw (y axis)
      yawObject.rotation.y -= deltaY * 0.5;
      this.touchStart = {
        x: e.touches[0].pageX,
        y: e.touches[0].pageY
      };
    }
  },

  onTouchEnd: {
    value: function () {
      this.touchStarted = false;
    }
  }

});
<|endoftext|>


<commit_before>import { maskInput, conformToMask } from 'vanilla-text-mask';
import _ from 'lodash';
import { createNumberMask } from 'text-mask-addons';
import Input from '../_classes/input/Input';
import { getNumberSeparators, getNumberDecimalLimit } from '../../utils/utils';

export default class NumberComponent extends Input {
  static schema(...extend) {
    return Input.schema({
      type: 'number',
      label: 'Number',
      key: 'number',
      validate: {
        min: '',
        max: '',
        step: 'any',
        integer: ''
      }
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Number',
      icon: 'hashtag',
      group: 'basic',
      documentation: 'http://help.form.io/userguide/#number',
      weight: 30,
      schema: NumberComponent.schema()
    };
  }

  constructor(...args) {
    super(...args);
    this.validators = this.validators.concat(['min', 'max']);

    const separators = getNumberSeparators(this.options.language);

    this.decimalSeparator = this.options.decimalSeparator = this.options.decimalSeparator
      || separators.decimalSeparator;

    if (this.component.delimiter) {
      if (this.options.hasOwnProperty('thousandsSeparator')) {
        console.warn("Property 'thousandsSeparator' is deprecated. Please use i18n to specify delimiter.");
      }

      this.delimiter = this.options.thousandsSeparator || separators.delimiter;
    }
    else {
      this.delimiter = '';
    }

    this.decimalLimit = getNumberDecimalLimit(this.component);

    // Currencies to override BrowserLanguage Config. Object key {}
    if (_.has(this.options, `languageOverride.${this.options.language}`)) {
      const override = _.get(this.options, `languageOverride.${this.options.language}`);
      this.decimalSeparator = override.decimalSeparator;
      this.delimiter = override.delimiter;
    }
    this.numberMask = this.createNumberMask();
  }

  /**
   * Creates the number mask for normal numbers.
   *
   * @return {*}
   */
  createNumberMask() {
    return createNumberMask({
      prefix: '',
      suffix: '',
      requireDecimal: _.get(this.component, 'requireDecimal', false),
      thousandsSeparatorSymbol: _.get(this.component, 'thousandsSeparator', this.delimiter),
      decimalSymbol: _.get(this.component, 'decimalSymbol', this.decimalSeparator),
      decimalLimit: _.get(this.component, 'decimalLimit', this.decimalLimit),
      allowNegative: _.get(this.component, 'allowNegative', true),
      allowDecimal: _.get(this.component, 'allowDecimal',
        !(this.component.validate && this.component.validate.integer))
    });
  }

  get defaultSchema() {
    return NumberComponent.schema();
  }

  get defaultValue() {
    let defaultValue = super.defaultValue;
    if (!defaultValue && this.component.defaultValue === 0) {
      defaultValue = this.component.defaultValue;
    }
    return defaultValue;
  }

  parseNumber(value) {
    // Remove delimiters and convert decimal separator to dot.
    value = value.split(this.delimiter).join('').replace(this.decimalSeparator, '.');

    if (this.component.validate && this.component.validate.integer) {
      return parseInt(value, 10);
    }
    else {
      return parseFloat(value);
    }
  }

  setInputMask(input) {
    let numberPattern = '[0-9';
    numberPattern += this.decimalSeparator ? `\\${this.decimalSeparator}` : '';
    numberPattern += this.delimiter ? `\\${this.delimiter}` : '';
    numberPattern += ']*';
    input.setAttribute('pattern', numberPattern);
    input.mask = maskInput({
      inputElement: input,
      mask: this.numberMask
    });
  }

  get inputInfo() {
    const info = super.inputInfo;
    if (this.component.mask) {
      info.attr.type = 'password';
    }
    else {
      info.attr.type = 'text';
    }
    info.attr.inputmode = 'numeric';
    info.changeEvent = 'input';
    return info;
  }

  getValueAt(index) {
    if (!this.refs.input.length || !this.refs.input[index]) {
      return null;
    }

    const val = this.refs.input[index].value;
    return val ? this.parseNumber(val) : null;
  }

  setValueAt(index, value, flags) {
    return super.setValueAt(index, this.formatValue(this.parseValue(value)), flags);
  }

  parseValue(input) {
    let value = parseFloat(input);

    if (!_.isNaN(value)) {
      value = String(value).replace('.', this.decimalSeparator);
    }
    else {
      value = null;
    }

    return value;
  }

  formatValue(value) {
    if (this.component.requireDecimal && value && !value.includes(this.decimalSeparator)) {
      return `${value}${this.decimalSeparator}${_.repeat('0', this.decimalLimit)}`;
    }
    else if (this.component.requireDecimal && value && value.includes(this.decimalSeparator)) {
      return `${value}${_.repeat('0', this.decimalLimit - value.split(this.decimalSeparator)[1].length)}`;
    }

    return value;
  }

  focus() {
    const input = this.refs.input[0];
    if (input) {
      input.focus();
      input.setSelectionRange(0, input.value.length);
    }
  }

  getMaskedValue(value) {
    return conformToMask(value === null ? '0' : value.toString(), this.numberMask).conformedValue;
  }

  getValueAsString(value) {
    if (!value && value !== 0) {
      return '';
    }
    value = this.getWidgetValueAsString(value);
    if (Array.isArray(value)) {
      return value.map(this.getMaskedValue).join(', ');
    }
    return this.getMaskedValue(value);
  }
}
<commit_msg>Fix #2191: invalid identity escape in numberPattern.<commit_after>import { maskInput, conformToMask } from 'vanilla-text-mask';
import _ from 'lodash';
import { createNumberMask } from 'text-mask-addons';
import Input from '../_classes/input/Input';
import { getNumberSeparators, getNumberDecimalLimit } from '../../utils/utils';

export default class NumberComponent extends Input {
  static schema(...extend) {
    return Input.schema({
      type: 'number',
      label: 'Number',
      key: 'number',
      validate: {
        min: '',
        max: '',
        step: 'any',
        integer: ''
      }
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Number',
      icon: 'hashtag',
      group: 'basic',
      documentation: 'http://help.form.io/userguide/#number',
      weight: 30,
      schema: NumberComponent.schema()
    };
  }

  constructor(...args) {
    super(...args);
    this.validators = this.validators.concat(['min', 'max']);

    const separators = getNumberSeparators(this.options.language);

    this.decimalSeparator = this.options.decimalSeparator = this.options.decimalSeparator
      || separators.decimalSeparator;

    if (this.component.delimiter) {
      if (this.options.hasOwnProperty('thousandsSeparator')) {
        console.warn("Property 'thousandsSeparator' is deprecated. Please use i18n to specify delimiter.");
      }

      this.delimiter = this.options.thousandsSeparator || separators.delimiter;
    }
    else {
      this.delimiter = '';
    }

    this.decimalLimit = getNumberDecimalLimit(this.component);

    // Currencies to override BrowserLanguage Config. Object key {}
    if (_.has(this.options, `languageOverride.${this.options.language}`)) {
      const override = _.get(this.options, `languageOverride.${this.options.language}`);
      this.decimalSeparator = override.decimalSeparator;
      this.delimiter = override.delimiter;
    }
    this.numberMask = this.createNumberMask();
  }

  /**
   * Creates the number mask for normal numbers.
   *
   * @return {*}
   */
  createNumberMask() {
    return createNumberMask({
      prefix: '',
      suffix: '',
      requireDecimal: _.get(this.component, 'requireDecimal', false),
      thousandsSeparatorSymbol: _.get(this.component, 'thousandsSeparator', this.delimiter),
      decimalSymbol: _.get(this.component, 'decimalSymbol', this.decimalSeparator),
      decimalLimit: _.get(this.component, 'decimalLimit', this.decimalLimit),
      allowNegative: _.get(this.component, 'allowNegative', true),
      allowDecimal: _.get(this.component, 'allowDecimal',
        !(this.component.validate && this.component.validate.integer))
    });
  }

  get defaultSchema() {
    return NumberComponent.schema();
  }

  get defaultValue() {
    let defaultValue = super.defaultValue;
    if (!defaultValue && this.component.defaultValue === 0) {
      defaultValue = this.component.defaultValue;
    }
    return defaultValue;
  }

  parseNumber(value) {
    // Remove delimiters and convert decimal separator to dot.
    value = value.split(this.delimiter).join('').replace(this.decimalSeparator, '.');

    if (this.component.validate && this.component.validate.integer) {
      return parseInt(value, 10);
    }
    else {
      return parseFloat(value);
    }
  }

  setInputMask(input) {
    let numberPattern = '[0-9';
    numberPattern += this.decimalSeparator || '';
    numberPattern += this.delimiter || '';
    numberPattern += ']*';
    input.setAttribute('pattern', numberPattern);
    input.mask = maskInput({
      inputElement: input,
      mask: this.numberMask
    });
  }

  get inputInfo() {
    const info = super.inputInfo;
    if (this.component.mask) {
      info.attr.type = 'password';
    }
    else {
      info.attr.type = 'text';
    }
    info.attr.inputmode = 'numeric';
    info.changeEvent = 'input';
    return info;
  }

  getValueAt(index) {
    if (!this.refs.input.length || !this.refs.input[index]) {
      return null;
    }

    const val = this.refs.input[index].value;
    return val ? this.parseNumber(val) : null;
  }

  setValueAt(index, value, flags) {
    return super.setValueAt(index, this.formatValue(this.parseValue(value)), flags);
  }

  parseValue(input) {
    let value = parseFloat(input);

    if (!_.isNaN(value)) {
      value = String(value).replace('.', this.decimalSeparator);
    }
    else {
      value = null;
    }

    return value;
  }

  formatValue(value) {
    if (this.component.requireDecimal && value && !value.includes(this.decimalSeparator)) {
      return `${value}${this.decimalSeparator}${_.repeat('0', this.decimalLimit)}`;
    }
    else if (this.component.requireDecimal && value && value.includes(this.decimalSeparator)) {
      return `${value}${_.repeat('0', this.decimalLimit - value.split(this.decimalSeparator)[1].length)}`;
    }

    return value;
  }

  focus() {
    const input = this.refs.input[0];
    if (input) {
      input.focus();
      input.setSelectionRange(0, input.value.length);
    }
  }

  getMaskedValue(value) {
    return conformToMask(value === null ? '0' : value.toString(), this.numberMask).conformedValue;
  }

  getValueAsString(value) {
    if (!value && value !== 0) {
      return '';
    }
    value = this.getWidgetValueAsString(value);
    if (Array.isArray(value)) {
      return value.map(this.getMaskedValue).join(', ');
    }
    return this.getMaskedValue(value);
  }
}
<|endoftext|>


<commit_before>/**
 * @ngdoc module
 * @name material.components.slider
 */
angular.module('material.components.slider', [
  'material.core',
  'material.animations',
  'material.services.aria'
])
.directive('mdSlider', [
  SliderDirective
]);

/**
 * @ngdoc directive
 * @name mdSlider
 * @module material.components.slider
 * @restrict E
 * @description
 * The `<md-slider>` component allows the user to choose from a range of
 * values.
 *
 * It has two modes: 'normal' mode, where the user slides between a wide range
 * of values, and 'discrete' mode, where the user slides between only a few
 * select values.
 *
 * To enable discrete mode, add the `discrete` attribute to a slider,
 * and use the `step` attribute to change the distance between
 * values the user is allowed to pick.
 *
 * @usage
 *
Normal Mode

 * <hljs lang="html">
 * <md-slider ng-model="myValue" min="5" max="500">
 * </md-slider>
 * </hljs>
 *
Discrete Mode

 * <hljs lang="html">
 * <md-slider discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
 * </md-slider>
 * </hljs>
 *
 * @param {boolean=} discrete Whether to enable discrete mode.
 * @param {number=} step The distance between values the user is allowed to pick. Default 1.
 * @param {number=} min The minimum value the user is allowed to pick. Default 0.
 * @param {number=} max The maximum value the user is allowed to pick. Default 100.
 */
function SliderDirective() {
  return {
    scope: {},
    require: ['?ngModel', 'mdSlider'],
    controller: [
      '$scope',
      '$element',
      '$attrs',
      '$$rAF',
      '$window',
      '$mdEffects',
      '$mdAria',
      '$mdUtil',
      '$mdConstant',
      SliderController
    ],
    template:
      '
' +
        '

' +
        '

' +
        '

' +
      '
' +
      '
' +
        '

' +
        '

' +
        '

' +
        '
' +
          '' +
        '
' +
        '

' +
      '
',
    link: postLink
  };

  function postLink(scope, element, attr, ctrls) {
    var ngModelCtrl = ctrls[0] || {
      // Mock ngModelController if it doesn't exist to give us
      // the minimum functionality needed
      $setViewValue: function(val) {
        this.$viewValue = val;
        this.$viewChangeListeners.forEach(function(cb) { cb(); });
      },
      $parsers: [],
      $formatters: [],
      $viewChangeListeners: []
    };

    var sliderCtrl = ctrls[1];
    sliderCtrl.init(ngModelCtrl);
  }
}

/**
 * We use a controller for all the logic so that we can expose a few
 * things to unit tests
 */
function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdAria, $mdUtil, $mdConstant) {

  this.init = function init(ngModelCtrl) {
    var thumb = angular.element(element[0].querySelector('.slider-thumb'));
    var thumbContainer = thumb.parent();
    var trackContainer = angular.element(element[0].querySelector('.slider-track-container'));
    var activeTrack = angular.element(element[0].querySelector('.slider-track-fill'));
    var tickContainer = angular.element(element[0].querySelector('.slider-track-ticks'));

    // Default values, overridable by attrs
    attr.min ? attr.$observe('min', updateMin) : updateMin(0);
    attr.max ? attr.$observe('max', updateMax) : updateMax(100);
    attr.step ? attr.$observe('step', updateStep) : updateStep(1);

    // We have to manually stop the $watch on ngDisabled because it exists
    // on the parent scope, and won't be automatically destroyed when
    // the component is destroyed.
    var stopDisabledWatch = angular.noop;
    if (attr.ngDisabled) {
      stopDisabledWatch = scope.$parent.$watch(attr.ngDisabled, updateAriaDisabled);
    } else {
      updateAriaDisabled(!!attr.disabled);
    }

    $mdAria.expect(element, 'aria-label');
    element.attr('tabIndex', 0);
    element.attr('role', 'slider');
    element.on('keydown', keydownListener);

    var hammertime = new Hammer(element[0], {
      recognizers: [
        [Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL }]
      ]
    });
    hammertime.on('hammer.input', onInput);
    hammertime.on('panstart', onPanStart);
    hammertime.on('pan', onPan);
    hammertime.on('panend', onPanEnd);

    // On resize, recalculate the slider's dimensions and re-render
    function updateAll() {
      refreshSliderDimensions();
      ngModelRender();
      redrawTicks();
    }
    setTimeout(updateAll);

    var debouncedUpdateAll = $$rAF.debounce(updateAll);
    angular.element($window).on('resize', debouncedUpdateAll);

    scope.$on('$destroy', function() {
      angular.element($window).off('resize', debouncedUpdateAll);
      hammertime.destroy();
      stopDisabledWatch();
    });

    ngModelCtrl.$render = ngModelRender;
    ngModelCtrl.$viewChangeListeners.push(ngModelRender);
    ngModelCtrl.$formatters.push(minMaxValidator);
    ngModelCtrl.$formatters.push(stepValidator);

    /**
     * Attributes
     */
    var min;
    var max;
    var step;
    function updateMin(value) {
      min = parseFloat(value);
      element.attr('aria-valuemin', value);
    }
    function updateMax(value) {
      max = parseFloat(value);
      element.attr('aria-valuemax', value);
    }
    function updateStep(value) {
      step = parseFloat(value);
      redrawTicks();
    }
    function updateAriaDisabled(isDisabled) {
      element.attr('aria-disabled', !!isDisabled);
    }

    // Draw the ticks with canvas.
    // The alternative to drawing ticks with canvas is to draw one element for each tick,
    // which could quickly become a performance bottleneck.
    var tickCanvas, tickCtx;
    function redrawTicks() {
      if (!angular.isDefined(attr.discrete)) return;

      var numSteps = Math.floor( (max - min) / step );
      if (!tickCanvas) {
        tickCanvas = angular.element('<canvas style="position:absolute;">');
        tickCtx = tickCanvas[0].getContext('2d');
        tickCtx.fillStyle = 'black';
        tickContainer.append(tickCanvas);
      }
      var dimensions = getSliderDimensions();
      tickCanvas[0].width = dimensions.width;
      tickCanvas[0].height = dimensions.height;

      var distance;
      for (var i = 0; i <= numSteps; i++) {
        distance = Math.floor(dimensions.width * (i / numSteps));
        tickCtx.fillRect(distance - 1, 0, 2, dimensions.height);
      }
    }

    /**
     * Refreshing Dimensions
     */
    var sliderDimensions = {};
    var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
    refreshSliderDimensions();
    function refreshSliderDimensions() {
      sliderDimensions = trackContainer[0].getBoundingClientRect();
    }
    function getSliderDimensions() {
      throttledRefreshDimensions();
      return sliderDimensions;
    }

    /**
     * left/right arrow listener
     */
    function keydownListener(ev) {
      var changeAmount;
      if (ev.which === $mdConstant.KEY_CODE.LEFT_ARROW) {
        changeAmount = -step;
      } else if (ev.which === $mdConstant.KEY_CODE.RIGHT_ARROW) {
        changeAmount = step;
      }
      if (changeAmount) {
        if (ev.metaKey || ev.ctrlKey || ev.altKey) {
          changeAmount *= 4;
        }
        ev.preventDefault();
        ev.stopPropagation();
        scope.$evalAsync(function() {
          setModelValue(ngModelCtrl.$viewValue + changeAmount);
        });
      }
    }

    /**
     * ngModel setters and validators
     */
    function setModelValue(value) {
      ngModelCtrl.$setViewValue( minMaxValidator(stepValidator(value)) );
    }
    function ngModelRender() {
      var percent = (ngModelCtrl.$viewValue - min) / (max - min);
      scope.modelValue = ngModelCtrl.$viewValue;
      element.attr('aria-valuenow', ngModelCtrl.$viewValue);
      setSliderPercent(percent);
    }

    function minMaxValidator(value) {
      if (angular.isNumber(value)) {
        return Math.max(min, Math.min(max, value));
      }
    }
    function stepValidator(value) {
      if (angular.isNumber(value)) {
        return Math.round(value / step) * step;
      }
    }

    /**
     * @param percent 0-1
     */
    function setSliderPercent(percent) {
      activeTrack.css('width', (percent * 100) + '%');
      thumbContainer.css(
        $mdEffects.TRANSFORM,
        'translate3d(' + getSliderDimensions().width * percent + 'px,0,0)'
      );
      element.toggleClass('slider-min', percent === 0);
    }

    /**
     * Slide listeners
     */
    var isSliding = false;
    var isDiscrete = angular.isDefined(attr.discrete);

    function onInput(ev) {
      if (!isSliding && ev.eventType === Hammer.INPUT_START &&
          !element[0].hasAttribute('disabled')) {

        isSliding = true;

        element.addClass('active');
        element[0].focus();
        refreshSliderDimensions();

        onPan(ev);

        ev.srcEvent.stopPropagation();

      } else if (isSliding && ev.eventType === Hammer.INPUT_END) {

        if ( isSliding && isDiscrete ) onPanEnd(ev);
        isSliding = false;

        element.removeClass('panning active');
      }
    }
    function onPanStart() {
      if (!isSliding) return;
      element.addClass('panning');
    }
    function onPan(ev) {
      if (!isSliding) return;

      // While panning discrete, update only the
      // visual positioning but not the model value.

      if ( isDiscrete ) adjustThumbPosition( ev.center.x );
      else              doSlide( ev.center.x );

      ev.preventDefault();
      ev.srcEvent.stopPropagation();
    }

    function onPanEnd(ev) {
      if ( isDiscrete ) {
        // Convert exact to closest discrete value.
        // Slide animate the thumb... and then update the model value.

        var exactVal = percentToValue( positionToPercent( ev.center.x ));
        var closestVal = minMaxValidator( stepValidator(exactVal) );

        setSliderPercent( valueToPercent(closestVal));
        $$rAF(function(){
          setModelValue( closestVal );
        });

        ev.preventDefault();
        ev.srcEvent.stopPropagation();
      }
    }

    /**
     * Expose for testing
     */
    this._onInput = onInput;
    this._onPanStart = onPanStart;
    this._onPan = onPan;

    /**
     * Slide the UI by changing the model value
     * @param x
     */
    function doSlide( x ) {
      scope.$evalAsync( function() {
        setModelValue( percentToValue( positionToPercent(x) ));
      });
    }

    /**
     * Slide the UI without changing the model (while dragging/panning)
     * @param x
     */
    function adjustThumbPosition( x ) {
      setSliderPercent( positionToPercent(x) );
    }

    /**
     * Convert horizontal position on slider to percentage value of offset from beginning...
     * @param x
     * @returns {number}
     */
    function positionToPercent( x ) {
      return Math.max(0, Math.min(1, (x - sliderDimensions.left) / (sliderDimensions.width)));
    }

    /**
     * Convert percentage offset on slide to equivalent model value
     * @param percent
     * @returns {*}
     */
    function percentToValue( percent ) {
      return (min + percent * (max - min));
    }

    function valueToPercent( val ) {
      return (val - min)/(max - min);
    }

  };
}
<commit_msg>fix(slider): disabled discrete sliders still usable<commit_after>/**
 * @ngdoc module
 * @name material.components.slider
 */
angular.module('material.components.slider', [
  'material.core',
  'material.animations',
  'material.services.aria'
])
.directive('mdSlider', [
  SliderDirective
]);

/**
 * @ngdoc directive
 * @name mdSlider
 * @module material.components.slider
 * @restrict E
 * @description
 * The `<md-slider>` component allows the user to choose from a range of
 * values.
 *
 * It has two modes: 'normal' mode, where the user slides between a wide range
 * of values, and 'discrete' mode, where the user slides between only a few
 * select values.
 *
 * To enable discrete mode, add the `discrete` attribute to a slider,
 * and use the `step` attribute to change the distance between
 * values the user is allowed to pick.
 *
 * @usage
 *
Normal Mode

 * <hljs lang="html">
 * <md-slider ng-model="myValue" min="5" max="500">
 * </md-slider>
 * </hljs>
 *
Discrete Mode

 * <hljs lang="html">
 * <md-slider discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
 * </md-slider>
 * </hljs>
 *
 * @param {boolean=} discrete Whether to enable discrete mode.
 * @param {number=} step The distance between values the user is allowed to pick. Default 1.
 * @param {number=} min The minimum value the user is allowed to pick. Default 0.
 * @param {number=} max The maximum value the user is allowed to pick. Default 100.
 */
function SliderDirective() {
  return {
    scope: {},
    require: ['?ngModel', 'mdSlider'],
    controller: [
      '$scope',
      '$element',
      '$attrs',
      '$$rAF',
      '$window',
      '$mdEffects',
      '$mdAria',
      '$mdUtil',
      '$mdConstant',
      SliderController
    ],
    template:
      '
' +
        '

' +
        '

' +
        '

' +
      '
' +
      '
' +
        '

' +
        '

' +
        '

' +
        '
' +
          '' +
        '
' +
        '

' +
      '
',
    link: postLink
  };

  function postLink(scope, element, attr, ctrls) {
    var ngModelCtrl = ctrls[0] || {
      // Mock ngModelController if it doesn't exist to give us
      // the minimum functionality needed
      $setViewValue: function(val) {
        this.$viewValue = val;
        this.$viewChangeListeners.forEach(function(cb) { cb(); });
      },
      $parsers: [],
      $formatters: [],
      $viewChangeListeners: []
    };

    var sliderCtrl = ctrls[1];
    sliderCtrl.init(ngModelCtrl);
  }
}

/**
 * We use a controller for all the logic so that we can expose a few
 * things to unit tests
 */
function SliderController(scope, element, attr, $$rAF, $window, $mdEffects, $mdAria, $mdUtil, $mdConstant) {

  this.init = function init(ngModelCtrl) {
    var thumb = angular.element(element[0].querySelector('.slider-thumb'));
    var thumbContainer = thumb.parent();
    var trackContainer = angular.element(element[0].querySelector('.slider-track-container'));
    var activeTrack = angular.element(element[0].querySelector('.slider-track-fill'));
    var tickContainer = angular.element(element[0].querySelector('.slider-track-ticks'));

    // Default values, overridable by attrs
    attr.min ? attr.$observe('min', updateMin) : updateMin(0);
    attr.max ? attr.$observe('max', updateMax) : updateMax(100);
    attr.step ? attr.$observe('step', updateStep) : updateStep(1);

    // We have to manually stop the $watch on ngDisabled because it exists
    // on the parent scope, and won't be automatically destroyed when
    // the component is destroyed.
    var stopDisabledWatch = angular.noop;
    if (attr.ngDisabled) {
      stopDisabledWatch = scope.$parent.$watch(attr.ngDisabled, updateAriaDisabled);
    } else {
      updateAriaDisabled(!!attr.disabled);
    }

    $mdAria.expect(element, 'aria-label');
    element.attr('tabIndex', 0);
    element.attr('role', 'slider');
    element.on('keydown', keydownListener);

    var hammertime = new Hammer(element[0], {
      recognizers: [
        [Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL }]
      ]
    });
    hammertime.on('hammer.input', onInput);
    hammertime.on('panstart', onPanStart);
    hammertime.on('pan', onPan);
    hammertime.on('panend', onPanEnd);

    // On resize, recalculate the slider's dimensions and re-render
    function updateAll() {
      refreshSliderDimensions();
      ngModelRender();
      redrawTicks();
    }
    setTimeout(updateAll);

    var debouncedUpdateAll = $$rAF.debounce(updateAll);
    angular.element($window).on('resize', debouncedUpdateAll);

    scope.$on('$destroy', function() {
      angular.element($window).off('resize', debouncedUpdateAll);
      hammertime.destroy();
      stopDisabledWatch();
    });

    ngModelCtrl.$render = ngModelRender;
    ngModelCtrl.$viewChangeListeners.push(ngModelRender);
    ngModelCtrl.$formatters.push(minMaxValidator);
    ngModelCtrl.$formatters.push(stepValidator);

    /**
     * Attributes
     */
    var min;
    var max;
    var step;
    function updateMin(value) {
      min = parseFloat(value);
      element.attr('aria-valuemin', value);
    }
    function updateMax(value) {
      max = parseFloat(value);
      element.attr('aria-valuemax', value);
    }
    function updateStep(value) {
      step = parseFloat(value);
      redrawTicks();
    }
    function updateAriaDisabled(isDisabled) {
      element.attr('aria-disabled', !!isDisabled);
    }

    // Draw the ticks with canvas.
    // The alternative to drawing ticks with canvas is to draw one element for each tick,
    // which could quickly become a performance bottleneck.
    var tickCanvas, tickCtx;
    function redrawTicks() {
      if (!angular.isDefined(attr.discrete)) return;

      var numSteps = Math.floor( (max - min) / step );
      if (!tickCanvas) {
        tickCanvas = angular.element('<canvas style="position:absolute;">');
        tickCtx = tickCanvas[0].getContext('2d');
        tickCtx.fillStyle = 'black';
        tickContainer.append(tickCanvas);
      }
      var dimensions = getSliderDimensions();
      tickCanvas[0].width = dimensions.width;
      tickCanvas[0].height = dimensions.height;

      var distance;
      for (var i = 0; i <= numSteps; i++) {
        distance = Math.floor(dimensions.width * (i / numSteps));
        tickCtx.fillRect(distance - 1, 0, 2, dimensions.height);
      }
    }

    /**
     * Refreshing Dimensions
     */
    var sliderDimensions = {};
    var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
    refreshSliderDimensions();
    function refreshSliderDimensions() {
      sliderDimensions = trackContainer[0].getBoundingClientRect();
    }
    function getSliderDimensions() {
      throttledRefreshDimensions();
      return sliderDimensions;
    }

    /**
     * left/right arrow listener
     */
    function keydownListener(ev) {
      var changeAmount;
      if (ev.which === $mdConstant.KEY_CODE.LEFT_ARROW) {
        changeAmount = -step;
      } else if (ev.which === $mdConstant.KEY_CODE.RIGHT_ARROW) {
        changeAmount = step;
      }
      if (changeAmount) {
        if (ev.metaKey || ev.ctrlKey || ev.altKey) {
          changeAmount *= 4;
        }
        ev.preventDefault();
        ev.stopPropagation();
        scope.$evalAsync(function() {
          setModelValue(ngModelCtrl.$viewValue + changeAmount);
        });
      }
    }

    /**
     * ngModel setters and validators
     */
    function setModelValue(value) {
      ngModelCtrl.$setViewValue( minMaxValidator(stepValidator(value)) );
    }
    function ngModelRender() {
      var percent = (ngModelCtrl.$viewValue - min) / (max - min);
      scope.modelValue = ngModelCtrl.$viewValue;
      element.attr('aria-valuenow', ngModelCtrl.$viewValue);
      setSliderPercent(percent);
    }

    function minMaxValidator(value) {
      if (angular.isNumber(value)) {
        return Math.max(min, Math.min(max, value));
      }
    }
    function stepValidator(value) {
      if (angular.isNumber(value)) {
        return Math.round(value / step) * step;
      }
    }

    /**
     * @param percent 0-1
     */
    function setSliderPercent(percent) {
      activeTrack.css('width', (percent * 100) + '%');
      thumbContainer.css(
        $mdEffects.TRANSFORM,
        'translate3d(' + getSliderDimensions().width * percent + 'px,0,0)'
      );
      element.toggleClass('slider-min', percent === 0);
    }

    /**
     * Slide listeners
     */
    var isSliding = false;
    var isDiscrete = angular.isDefined(attr.discrete);

    function onInput(ev) {
      if (!isSliding && ev.eventType === Hammer.INPUT_START &&
          !element[0].hasAttribute('disabled')) {

        isSliding = true;

        element.addClass('active');
        element[0].focus();
        refreshSliderDimensions();

        onPan(ev);

        ev.srcEvent.stopPropagation();

      } else if (isSliding && ev.eventType === Hammer.INPUT_END) {

        if ( isSliding && isDiscrete ) onPanEnd(ev);
        isSliding = false;

        element.removeClass('panning active');
      }
    }
    function onPanStart() {
      if (!isSliding) return;
      element.addClass('panning');
    }
    function onPan(ev) {
      if (!isSliding) return;

      // While panning discrete, update only the
      // visual positioning but not the model value.

      if ( isDiscrete ) adjustThumbPosition( ev.center.x );
      else              doSlide( ev.center.x );

      ev.preventDefault();
      ev.srcEvent.stopPropagation();
    }

    function onPanEnd(ev) {
      if ( isDiscrete && !element[0].hasAttribute('disabled') ) {
        // Convert exact to closest discrete value.
        // Slide animate the thumb... and then update the model value.

        var exactVal = percentToValue( positionToPercent( ev.center.x ));
        var closestVal = minMaxValidator( stepValidator(exactVal) );

        setSliderPercent( valueToPercent(closestVal));
        $$rAF(function(){
          setModelValue( closestVal );
        });

        ev.preventDefault();
        ev.srcEvent.stopPropagation();
      }
    }

    /**
     * Expose for testing
     */
    this._onInput = onInput;
    this._onPanStart = onPanStart;
    this._onPan = onPan;

    /**
     * Slide the UI by changing the model value
     * @param x
     */
    function doSlide( x ) {
      scope.$evalAsync( function() {
        setModelValue( percentToValue( positionToPercent(x) ));
      });
    }

    /**
     * Slide the UI without changing the model (while dragging/panning)
     * @param x
     */
    function adjustThumbPosition( x ) {
      setSliderPercent( positionToPercent(x) );
    }

    /**
     * Convert horizontal position on slider to percentage value of offset from beginning...
     * @param x
     * @returns {number}
     */
    function positionToPercent( x ) {
      return Math.max(0, Math.min(1, (x - sliderDimensions.left) / (sliderDimensions.width)));
    }

    /**
     * Convert percentage offset on slide to equivalent model value
     * @param percent
     * @returns {*}
     */
    function percentToValue( percent ) {
      return (min + percent * (max - min));
    }

    function valueToPercent( val ) {
      return (val - min)/(max - min);
    }

  };
}
<|endoftext|>


<commit_before>import _ from 'lodash';
import BaseComponent from '../base/Base';

export default class SurveyComponent extends BaseComponent {
  static schema(...extend) {
    return BaseComponent.schema({
      type: 'survey',
      label: 'Survey',
      key: 'survey',
      questions: [],
      values: []
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Survey',
      group: 'advanced',
      icon: 'fa fa-list',
      weight: 170,
      documentation: 'http://help.form.io/userguide/#survey',
      schema: SurveyComponent.schema()
    };
  }

  get defaultSchema() {
    return SurveyComponent.schema();
  }

  build() {
    if (this.viewOnly) {
      this.viewOnlyBuild();
    }
    else {
      this.createElement();
      const labelAtTheBottom = this.component.labelPosition === 'bottom';
      if (!labelAtTheBottom) {
        this.createLabel(this.element);
      }
      this.table = this.ce('table', {
        class: 'table table-striped table-bordered'
      });
      this.setInputStyles(this.table);

      // Build header.
      const thead = this.ce('thead');
      const thr = this.ce('tr');
      thr.appendChild(this.ce('td'));
      _.each(this.component.values, (value) => {
        const th = this.ce('th', {
          style: 'text-align: center;'
        });
        th.appendChild(this.text(value.label));
        thr.appendChild(th);
      });
      thead.appendChild(thr);
      this.table.appendChild(thead);
      // Build the body.
      const tbody = this.ce('tbody');
      _.each(this.component.questions, (question) => {
        const tr = this.ce('tr');
        const td = this.ce('td');
        td.appendChild(this.text(question.label));
        tr.appendChild(td);
        _.each(this.component.values, (value) => {
          const td = this.ce('td', {
            style: 'text-align: center;'
          });
          const input = this.ce('input', {
            type: 'radio',
            name: `data[${this.key}][${question.value}]`,
            value: value.value,
            id: `${this.id}-${question.value}-${value.value}`
          });
          this.addInput(input, td);
          tr.appendChild(td);
        });
        tbody.appendChild(tr);
      });
      this.table.appendChild(tbody);
      this.element.appendChild(this.table);
      if (labelAtTheBottom) {
        this.createLabel(this.element);
      }
      this.createDescription(this.element);
      this.restoreValue();
      if (this.shouldDisable) {
        this.disabled = true;
      }
      this.autofocus();
    }
  }

  setValue(value, flags) {
    flags = this.getFlags.apply(this, arguments);
    if (!value) {
      return;
    }
    const key = `data[${this.key}]`;
    _.each(this.component.questions, (question) => {
      _.each(this.inputs, (input) => {
        if (input.name === (`${key}[${question.value}]`)) {
          input.checked = (input.value === value[question.value]);
        }
      });
    });
    this.updateValue(flags);
  }

  get emptyValue() {
    return {};
  }

  getValue() {
    if (this.viewOnly) {
      return this.dataValue;
    }
    const value = {};
    const key = `data[${this.key}]`;
    _.each(this.component.questions, (question) => {
      _.each(this.inputs, (input) => {
        if (input.checked && (input.name === (`${key}[${question.value}]`))) {
          value[question.value] = input.value;
          return false;
        }
      });
    });
    return value;
  }

  getView(value) {
    if (!value) {
      return '';
    }
    const table = this.ce('table', {
      class: 'table table-striped table-bordered table-condensed'
    });
    const tbody = this.ce('tbody');

    _.each(value, (value, question) => {
      const row = this.ce('tr');

      const questionCell = this.ce('th');
      const valueCell = this.ce('td');

      const questionText = _.find(this.component.questions, ['value', question]).label;
      const valueText = _.find(this.component.values, ['value', value]).label;

      questionCell.appendChild(this.text(questionText));
      valueCell.appendChild(this.text(valueText));

      row.appendChild(questionCell);
      row.appendChild(valueCell);

      tbody.appendChild(row);
    });

    table.appendChild(tbody);
    return table.outerHTML;
  }
}
<commit_msg>FOR-1311: Implemented required validation for Survey component.<commit_after>import _ from 'lodash';
import BaseComponent from '../base/Base';

export default class SurveyComponent extends BaseComponent {
  static schema(...extend) {
    return BaseComponent.schema({
      type: 'survey',
      label: 'Survey',
      key: 'survey',
      questions: [],
      values: []
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Survey',
      group: 'advanced',
      icon: 'fa fa-list',
      weight: 170,
      documentation: 'http://help.form.io/userguide/#survey',
      schema: SurveyComponent.schema()
    };
  }

  get defaultSchema() {
    return SurveyComponent.schema();
  }

  build() {
    if (this.viewOnly) {
      this.viewOnlyBuild();
    }
    else {
      this.createElement();
      const labelAtTheBottom = this.component.labelPosition === 'bottom';
      if (!labelAtTheBottom) {
        this.createLabel(this.element);
      }
      this.table = this.ce('table', {
        class: 'table table-striped table-bordered'
      });
      this.setInputStyles(this.table);

      // Build header.
      const thead = this.ce('thead');
      const thr = this.ce('tr');
      thr.appendChild(this.ce('td'));
      _.each(this.component.values, (value) => {
        const th = this.ce('th', {
          style: 'text-align: center;'
        });
        th.appendChild(this.text(value.label));
        thr.appendChild(th);
      });
      thead.appendChild(thr);
      this.table.appendChild(thead);
      // Build the body.
      const tbody = this.ce('tbody');
      _.each(this.component.questions, (question) => {
        const tr = this.ce('tr');
        const td = this.ce('td');
        td.appendChild(this.text(question.label));
        tr.appendChild(td);
        _.each(this.component.values, (value) => {
          const td = this.ce('td', {
            style: 'text-align: center;'
          });
          const input = this.ce('input', {
            type: 'radio',
            name: `data[${this.key}][${question.value}]`,
            value: value.value,
            id: `${this.id}-${question.value}-${value.value}`
          });
          this.addInput(input, td);
          tr.appendChild(td);
        });
        tbody.appendChild(tr);
      });
      this.table.appendChild(tbody);
      this.element.appendChild(this.table);
      if (labelAtTheBottom) {
        this.createLabel(this.element);
      }
      this.createDescription(this.element);
      this.restoreValue();
      if (this.shouldDisable) {
        this.disabled = true;
      }
      this.autofocus();
    }
  }

  setValue(value, flags) {
    flags = this.getFlags.apply(this, arguments);
    if (!value) {
      return;
    }
    const key = `data[${this.key}]`;
    _.each(this.component.questions, (question) => {
      _.each(this.inputs, (input) => {
        if (input.name === (`${key}[${question.value}]`)) {
          input.checked = (input.value === value[question.value]);
        }
      });
    });
    this.updateValue(flags);
  }

  get emptyValue() {
    return {};
  }

  getValue() {
    if (this.viewOnly) {
      return this.dataValue;
    }
    const value = {};
    const key = `data[${this.key}]`;
    _.each(this.component.questions, (question) => {
      _.each(this.inputs, (input) => {
        if (input.checked && (input.name === (`${key}[${question.value}]`))) {
          value[question.value] = input.value;
          return false;
        }
      });
    });
    return value;
  }

  validateRequired(setting, value) {
    return this.component.questions.reduce((result, question) =>
      result && Boolean(value[question.value]), true);
  }

  getView(value) {
    if (!value) {
      return '';
    }
    const table = this.ce('table', {
      class: 'table table-striped table-bordered table-condensed'
    });
    const tbody = this.ce('tbody');

    _.each(value, (value, question) => {
      const row = this.ce('tr');

      const questionCell = this.ce('th');
      const valueCell = this.ce('td');

      const questionText = _.find(this.component.questions, ['value', question]).label;
      const valueText = _.find(this.component.values, ['value', value]).label;

      questionCell.appendChild(this.text(questionText));
      valueCell.appendChild(this.text(valueText));

      row.appendChild(questionCell);
      row.appendChild(valueCell);

      tbody.appendChild(row);
    });

    table.appendChild(tbody);
    return table.outerHTML;
  }
}
<|endoftext|>


<commit_before>import React from "react";
import Radium from "radium";
// import _ from "lodash";
// import Flex from './framework/flex';
// import { connect } from "react-redux";
// import { FOO } from "../actions";
// import { visualization } from "../../visualization/visualization";
import d3 from "d3";
import { processNodes, calcLayouts } from "../../util/processNodes";
import * as globals from "../../util/globals";
import Tree from "./tree";
import Grid from "./grid";
import Card from "../framework/card";
import Legend from "../controls/legend";
import ZoomOutIcon from "../framework/zoom-out-icon";
import ZoomInIcon from "../framework/zoom-in-icon";
import MoveIcon from "../framework/move-icon";

import {Viewer, ViewerHelper} from 'react-svg-pan-zoom';

/*
 * TreeView creates and SVG and scales according to layout
 * such that branches and tips are correctly placed.
 * will handle zooming
*/
@Radium
class TreeView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      okToDraw: false,
      value: ViewerHelper.getDefaultValue(),
      tool: "none",  //one of `none`, `pan`, `zoom`, `zoom-in`, `zoom-out`
    };
  }
  static propTypes = {
    /* react */
    // dispatch: React.PropTypes.func,
    params: React.PropTypes.object,
    routes: React.PropTypes.array,
    /* component api */
    style: React.PropTypes.object,
    tree: React.PropTypes.object
  }

  componentWillMount() {
    if (this.state.currentDatasetGuid !== this.props.datasetGuid) {
      const scales = this.updateScales(nodes);
      this.setState({
        okToDraw: true,
        currentDatasetGuid: this.props.datasetGuid,
        nodes: nodes,
        width: globals.width,
        xScale: scales.xScale,
        yScale: scales.yScale
      });
    }
  }

  componentWillReceiveProps(nextProps) {
    // Do we have a tree to draw? if yes, check whether it needs to be redrawn
    if (!(nextProps.datasetGuid && nextProps.nodes)){
      this.setState({okToDraw: false});
    } else if ((nextProps.datasetGuid !== this.props.datasetGuid)
               || (nextProps.layout !== this.props.layout)
               || (nextProps.distanceMeasure !== this.props.distanceMeasure)) {
      const scales = this.updateScales(nextProps.nodes, nextProps.layout, nextProps.distanceMeasure);
      console.log("setting ok to draw");
      this.setState({
        okToDraw: true,
        currentDatasetGuid: nextProps.datasetGuid,
        width: globals.width,
        xScale: scales.xScale,
        yScale: scales.yScale
      });
    }
  }

  updateScales(nodes, layout_in, distanceMeasure_in) {
    const layout = (layout_in) ? layout_in : "rectangular";
    const distanceMeasure = (distanceMeasure_in) ? distanceMeasure_in : "div";

    const xValues = nodes.map((node) => {
      return +node.geometry[distanceMeasure][layout].xVal;
    });

    const yValues = nodes.map((node) => {
      return +node.geometry[distanceMeasure][layout].yVal;
    });

    const height = this.treePlotHeight(globals.width);
    const minDim = ((globals.width<height) ? globals.width : height) - 2 * globals.margin;
    const xScale = d3.scale.linear().range([globals.margin, globals.width - globals.margin]);
    const yScale = d3.scale.linear().range([globals.margin, height - globals.margin ]);

    if (layout === "radial") {
      xScale.domain([-d3.max(xValues), d3.max(xValues)]);
      yScale.domain([-d3.max(xValues), d3.max(xValues)]);
      xScale.range([globals.width - minDim - globals.margin, globals.width - globals.margin]);
      yScale.range([height - minDim - globals.margin, height - globals.margin]);
    } else {
      xScale.domain([d3.min(xValues), d3.max(xValues)]);
      yScale.domain([d3.min(yValues), d3.max(yValues)]);
    }

    return {
      xScale,
      yScale
    };

  }

  treePlotHeight(width) {
    return 400 + 0.30 * width;
  }

  createTree() {
    /* two svgs! one scales, one doesn't :) */
    // <svg
    //   style={{backgroundColor: "#FFFFFF"}}
    //   width={this.state.width}
    //   height={this.treePlotHeight(this.state.width)}
    //
    //   >

    //
    return (
      <Card title="Phylogeny">

 {this.state.scaleFactor}

          <svg width={300} height={300} style={{position: "absolute", left: 13, top: 50, pointerEvents: "none"}}>
            <Legend colorScale={this.props.colorScale}/>
          </svg>
          <Viewer
            width={this.state.width}
            height={this.treePlotHeight(this.state.width)}
            value={this.state.value}
            tool={this.state.tool}
            detectPinch={false}
            detectAutoPan={false}
            background="#FFF"
            onChange={this.handleChange.bind(this)}
            onClick={this.handleClick.bind(this)}>
            <svg style={{pointerEvents: "auto"}} width={this.state.width} height={this.treePlotHeight(this.state.width)} id="treeplot">
              <Grid
                layout={this.props.layout}
                distanceMeasure={this.props.distanceMeasure}
                xScale={this.state.xScale}
                yScale={this.state.yScale}
                nodes={this.props.nodes}
              />
              <Tree
                nodes={this.props.nodes}
                nodeColor={this.props.nodeColor}
                nodeColorAttr={this.props.nodeColorAttr}
                tipRadii={this.props.tipRadii}
                tipVisibility={this.props.tipVisibility}
                layout={this.props.layout}
                distanceMeasure={this.props.distanceMeasure}
                xScale={this.state.xScale}
                yScale={this.state.yScale}
              />
            </svg>
          </Viewer>
          <svg width={50} height={130} style={{position: "absolute", right: 20, bottom: 20}}>
              <defs>
                <filter id="dropshadow" height="130%">
                  <feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
                  <feOffset dx="2" dy="2" result="offsetblur"/>
                  <feComponentTransfer>
                    <feFuncA type="linear" slope="0.2"/>
                  </feComponentTransfer>
                  <feMerge>
                    <feMergeNode/>
                    <feMergeNode in="SourceGraphic"/>
                  </feMerge>
                </filter>
              </defs>
            <MoveIcon
              handleClick={this.handleIconClick("pan")}
              active={this.state.tool === "pan"}
              x={10}
              y={10}
              />
            <ZoomInIcon
              handleClick={this.handleIconClick("zoom-in")}
              active={this.state.tool === "zoom-in"}
              x={10}
              y={50}
              />
            <ZoomOutIcon
              handleClick={this.handleIconClick("zoom-out")}
              active={this.state.tool === "zoom-out"}
              x={10}
              y={90}
              />
          </svg>
      </Card>
    );
  }

  handleIconClick(tool) {
    return () => {
      if (this.state.tool !== tool) {
        this.setState({ tool });
      } else {
        this.setState({ tool: "none" });
      }
    };
  }

  // handleZoomEvent(direction) {
  //   return () => {
  //     this.state.value.matrix
  //
  //     console.log(direction)
  //   }
  // }

  handleChange(event) {
    // console.log(event.scaleFactor)
    // console.log('scaleFactor', event.scaleFactor);
    // console.log(this.state, event.value, event)
    this.setState({value: event.value, scaleFactor: event.scaleFactor});
  }

  handleClick(event){
    // console.log('click', event.x, event.y, event.originalEvent);
  }

  render() {
    /*
      1. if we just loaded a new dataset, run setup tree,
      2. otherwise if we just rescaled, run updatescales,
      3. otherwise just have components rerender because for instance colorby changed
    */
    return (

        {this.state.okToDraw ? this.createTree() : "We don't have tree data yet [spinner]"}

    );
  }
}

export default TreeView;
<commit_msg>pan by default, click icons to zoom in and out<commit_after>import React from "react";
import Radium from "radium";
// import _ from "lodash";
// import Flex from './framework/flex';
// import { connect } from "react-redux";
// import { FOO } from "../actions";
// import { visualization } from "../../visualization/visualization";
import d3 from "d3";
import { processNodes, calcLayouts } from "../../util/processNodes";
import * as globals from "../../util/globals";
import Tree from "./tree";
import Grid from "./grid";
import Card from "../framework/card";
import Legend from "../controls/legend";
import ZoomOutIcon from "../framework/zoom-out-icon";
import ZoomInIcon from "../framework/zoom-in-icon";
import MoveIcon from "../framework/move-icon";

import {Viewer, ViewerHelper} from 'react-svg-pan-zoom';

/*
 * TreeView creates and SVG and scales according to layout
 * such that branches and tips are correctly placed.
 * will handle zooming
*/
@Radium
class TreeView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      okToDraw: false,
      value: ViewerHelper.getDefaultValue(),
      zoom: 1,
      tool: "pan",  //one of `none`, `pan`, `zoom`, `zoom-in`, `zoom-out`
    };
  }
  static propTypes = {
    /* react */
    // dispatch: React.PropTypes.func,
    params: React.PropTypes.object,
    routes: React.PropTypes.array,
    /* component api */
    style: React.PropTypes.object,
    tree: React.PropTypes.object
  }

  componentWillMount() {
    if (this.state.currentDatasetGuid !== this.props.datasetGuid) {
      const scales = this.updateScales(nodes);
      this.setState({
        okToDraw: true,
        currentDatasetGuid: this.props.datasetGuid,
        nodes: nodes,
        width: globals.width,
        xScale: scales.xScale,
        yScale: scales.yScale
      });
    }
  }

  componentWillReceiveProps(nextProps) {
    // Do we have a tree to draw? if yes, check whether it needs to be redrawn
    if (!(nextProps.datasetGuid && nextProps.nodes)){
      this.setState({okToDraw: false});
    } else if ((nextProps.datasetGuid !== this.props.datasetGuid)
               || (nextProps.layout !== this.props.layout)
               || (nextProps.distanceMeasure !== this.props.distanceMeasure)) {
      const scales = this.updateScales(nextProps.nodes, nextProps.layout, nextProps.distanceMeasure);
      console.log("setting ok to draw");
      this.setState({
        okToDraw: true,
        currentDatasetGuid: nextProps.datasetGuid,
        width: globals.width,
        xScale: scales.xScale,
        yScale: scales.yScale
      });
    }
  }

  updateScales(nodes, layout_in, distanceMeasure_in) {
    const layout = (layout_in) ? layout_in : "rectangular";
    const distanceMeasure = (distanceMeasure_in) ? distanceMeasure_in : "div";

    const xValues = nodes.map((node) => {
      return +node.geometry[distanceMeasure][layout].xVal;
    });

    const yValues = nodes.map((node) => {
      return +node.geometry[distanceMeasure][layout].yVal;
    });

    const height = this.treePlotHeight(globals.width);
    const minDim = ((globals.width<height) ? globals.width : height) - 2 * globals.margin;
    const xScale = d3.scale.linear().range([globals.margin, globals.width - globals.margin]);
    const yScale = d3.scale.linear().range([globals.margin, height - globals.margin ]);

    if (layout === "radial") {
      xScale.domain([-d3.max(xValues), d3.max(xValues)]);
      yScale.domain([-d3.max(xValues), d3.max(xValues)]);
      xScale.range([globals.width - minDim - globals.margin, globals.width - globals.margin]);
      yScale.range([height - minDim - globals.margin, height - globals.margin]);
    } else {
      xScale.domain([d3.min(xValues), d3.max(xValues)]);
      yScale.domain([d3.min(yValues), d3.max(yValues)]);
    }

    return {
      xScale,
      yScale
    };

  }

  treePlotHeight(width) {
    return 400 + 0.30 * width;
  }

  createTree() {
    /* two svgs! one scales, one doesn't :) */
    // <svg
    //   style={{backgroundColor: "#FFFFFF"}}
    //   width={this.state.width}
    //   height={this.treePlotHeight(this.state.width)}
    //
    //   >

    //
    return (
      <Card title="Phylogeny">

 {this.state.scaleFactor}

          <svg width={300} height={300} style={{position: "absolute", left: 13, top: 50, pointerEvents: "none"}}>
            <Legend colorScale={this.props.colorScale}/>
          </svg>
          <Viewer
            width={this.state.width}
            height={this.treePlotHeight(this.state.width)}
            value={this.state.value}
            tool={this.state.tool}
            detectPinch={false}
            detectAutoPan={false}
            background="#FFF"
            onChange={this.handleChange.bind(this)}
            onClick={this.handleClick.bind(this)}>
            <svg style={{pointerEvents: "auto"}} width={this.state.width} height={this.treePlotHeight(this.state.width)} id="treeplot">
              <Grid
                layout={this.props.layout}
                distanceMeasure={this.props.distanceMeasure}
                xScale={this.state.xScale}
                yScale={this.state.yScale}
                nodes={this.props.nodes}
              />
              <Tree
                nodes={this.props.nodes}
                nodeColor={this.props.nodeColor}
                nodeColorAttr={this.props.nodeColorAttr}
                tipRadii={this.props.tipRadii}
                tipVisibility={this.props.tipVisibility}
                layout={this.props.layout}
                distanceMeasure={this.props.distanceMeasure}
                xScale={this.state.xScale}
                yScale={this.state.yScale}
              />
            </svg>
          </Viewer>
          <svg width={50} height={130} style={{position: "absolute", right: 20, bottom: 20}}>
              <defs>
                <filter id="dropshadow" height="130%">
                  <feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
                  <feOffset dx="2" dy="2" result="offsetblur"/>
                  <feComponentTransfer>
                    <feFuncA type="linear" slope="0.2"/>
                  </feComponentTransfer>
                  <feMerge>
                    <feMergeNode/>
                    <feMergeNode in="SourceGraphic"/>
                  </feMerge>
                </filter>
              </defs>
            <ZoomInIcon
              handleClick={this.handleIconClick("zoom-in")}
              active={true}
              x={10}
              y={50}
              />
            <ZoomOutIcon
              handleClick={this.handleIconClick("zoom-out")}
              active={true}
              x={10}
              y={90}
              />
          </svg>
      </Card>
    );
    // <MoveIcon
    //   handleClick={this.handleIconClick("pan")}
    //   active={this.state.tool === "pan"}
    //   x={10}
    //   y={10}
    //   />
  }

  handleIconClick(tool) {
    return () => {

      console.log(tool)

      let zoom;
      if (tool === "zoom-in") {
        zoom = this.state.zoom + .1;
        console.log('zooming in', this.state.zoom, zoom)
      } else {
        zoom = this.state.zoom - .1;
        console.log('zooming out', this.state.zoom, zoom)
      }
      let viewerX = this.state.width / 2;
      let viewerY = this.treePlotHeight(this.state.width) / 2;
      let nextValue = ViewerHelper.zoom(this.state.value, zoom, viewerX, viewerY);

      this.setState({value: nextValue})
    };
  }

  // handleZoomEvent(direction) {
  //   return () => {
  //     this.state.value.matrix
  //
  //     console.log(direction)
  //   }
  // }

  handleChange(event) {
    // console.log(event.scaleFactor)
    // console.log('scaleFactor', event.scaleFactor);
    // console.log(this.state, event.value, event)
    this.setState({value: event.value});
  }

  handleClick(event){
    // console.log('click', event.x, event.y, event.originalEvent);
  }

  render() {
    /*
      1. if we just loaded a new dataset, run setup tree,
      2. otherwise if we just rescaled, run updatescales,
      3. otherwise just have components rerender because for instance colorby changed
    */
    return (

        {this.state.okToDraw ? this.createTree() : "We don't have tree data yet [spinner]"}

    );
  }
}

export default TreeView;
<|endoftext|>