<commit_before>import React from 'react';
import ViewerHelper from './viewer-helper';
import ViewerEvent from './viewer-event';
import cursor from './cursor';
import {calculateBox, mapRange} from './utils';
import {
  TOOL_NONE, TOOL_PAN, TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT,
  MODE_IDLE, MODE_PANNING, MODE_ZOOMING,
  DIRECTION_NONE, DIRECTION_UP, DIRECTION_RIGHT, DIRECTION_DOWN, DIRECTION_LEFT
} from './constants';

export default class Viewer extends React.Component {

  constructor(props) {
    super(props);
    this.handleSpecialKeyChange = this.handleSpecialKeyChange.bind(this);
    this.handleAutoPan = this.handleAutoPan.bind(this);
  }

  shouldComponentUpdate(nextProps) {
    return nextProps !== this.props;
  }

  handleStartPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_IDLE) return;

    let nextValue = ViewerHelper.startPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleUpdatePan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    //the mouse exited and reentered into svg
    let forceExit = (value.mode === MODE_PANNING && event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopPan(value) :
      ViewerHelper.updatePan(value, x, y, 20, SVGWidth, SVGHeight, width, height);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStopPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    let nextValue = ViewerHelper.stopPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStartZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN].indexOf(tool) === -1) return;
    if (value.mode !== MODE_IDLE) return;

    //let point = ViewerHelper.getSVGPoint(value, x, y);
    //if(!ViewerHelper.isPointInsideSVG(point.x, point.y, SVGWidth, SVGHeight)) return;

    let nextValue = ViewerHelper.startZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleUpdateZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN].indexOf(tool) === -1) return;
    if (value.mode !== MODE_ZOOMING) return;

    //the mouse exited and reentered into svg
    let forceExit = (event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopZoomSelection(value, width, height)
      : ViewerHelper.updateZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStopZoom(event) {
    let abs = Math.abs;
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;
    let {startX, endX, startY, endY, specialKeyEnabled} = value;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT].indexOf(tool) === -1) return;
    if (value.mode !== MODE_ZOOMING && tool !== TOOL_ZOOM_OUT) return;

    let selectionMode = abs(startX - endX) > 7 && abs(startY - endY) > 7 && tool !== TOOL_ZOOM_OUT;

    let nextValue;

    if (selectionMode) {
      nextValue = ViewerHelper.stopZoomSelection(value, width, height);
    } else {
      let needZoomIn = (tool === TOOL_ZOOM_IN) || (tool === TOOL_ZOOM && !specialKeyEnabled);
      let scaleFactor = needZoomIn ? 1.1 : 0.9;
      nextValue = ViewerHelper.zoom(value, scaleFactor, x, y);
    }

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleEvent(event) {
    let {value, tool, onClick, onMouseUp, onMouseMove, onMouseDown} = this.props;
    let eventsHandler = {click: onClick, mousemove: onMouseMove, mouseup: onMouseUp, mousedown: onMouseDown};

    if (tool !== TOOL_NONE) return;
    let onEventHandler = eventsHandler[event.type];
    if (!onEventHandler) return;

    event.target = this.refs.svg;

    onEventHandler(new ViewerEvent(event, value));
  }

  handleSpecialKeyChange(event) {
    let {value, specialKeys, onChange} = this.props;
    let key = event.which;
    let active = event.type === "keydown";

    if (specialKeys.indexOf(key) === -1) return;

    let nextValue = active ? ViewerHelper.enableSpecialKey(value) : ViewerHelper.disableSpecialKey(value);

    onChange(new ViewerEvent(event, nextValue));
  }

  handlePinch(event) {
    let {value, onChange, detectPinch} = this.props;
    if (!detectPinch) return;

    let rect = this.refs.svg.getBoundingClientRect();
    let x = event.clientX - Math.round(rect.left);
    let y = event.clientY - Math.round(rect.top);
    var delta = Math.max(-1, Math.min(1, event.deltaY));
    let scaleFactor = mapRange(delta, -1, 1, 1.06, 0.96);

    let nextValue = ViewerHelper.zoom(value, scaleFactor, x, y);
    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleAutoPanDetection(event) {
    let {value, onChange, width, height, tool} = this.props;
    if (tool !== TOOL_NONE) return;

    let rect = this.refs.svg.getBoundingClientRect();
    let x = event.clientX - Math.round(rect.left);
    let y = event.clientY - Math.round(rect.top);

    let nextValue = ViewerHelper.updateAutoPan(value, x, y, width, height);
    onChange(new ViewerEvent(event, nextValue));
  }

  handleAutoPan() {
    let {value, onChange, tool, detectAutoPan} = this.props;
    let {autoPanX, autoPanY} = value;
    let deltaX = 0, deltaY = 0, delta = 30;

    if (tool !== TOOL_NONE) return;
    if (!value.focus) return;
    if (!detectAutoPan) return;

    if (autoPanX === DIRECTION_LEFT) {
      deltaX = delta;
    } else if (autoPanX === DIRECTION_RIGHT) {
      deltaX = -delta;
    }

    if (autoPanY === DIRECTION_UP) {
      deltaY = delta
    } else if (autoPanY === DIRECTION_DOWN) {
      deltaY = -delta;
    }

    let nextValue = ViewerHelper.pan(value, deltaX, deltaY);
    onChange(new ViewerEvent(null, nextValue));
  }

  handleUpdateFocus(event, focus) {
    let {value, onChange} = this.props;
    let nextValue = ViewerHelper.updateFocus(value, focus);
    onChange(new ViewerEvent(event, nextValue));
  }

  componentWillMount(event) {
    window.addEventListener("keydown", this.handleSpecialKeyChange, false);
    window.addEventListener("keyup", this.handleSpecialKeyChange, false);
  }

  componentDidMount() {
    this.autoPanTimer = setInterval(this.handleAutoPan, 200);
  }

  componentWillUnmount(event) {
    window.removeEventListener("keydown", this.handleSpecialKeyChange, false);
    window.removeEventListener("keyup", this.handleSpecialKeyChange, false);
    clearTimeout(this.autoPanTimer);
  }

  render() {
    let originalSVG = this.props.children;
    let tool = this.props.tool;
    let {matrix, mode, specialKeyEnabled} = this.props.value;
    let matrixStr = `matrix(${matrix.a}, ${matrix.b}, ${matrix.c}, ${matrix.d}, ${matrix.e}, ${matrix.f})`;

    let style = {};
    let gStyle = tool === TOOL_NONE ? {} : {pointerEvents: "none"};
    if (tool === TOOL_PAN) style.cursor = cursor(mode === MODE_PANNING ? 'grabbing' : 'grab');
    if ([TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT].indexOf(tool) >= 0) {
      let needZoomIn = (tool === TOOL_ZOOM_IN) || (tool === TOOL_ZOOM && !specialKeyEnabled);
      style.cursor = cursor(needZoomIn ? 'zoom-in' : 'zoom-out');
    }

    let zoomSelectionRect;
    if (mode === MODE_ZOOMING) {
      let {startX, startY, endX, endY} = this.props.value;
      let box = calculateBox({x: startX, y: startY}, {x: endX, y: endY});

      zoomSelectionRect =
        <rect
          stroke="#969FFF"
          strokeOpacity={0.7}
          fill="#F3F4FF"
          fillOpacity={0.7}
          x={box.x}
          y={box.y}
          width={box.width}
          height={box.height}/>
    }

    return (
      <svg
        ref="svg"
        width={this.props.width}
        height={this.props.height}
        style={Object.assign(style, this.props.style)}
        onMouseDown={ event => {
          this.handleStartPan(event);
          this.handleStartZoom(event)
        } }
        onMouseMove={ event => {
          this.handleUpdatePan(event);
          this.handleUpdateZoom(event);
          this.handleAutoPanDetection(event);
        } }
        onMouseUp={ event => {
          this.handleStopPan(event);
          this.handleStopZoom(event)
        } }
        onWheel={ event => {
          this.handlePinch(event)
        }}
        onMouseEnter={event => {
          this.handleUpdateFocus(event, true);
        }}
        onMouseLeave={event => {
          this.handleUpdateFocus(event, false);
        }}
      >

        <rect
          fill={this.props.background}
          x={0}
          y={0}
          width={this.props.width}
          height={this.props.height}
          style={{pointerEvents: "none"}}
        />

        <g
          ref="originalSVG"
          transform={matrixStr}
          style={gStyle}
          onMouseDown={ event => this.handleEvent(event)}
          onMouseMove={event => this.handleEvent(event)}
          onMouseUp={event => this.handleEvent(event)}
          onClick={event => this.handleEvent(event)}
        >
          <rect
            fill={this.props.SVGBackground}
            x={0}
            y={0}
            width={originalSVG.props.width}
            height={originalSVG.props.height}/>
          <g ref="content">
            {originalSVG.props.children}
          </g>
        </g>
        {zoomSelectionRect}
      </svg>
    );
  }
}

Viewer.propTypes = {
  //width of the viewer displayed on screen
  width: React.PropTypes.number.isRequired,

  //height of the viewer displayed on screen
  height: React.PropTypes.number.isRequired,

  //background of the viewer
  background: React.PropTypes.string,

  //background of the svg
  SVGBackground: React.PropTypes.string,

  //value of the viewer (current point of view)
  value: React.PropTypes.object.isRequired,

  //CSS style of the SVG tag
  style: React.PropTypes.object,

  //array of keys that in zoom mode switch zoom in and zoom out
  specialKeys: React.PropTypes.arrayOf(React.PropTypes.number),

  //detect zoom operation performed trough pinch gesture or mouse scroll
  detectPinch: React.PropTypes.bool,

  //perform PAN if the mouse is on viewer border
  detectAutoPan: React.PropTypes.bool,

  //handler something changed
  onChange: React.PropTypes.func.isRequired,

  //handler click
  onClick: React.PropTypes.func,

  //handler mouseup
  onMouseUp: React.PropTypes.func,

  //handler mousemove
  onMouseMove: React.PropTypes.func,

  //handler mousedown
  onMouseDown: React.PropTypes.func,

  //current active tool (TOOL_NONE, TOOL_PAN, TOOL_ZOOM)
  tool: React.PropTypes.oneOf([TOOL_NONE, TOOL_PAN, TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT]),

  //accept only one node SVG
  children: function (props, propName, componentName) {
    // Only accept a single child, of the appropriate type
    //credits: http://www.mattzabriskie.com/blog/react-validating-children
    var prop = props[propName];
    var types = ['svg'];
    if (React.Children.count(prop) !== 1 ||
      types.indexOf(prop.type) === -1) {
      return new Error(
        '`' + componentName + '` ' +
        'should have a single child of the following types: ' +
        ' `' + types.join('`, `') + '`.'
      );
    }
    if (!prop.props.hasOwnProperty('width') || !prop.props.hasOwnProperty('height')) {
      return new Error('SVG should have props `width` and `height`');
    }

  }
};

Viewer.defaultProps = {
  style: {},
  background: "#616264",
  SVGBackground: "#fff",
  tool: TOOL_NONE,
  detectPinch: true,
  detectAutoPan: true,
  specialKeys: [91, 17] //91=Win/Cmd 17=Ctrl,
};
<commit_msg>fix autopan<commit_after>import React from 'react';
import ViewerHelper from './viewer-helper';
import ViewerEvent from './viewer-event';
import cursor from './cursor';
import {calculateBox, mapRange} from './utils';
import {
  TOOL_NONE, TOOL_PAN, TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT,
  MODE_IDLE, MODE_PANNING, MODE_ZOOMING,
  DIRECTION_NONE, DIRECTION_UP, DIRECTION_RIGHT, DIRECTION_DOWN, DIRECTION_LEFT
} from './constants';

export default class Viewer extends React.Component {

  constructor(props) {
    super(props);
    this.handleSpecialKeyChange = this.handleSpecialKeyChange.bind(this);
    this.handleAutoPan = this.handleAutoPan.bind(this);
  }

  shouldComponentUpdate(nextProps) {
    return nextProps !== this.props;
  }

  handleStartPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_IDLE) return;

    let nextValue = ViewerHelper.startPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleUpdatePan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    //the mouse exited and reentered into svg
    let forceExit = (value.mode === MODE_PANNING && event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopPan(value) :
      ViewerHelper.updatePan(value, x, y, 20, SVGWidth, SVGHeight, width, height);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStopPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    let nextValue = ViewerHelper.stopPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStartZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN].indexOf(tool) === -1) return;
    if (value.mode !== MODE_IDLE) return;

    //let point = ViewerHelper.getSVGPoint(value, x, y);
    //if(!ViewerHelper.isPointInsideSVG(point.x, point.y, SVGWidth, SVGHeight)) return;

    let nextValue = ViewerHelper.startZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleUpdateZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN].indexOf(tool) === -1) return;
    if (value.mode !== MODE_ZOOMING) return;

    //the mouse exited and reentered into svg
    let forceExit = (event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopZoomSelection(value, width, height)
      : ViewerHelper.updateZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStopZoom(event) {
    let abs = Math.abs;
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;
    let {startX, endX, startY, endY, specialKeyEnabled} = value;

    if ([TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT].indexOf(tool) === -1) return;
    if (value.mode !== MODE_ZOOMING && tool !== TOOL_ZOOM_OUT) return;

    let selectionMode = abs(startX - endX) > 7 && abs(startY - endY) > 7 && tool !== TOOL_ZOOM_OUT;

    let nextValue;

    if (selectionMode) {
      nextValue = ViewerHelper.stopZoomSelection(value, width, height);
    } else {
      let needZoomIn = (tool === TOOL_ZOOM_IN) || (tool === TOOL_ZOOM && !specialKeyEnabled);
      let scaleFactor = needZoomIn ? 1.1 : 0.9;
      nextValue = ViewerHelper.zoom(value, scaleFactor, x, y);
    }

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleEvent(event) {
    let {value, tool, onClick, onMouseUp, onMouseMove, onMouseDown} = this.props;
    let eventsHandler = {click: onClick, mousemove: onMouseMove, mouseup: onMouseUp, mousedown: onMouseDown};

    if (tool !== TOOL_NONE) return;
    let onEventHandler = eventsHandler[event.type];
    if (!onEventHandler) return;

    event.target = this.refs.svg;

    onEventHandler(new ViewerEvent(event, value));
  }

  handleSpecialKeyChange(event) {
    let {value, specialKeys, onChange} = this.props;
    let key = event.which;
    let active = event.type === "keydown";

    if (specialKeys.indexOf(key) === -1) return;

    let nextValue = active ? ViewerHelper.enableSpecialKey(value) : ViewerHelper.disableSpecialKey(value);

    onChange(new ViewerEvent(event, nextValue));
  }

  handlePinch(event) {
    let {value, onChange, detectPinch} = this.props;
    if (!detectPinch) return;

    let rect = this.refs.svg.getBoundingClientRect();
    let x = event.clientX - Math.round(rect.left);
    let y = event.clientY - Math.round(rect.top);
    var delta = Math.max(-1, Math.min(1, event.deltaY));
    let scaleFactor = mapRange(delta, -1, 1, 1.06, 0.96);

    let nextValue = ViewerHelper.zoom(value, scaleFactor, x, y);
    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleAutoPanDetection(event) {
    let {value, onChange, width, height, tool} = this.props;
    if (tool !== TOOL_NONE) return;

    let rect = this.refs.svg.getBoundingClientRect();
    let x = event.clientX - Math.round(rect.left);
    let y = event.clientY - Math.round(rect.top);

    let nextValue = ViewerHelper.updateAutoPan(value, x, y, width, height);
    if (value !== nextValue) onChange(new ViewerEvent(event, nextValue));
  }

  handleAutoPan() {
    let {value, onChange, tool, detectAutoPan} = this.props;
    let {autoPanX, autoPanY} = value;
    let deltaX = 0, deltaY = 0, delta = 30;

    if (tool !== TOOL_NONE) return;
    if (!value.focus) return;
    if (!detectAutoPan) return;
    if (autoPanX === DIRECTION_NONE && autoPanY === DIRECTION_NONE) return;

    if (autoPanX === DIRECTION_LEFT) {
      deltaX = delta;
    } else if (autoPanX === DIRECTION_RIGHT) {
      deltaX = -delta;
    }

    if (autoPanY === DIRECTION_UP) {
      deltaY = delta
    } else if (autoPanY === DIRECTION_DOWN) {
      deltaY = -delta;
    }

    let nextValue = ViewerHelper.pan(value, deltaX, deltaY);
    onChange(new ViewerEvent(null, nextValue));
  }

  handleUpdateFocus(event, focus) {
    let {value, onChange} = this.props;
    let nextValue = ViewerHelper.updateFocus(value, focus);
    onChange(new ViewerEvent(event, nextValue));
  }

  componentWillMount(event) {
    window.addEventListener("keydown", this.handleSpecialKeyChange, false);
    window.addEventListener("keyup", this.handleSpecialKeyChange, false);
  }

  componentDidMount() {
    this.autoPanTimer = setInterval(this.handleAutoPan, 200);
  }

  componentWillUnmount(event) {
    window.removeEventListener("keydown", this.handleSpecialKeyChange, false);
    window.removeEventListener("keyup", this.handleSpecialKeyChange, false);
    clearTimeout(this.autoPanTimer);
  }

  render() {
    let originalSVG = this.props.children;
    let tool = this.props.tool;
    let {matrix, mode, specialKeyEnabled} = this.props.value;
    let matrixStr = `matrix(${matrix.a}, ${matrix.b}, ${matrix.c}, ${matrix.d}, ${matrix.e}, ${matrix.f})`;

    let style = {};
    let gStyle = tool === TOOL_NONE ? {} : {pointerEvents: "none"};
    if (tool === TOOL_PAN) style.cursor = cursor(mode === MODE_PANNING ? 'grabbing' : 'grab');
    if ([TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT].indexOf(tool) >= 0) {
      let needZoomIn = (tool === TOOL_ZOOM_IN) || (tool === TOOL_ZOOM && !specialKeyEnabled);
      style.cursor = cursor(needZoomIn ? 'zoom-in' : 'zoom-out');
    }

    let zoomSelectionRect;
    if (mode === MODE_ZOOMING) {
      let {startX, startY, endX, endY} = this.props.value;
      let box = calculateBox({x: startX, y: startY}, {x: endX, y: endY});

      zoomSelectionRect =
        <rect
          stroke="#969FFF"
          strokeOpacity={0.7}
          fill="#F3F4FF"
          fillOpacity={0.7}
          x={box.x}
          y={box.y}
          width={box.width}
          height={box.height}/>
    }

    return (
      <svg
        ref="svg"
        width={this.props.width}
        height={this.props.height}
        style={Object.assign(style, this.props.style)}
        onMouseDown={ event => {
          this.handleStartPan(event);
          this.handleStartZoom(event)
        } }
        onMouseMove={ event => {
          this.handleUpdatePan(event);
          this.handleUpdateZoom(event);
          this.handleAutoPanDetection(event);
        } }
        onMouseUp={ event => {
          this.handleStopPan(event);
          this.handleStopZoom(event)
        } }
        onWheel={ event => {
          this.handlePinch(event)
        }}
        onMouseEnter={event => {
          this.handleUpdateFocus(event, true);
        }}
        onMouseLeave={event => {
          this.handleUpdateFocus(event, false);
        }}
      >

        <rect
          fill={this.props.background}
          x={0}
          y={0}
          width={this.props.width}
          height={this.props.height}
          style={{pointerEvents: "none"}}
        />

        <g
          ref="originalSVG"
          transform={matrixStr}
          style={gStyle}
          onMouseDown={ event => this.handleEvent(event)}
          onMouseMove={event => this.handleEvent(event)}
          onMouseUp={event => this.handleEvent(event)}
          onClick={event => this.handleEvent(event)}
        >
          <rect
            fill={this.props.SVGBackground}
            x={0}
            y={0}
            width={originalSVG.props.width}
            height={originalSVG.props.height}/>
          <g ref="content">
            {originalSVG.props.children}
          </g>
        </g>
        {zoomSelectionRect}
      </svg>
    );
  }
}

Viewer.propTypes = {
  //width of the viewer displayed on screen
  width: React.PropTypes.number.isRequired,

  //height of the viewer displayed on screen
  height: React.PropTypes.number.isRequired,

  //background of the viewer
  background: React.PropTypes.string,

  //background of the svg
  SVGBackground: React.PropTypes.string,

  //value of the viewer (current point of view)
  value: React.PropTypes.object.isRequired,

  //CSS style of the SVG tag
  style: React.PropTypes.object,

  //array of keys that in zoom mode switch zoom in and zoom out
  specialKeys: React.PropTypes.arrayOf(React.PropTypes.number),

  //detect zoom operation performed trough pinch gesture or mouse scroll
  detectPinch: React.PropTypes.bool,

  //perform PAN if the mouse is on viewer border
  detectAutoPan: React.PropTypes.bool,

  //handler something changed
  onChange: React.PropTypes.func.isRequired,

  //handler click
  onClick: React.PropTypes.func,

  //handler mouseup
  onMouseUp: React.PropTypes.func,

  //handler mousemove
  onMouseMove: React.PropTypes.func,

  //handler mousedown
  onMouseDown: React.PropTypes.func,

  //current active tool (TOOL_NONE, TOOL_PAN, TOOL_ZOOM)
  tool: React.PropTypes.oneOf([TOOL_NONE, TOOL_PAN, TOOL_ZOOM, TOOL_ZOOM_IN, TOOL_ZOOM_OUT]),

  //accept only one node SVG
  children: function (props, propName, componentName) {
    // Only accept a single child, of the appropriate type
    //credits: http://www.mattzabriskie.com/blog/react-validating-children
    var prop = props[propName];
    var types = ['svg'];
    if (React.Children.count(prop) !== 1 ||
      types.indexOf(prop.type) === -1) {
      return new Error(
        '`' + componentName + '` ' +
        'should have a single child of the following types: ' +
        ' `' + types.join('`, `') + '`.'
      );
    }
    if (!prop.props.hasOwnProperty('width') || !prop.props.hasOwnProperty('height')) {
      return new Error('SVG should have props `width` and `height`');
    }

  }
};

Viewer.defaultProps = {
  style: {},
  background: "#616264",
  SVGBackground: "#fff",
  tool: TOOL_NONE,
  detectPinch: true,
  detectAutoPan: true,
  specialKeys: [91, 17] //91=Win/Cmd 17=Ctrl,
};
<|endoftext|>


<commit_before>(function($) {
    // Import
    var ObjectBase = gce.utils.ObjectBase;

    var aabb_create = gce.datatypes.aabb_create;

    var pos_create = gce.datatypes.pos_create;
    var pos_copy = gce.datatypes.pos_copy;
    var pos_concat = gce.datatypes.pos_concat;
    var pos_invert = gce.datatypes.pos_invert;
    var pos_transform = gce.datatypes.pos_transform;
    var pos_set_transform = gce.datatypes.pos_set_transform;

    var DragManager = gce.managers.DragManager;

    // ----------------------------------------------------------------------
    // Platform dependency shims
    // ----------------------------------------------------------------------

    /**
     * Test for and correct Mozilla's PointInPath bug (credit:
     * stackoverflow).
     */
    (function() {
        // Create a temporary canvas.
        var c = document.createElement("canvas").getContext("2d");
        c.translate(50, 0);
        c.moveTo(125, 50);
        c.arc(100, 50, 25, 0, 360, false);
        if (!c.isPointInPath(150, 50))
        {
            // Replace the method with a new wrapper.
            var proto = CanvasRenderingContext2D.prototype;
            var original = proto.isPointInPath;
            proto.isPointInPath = function(x, y) {
                this.save();
                this.setTransform(1,0, 0,1, 0,0);
                var result = original.call(this, x, y);
                this.restore();
                return result;
            };
        };
    })();

    // --------------------------------------------------------------------
    // Renderers are used to display object.
    // --------------------------------------------------------------------
    var Renderer = ObjectBase.extend();

    /**
     * Creates a renderer for the given element. If any recursively
     * nested elements also need their own renderers, then the given
     * get_renderer function can be called to provide them.
     */
    Renderer.init = function(parent, element, get_renderer) {
        this.parent = parent;
        this.element = element;
    };

    /**
     * Renders this object to the given context. This is normally not
     * overridden, since it provides top level support for things like
     * in-bounds detection, Position-Orientation-Scale and
     * filters. Instead, override the render_local_coords function.
     */
    Renderer.render_global_coords = function(c, pos_stack, global_bounds) {
        var pos = this.element.pos;
        pos = pos_concat(pos_stack[0], pos);

        // TODO: Calculate the local bounds from the transform and
        // global bounds.
        var local_bounds = undefined;

        // Set the transform.
        c.save();
        pos_stack.unshift(pos);
        pos_set_transform(pos, c);

        // Draw the object.
        this.render_local_coords(c, pos_stack, local_bounds);

        // Remove the transform.
        pos_stack.shift();
        c.restore();

        // TODO: Handle filters.
    };

    /**
     * Override this function to do the actual rendering for this
     * object.
     */
    Renderer.render_local_coords = function(c, pos_stack, local_bounds) {
        c.fillRect(-50, -50, 100, 100);
    };

    /**
     * Returns true if the given global point is inside this
     * object. Objects can decide whether to be transparent to clicks,
     * or can use click areas that are different from their rendering
     * envelope. By default this method performs global to local
     * transform on the given point, then calls
     * is_local_point_in_object. In most cases, however, there are
     * better ways to calculate the same thing.
     */
    Renderer.is_global_point_in_object = function(c, pos_stack, global_point) {
        var pos = this.element.pos;
        pos = pos_concat(pos_stack[0], pos);

        // Calculate the local point.
        var global_to_local = pos_invert(pos);
        var local_point = pos_transform(global_to_local, global_point);

        // Calculate the result.
        pos_stack.unshift(pos);
        var result = this.is_local_point_in_object(c, pos_stack, local_point);
        pos_stack.shift();
        return result;
    };

    /**
     * Should return true if the given point (in object coordinates)
     * is in this object. This should be overridden, unless the global
     * version is overridden.
     */
    Renderer.is_local_point_in_object = function(c, pos_stack, local_point) {
        var x = local_point.x, y = local_point.y;
        return x > -50 && x < 50 && y > -50 && y < 50;;
    };

    // --------------------------------------------------------------------
    // The viewer displays a document on a canvas.
    // --------------------------------------------------------------------

    var Viewer = ObjectBase.extend();

    /**
     * Creates a new viewer to connect the given canvas to the given
     * document.
     *
     * The given list of renderers are used to turn objects in the
     * document into a visual representation. They should be derived
     * from the Renderer object.
     */
    Viewer.init = function($canvas, document, renderers) {
        this.$canvas = $canvas
        this.canvas = $canvas.get(0);
        this.c = this.canvas.getContext("2d");

        this.pos = pos_create();
        this.document = document;

        this._init_renderers(renderers);
        this._init_events();

        this.draw();
    };

    /**
     * Initializes events.
     */
    Viewer._init_events = function() {
        var that = this;
        this.$canvas.mousedown(function(event) {
            var w = that.$canvas.width(), h = that.$canvas.height();

            var dm = DragManager.create();
            dm.set_pos(that.pos, {x:w*0.5, y:h*0.5}, false);
            dm.set_locks(false, true, false);
            dm.set_rotate_scale_override(event.shiftKey);
            dm.start_touch(1, {x:event.offsetX, y:event.offsetY});

            var move = function(event) {
                dm.set_rotate_scale_override(event.shiftKey);
                dm.move_touch(1, {x:event.offsetX, y:event.offsetY});

                that.pos = pos_copy(dm.pos);
                that.draw();
            };

            var up = function(event) {
                dm.end_touch(1, {x:event.offsetX, y:event.offsetY});

                that.$canvas.unbind('mousemove', move);
                that.$canvas.unbind('mouseup', up);
            };

            that.$canvas.bind('mousemove', move);
            that.$canvas.bind('mouseup', up);
        });
    };

    /**
     * Initializes the renderer lookup.
     */
    Viewer._init_renderers = function(renderers) {
        /*
         * Create a local function that can retrieve a valid renderer
         * for the given element.
         */
        var that = this;
        var get_renderer = function(element) {
            // Try to find a cached renderer.
            var renderer = element["-renderer"];
            if (renderer === undefined) {

                // Otherwise find the approprate type.
                var RendererType = that.renderers[element.type];
                if (RendererType === undefined) {
                    RendererType = that.renderers["$default"];
                }

                // Instantiate it.
                renderer = RendererType.create(that, element, get_renderer);

                // Cache it.
                element["-renderer"] = renderer;
            }

            return renderer;
        };

        // Store the renderers and the function.
        this.renderers = renderers || {"$default":Renderer};
        this.get_renderer = get_renderer;
    };

    /**
     * Returns the POS of the main view, which is based on the
     * internal POS field, and the dimensions of the canvas, such that
     * the position in that POS field appears at the center of the canvas.
     */
    Viewer.get_view_pos = function() {
        var pos = this.pos;
        var w = this.$canvas.width(), h = this.$canvas.height();
        return {x:w*0.5+pos.x, y:h*0.5+pos.y, o:pos.o, s:pos.s};
    };

    /**
     * Performs a complete redraw of the canvas.
     */
    Viewer.draw = function() {
        var c = this.c;
        var w = this.$canvas.width(), h = this.$canvas.height();

        c.setTransform(1, 0, 0, 1, 0, 0);
        this.redraw(0, 0, w, h);
    };

    /**
     * Redraws the given part of the canvas.
     */
    Viewer.redraw = function(x, y, w, h) {
        var c = this.c;
        c.clearRect(x, y, w, h);

        var pos_stack = [this.get_view_pos()];
        var aabb = aabb_create(x, y, w, h);

        var content = this.document.content;
        for (var i = 0; i < content.length; i++) {
            var element = content[i];
            var renderer = this.get_renderer(element);
            renderer.render_global_coords(c, pos_stack, aabb);
        }
    };

    /**
     * Returns the renderers of the objects at the given global
     * coordinates.
     */
    Viewer.get_renderers_at = function(xy) {
        var pos_stack = [this.get_view_pos()];
        var c = this.c;

        var result = [];
        var content = this.document.content;
        for (var i = 0; i < content.length; i++) {
            var element = content[i];
            var renderer = this.get_renderer(element);
            if (renderer.is_global_point_in_object(c, pos_stack, xy)) {
                result.push(renderer);
            };
        }
        return result;
    };

    // --------------------------------------------------------------------
    // API
    // --------------------------------------------------------------------

    if (window.gce === undefined) window.gce = {};
    window.gce.viewer = {
        Viewer: Viewer
    };

})(jQuery);<commit_msg>Added animation shim.<commit_after>(function($) {
    // Import
    var ObjectBase = gce.utils.ObjectBase;

    var aabb_create = gce.datatypes.aabb_create;

    var pos_create = gce.datatypes.pos_create;
    var pos_copy = gce.datatypes.pos_copy;
    var pos_concat = gce.datatypes.pos_concat;
    var pos_invert = gce.datatypes.pos_invert;
    var pos_transform = gce.datatypes.pos_transform;
    var pos_set_transform = gce.datatypes.pos_set_transform;

    var DragManager = gce.managers.DragManager;

    // ----------------------------------------------------------------------
    // Platform dependency shims
    // ----------------------------------------------------------------------

    /**
     * Animation can be synched to the platform refresh rate, but in a
     * platform-dependent way.
     */
    (function() {
        if (!window.requestAnimationFrame) {
            window.requestAnimationFrame = (
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimationFrame ||
                function(callback) {
                    window.setTimeout(callback, 1000 / 60);
                }
            );
        }
    })();

    /**
     * Test for and correct Mozilla's PointInPath bug (credit:
     * stackoverflow).
     */
    (function() {
        // Create a temporary canvas.
        var c = document.createElement("canvas").getContext("2d");
        c.translate(50, 0);
        c.moveTo(125, 50);
        c.arc(100, 50, 25, 0, 360, false);
        if (!c.isPointInPath(150, 50))
        {
            // Replace the method with a new wrapper.
            var proto = CanvasRenderingContext2D.prototype;
            var original = proto.isPointInPath;
            proto.isPointInPath = function(x, y) {
                this.save();
                this.setTransform(1,0, 0,1, 0,0);
                var result = original.call(this, x, y);
                this.restore();
                return result;
            };
        };
    })();

    // --------------------------------------------------------------------
    // Renderers are used to display objects.
    // --------------------------------------------------------------------
    var Renderer = ObjectBase.extend();

    /**
     * Creates a renderer for the given element. If any recursively
     * nested elements also need their own renderers, then the given
     * get_renderer function can be called to provide them.
     */
    Renderer.init = function(parent, element, get_renderer) {
        this.parent = parent;
        this.element = element;
    };

    /**
     * Renders this object to the given context. This is normally not
     * overridden, since it provides top level support for things like
     * in-bounds detection, Position-Orientation-Scale and
     * filters. Instead, override the render_local_coords function.
     */
    Renderer.render_global_coords = function(c, pos_stack, global_bounds) {
        var pos = this.element.pos;
        pos = pos_concat(pos_stack[0], pos);

        // TODO: Calculate the local bounds from the transform and
        // global bounds.
        var local_bounds = undefined;

        // Set the transform.
        c.save();
        pos_stack.unshift(pos);
        pos_set_transform(pos, c);

        // Draw the object.
        this.render_local_coords(c, pos_stack, local_bounds);

        // Remove the transform.
        pos_stack.shift();
        c.restore();

        // TODO: Handle filters.
    };

    /**
     * Override this function to do the actual rendering for this
     * object.
     */
    Renderer.render_local_coords = function(c, pos_stack, local_bounds) {
        c.fillRect(-50, -50, 100, 100);
    };

    /**
     * Returns true if the given global point is inside this
     * object. Objects can decide whether to be transparent to clicks,
     * or can use click areas that are different from their rendering
     * envelope. By default this method performs global to local
     * transform on the given point, then calls
     * is_local_point_in_object. In most cases, however, there are
     * better ways to calculate the same thing.
     */
    Renderer.is_global_point_in_object = function(c, pos_stack, global_point) {
        var pos = this.element.pos;
        pos = pos_concat(pos_stack[0], pos);

        // Calculate the local point.
        var global_to_local = pos_invert(pos);
        var local_point = pos_transform(global_to_local, global_point);

        // Calculate the result.
        pos_stack.unshift(pos);
        var result = this.is_local_point_in_object(c, pos_stack, local_point);
        pos_stack.shift();
        return result;
    };

    /**
     * Should return true if the given point (in object coordinates)
     * is in this object. This should be overridden, unless the global
     * version is overridden.
     */
    Renderer.is_local_point_in_object = function(c, pos_stack, local_point) {
        var x = local_point.x, y = local_point.y;
        return x > -50 && x < 50 && y > -50 && y < 50;;
    };

    // --------------------------------------------------------------------
    // The viewer displays a document on a canvas.
    // --------------------------------------------------------------------

    var Viewer = ObjectBase.extend();

    /**
     * Creates a new viewer to connect the given canvas to the given
     * document.
     *
     * The given list of renderers are used to turn objects in the
     * document into a visual representation. They should be derived
     * from the Renderer object.
     */
    Viewer.init = function($canvas, document, renderers) {
        this.$canvas = $canvas
        this.canvas = $canvas.get(0);
        this.c = this.canvas.getContext("2d");

        this.pos = pos_create();
        this.document = document;

        this._init_renderers(renderers);
        this._init_events();

        this.draw();
    };

    /**
     * Initializes events.
     */
    Viewer._init_events = function() {
        var that = this;
        this.$canvas.mousedown(function(event) {
            var w = that.$canvas.width(), h = that.$canvas.height();

            var dm = DragManager.create();
            dm.set_pos(that.pos, {x:w*0.5, y:h*0.5}, false);
            dm.set_locks(false, true, false);
            dm.set_rotate_scale_override(event.shiftKey);
            dm.start_touch(1, {x:event.offsetX, y:event.offsetY});

            var move = function(event) {
                dm.set_rotate_scale_override(event.shiftKey);
                dm.move_touch(1, {x:event.offsetX, y:event.offsetY});

                that.pos = pos_copy(dm.pos);
                that.draw();
            };

            var up = function(event) {
                dm.end_touch(1, {x:event.offsetX, y:event.offsetY});

                that.$canvas.unbind('mousemove', move);
                that.$canvas.unbind('mouseup', up);
            };

            that.$canvas.bind('mousemove', move);
            that.$canvas.bind('mouseup', up);
        });
    };

    /**
     * Initializes the renderer lookup.
     */
    Viewer._init_renderers = function(renderers) {
        /*
         * Create a local function that can retrieve a valid renderer
         * for the given element.
         */
        var that = this;
        var get_renderer = function(element) {
            // Try to find a cached renderer.
            var renderer = element["-renderer"];
            if (renderer === undefined) {

                // Otherwise find the approprate type.
                var RendererType = that.renderers[element.type];
                if (RendererType === undefined) {
                    RendererType = that.renderers["$default"];
                }

                // Instantiate it.
                renderer = RendererType.create(that, element, get_renderer);

                // Cache it.
                element["-renderer"] = renderer;
            }

            return renderer;
        };

        // Store the renderers and the function.
        this.renderers = renderers || {"$default":Renderer};
        this.get_renderer = get_renderer;
    };

    /**
     * Returns the POS of the main view, which is based on the
     * internal POS field, and the dimensions of the canvas, such that
     * the position in that POS field appears at the center of the canvas.
     */
    Viewer.get_view_pos = function() {
        var pos = this.pos;
        var w = this.$canvas.width(), h = this.$canvas.height();
        return {x:w*0.5+pos.x, y:h*0.5+pos.y, o:pos.o, s:pos.s};
    };

    /**
     * Performs a complete redraw of the canvas.
     */
    Viewer.draw = function() {
        var c = this.c;
        var w = this.$canvas.width(), h = this.$canvas.height();

        c.setTransform(1, 0, 0, 1, 0, 0);
        this.redraw(0, 0, w, h);
    };

    /**
     * Redraws the given part of the canvas.
     */
    Viewer.redraw = function(x, y, w, h) {
        var c = this.c;
        c.clearRect(x, y, w, h);

        var pos_stack = [this.get_view_pos()];
        var aabb = aabb_create(x, y, w, h);

        var content = this.document.content;
        for (var i = 0; i < content.length; i++) {
            var element = content[i];
            var renderer = this.get_renderer(element);
            renderer.render_global_coords(c, pos_stack, aabb);
        }
    };

    /**
     * Returns the renderers of the objects at the given global
     * coordinates.
     */
    Viewer.get_renderers_at = function(xy) {
        var pos_stack = [this.get_view_pos()];
        var c = this.c;

        var result = [];
        var content = this.document.content;
        for (var i = 0; i < content.length; i++) {
            var element = content[i];
            var renderer = this.get_renderer(element);
            if (renderer.is_global_point_in_object(c, pos_stack, xy)) {
                result.push(renderer);
            };
        }
        return result;
    };

    // --------------------------------------------------------------------
    // API
    // --------------------------------------------------------------------

    if (window.gce === undefined) window.gce = {};
    window.gce.viewer = {
        Viewer: Viewer
    };

})(jQuery);<|endoftext|>


<commit_before>var MAGNET = "magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d&dn=sintel.mp4&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.webtorrent.io&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel-1024-surround.mp4"
var OK = {
  'log': false,
  'client': false,
  'directory': false
}

function logTestModule(module){
  console.log('\n==========\n' + module + '\n==========\n')
}
function logTestProto(proto){
  console.log('==> ' + proto)
}
function finishTest(){
  var end = true
  for (var m in OK){
    end = end && OK[m]
  }
  if(end){
    process.exit(0)
  }
}

/*
* TEST LOG
*/
function testLog(cb){
  logTestModule('Log')
  var Log = require('./log.js')

  logTestProto('print')
  Log.print('Print ok')

  logTestProto('echo')
  Log.echo('Echo ok')

  setTimeout(function(){
    delete Log
    cb(true)
  }, 1000)
}

testLog(function(value){
  OK.log = value
  finishTest()
})

/*
* TEST CLIENT
*/
function testClient(cb){
  logTestModule('Client')
  var Client = require('./client.js')
  var c = new Client()

  logTestProto('on start')
  c.on('start', function(hash){
    console.log('Client start trigger')
    console.log('Hash: ' + hash)

    logTestProto('getTorrent')
    var torrent = c.getTorrent()
    console.log('Torrent: ')
    console.log(torrent)
  })

  logTestProto('on download')
  c.on('download', function(torrent){
    console.log('Client download trigger')
    console.log('Torrent: ')
    console.log(torrent)

    logTestProto('stop')
    c.stop()
  })

  logTestProto('on done')
  c.on('done', function(hash, name){
    console.log('Client done trigger')
    console.log('Hash: ' + hash + '\nName: ' + name)

    cb(true)
    delete Client
    delete c
  })

  c.download(MAGNET)
}

testClient(function(value){
  OK.client = value
  finishTest()
})

/*
* TEST DIRECTORY
*/
function testDirectory(cb){
  logTestModule('Directory')
  var Directory = require('./directory.js')
  var dir = "ok"
  var dir2 = "ok2"
  var dir3 = "ok3"

  logTestProto('mkdir')
  Directory.mkdir(dir)

  logTestProto('list')
  console.log(Directory.list(dir))

  logTestProto('getDir')
  console.log(Directory.getDir(dir))

  logTestProto('getInfo')
  console.log(Directory.getInfo(dir))

  logTestProto('setDownloading')
  Directory.setDownloading(dir)

  logTestProto('isDownloading')
  var isDl = Directory.isDownloading(dir))
  console.log(isDl)
  if(!isDl){
    cb(false)
  }

  logTestProto('updateDownloads')
  Directory.updateDownloads()

  logTestProto('finishDownloading')
  Directory.finishDownloading(dir)

  logTestProto('isDownloading')
  var isDl = Directory.isDownloading(dir))
  console.log(isDl)
  if(isDl){
    cb(false)
  }

  logTestProto('rename')
  Directory.rename('/', dir, dir2)

  logTestProto('mv')
  Directory.mkdir(dir3)
  Directory.mv('/', dir2, dir3)

  logTestProto('remove')
  Directory.remove(dir3)
}

testDirectory(function(value){
  OK.directory = value
  finishTest()
})
<commit_msg>fix directory test<commit_after>var MAGNET = "magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d&dn=sintel.mp4&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.webtorrent.io&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel-1024-surround.mp4"
var OK = {
  'log': false,
  'client': false,
  'directory': false
}

function logTestModule(module){
  console.log('\n==========\n' + module + '\n==========\n')
}
function logTestProto(proto){
  console.log('==> ' + proto)
}
function finishTest(){
  var end = true
  for (var m in OK){
    end = end && OK[m]
  }
  if(end){
    process.exit(0)
  }
}

/*
* TEST LOG
*/
function testLog(cb){
  logTestModule('Log')
  var Log = require('./log.js')

  logTestProto('print')
  Log.print('Print ok')

  logTestProto('echo')
  Log.echo('Echo ok')

  setTimeout(function(){
    delete Log
    cb(true)
  }, 1000)
}

testLog(function(value){
  OK.log = value
  finishTest()
})

/*
* TEST CLIENT
*/
function testClient(cb){
  logTestModule('Client')
  var Client = require('./client.js')
  var c = new Client()

  logTestProto('on start')
  c.on('start', function(hash){
    console.log('Client start trigger')
    console.log('Hash: ' + hash)

    logTestProto('getTorrent')
    var torrent = c.getTorrent()
    console.log('Torrent: ')
    console.log(torrent)
  })

  logTestProto('on download')
  c.on('download', function(torrent){
    console.log('Client download trigger')
    console.log('Torrent: ')
    console.log(torrent)

    logTestProto('stop')
    c.stop()
  })

  logTestProto('on done')
  c.on('done', function(hash, name){
    console.log('Client done trigger')
    console.log('Hash: ' + hash + '\nName: ' + name)

    cb(true)
    delete Client
    delete c
  })

  c.download(MAGNET)
}

testClient(function(value){
  OK.client = value
  finishTest()
})

/*
* TEST DIRECTORY
*/
function testDirectory(cb){
  logTestModule('Directory')
  var Directory = require('./directory.js')
  var dir = "ok"
  var dir2 = "ok2"
  var dir3 = "ok3"

  logTestProto('mkdir')
  Directory.mkdir('/', dir)

  logTestProto('list')
  console.log(Directory.list(dir))

  logTestProto('getDir')
  console.log(Directory.getDir(dir))

  logTestProto('getInfo')
  console.log(Directory.getInfo(dir))

  logTestProto('setDownloading')
  Directory.setDownloading(dir)

  logTestProto('isDownloading')
  var isDl = Directory.isDownloading(dir)
  console.log(isDl)
  if(!isDl){
    cb(false)
  }

  logTestProto('updateDownloads')
  Directory.updateDownloads()

  logTestProto('finishDownloading')
  Directory.finishDownloading(dir)

  logTestProto('isDownloading')
  var isDl = Directory.isDownloading(dir)
  console.log(isDl)
  if(isDl){
    cb(false)
  }

  logTestProto('rename')
  Directory.rename('/', dir, dir2)

  logTestProto('mv')
  Directory.mkdir(dir3)
  Directory.mv('/', dir2, dir3)

  logTestProto('remove')
  Directory.remove(dir3)
}

testDirectory(function(value){
  OK.directory = value
  finishTest()
})
<|endoftext|>


<commit_before>function plot(data, varname, tag) {

    $(tag).empty();

    var margin = {top: 20, right: 30, bottom: 80, left: 10},
	width = 1500 - margin.left - margin.right,
	height = 500 - margin.top - margin.bottom;

    var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse,
	formatDate = d3.time.format("%Y-%m-%d");

    var x = d3.time.scale()
	.range([0, width]);

    var y = d3.scale.linear()
	.range([height, 0]);

    var xAxis = d3.svg.axis()
	.scale(x)
	.orient("bottom")

    var yAxis = d3.svg.axis()
	.scale(y)
	.orient("right")

    var line = d3.svg.line()
	.x(function(d) { return x(d.date); })
	.y(function(d) { return y(d.var); });

    var svg = d3.select(tag).append("svg")
	.attr("width", width + margin.left + margin.right)
	.attr("height", height + margin.top + margin.bottom)
	.append("g")
	.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    svg.append("g")
	.attr("class", "x axis")
	.attr("transform", "translate(0," + height + ")");

    svg.append("g")
	.attr("class", "y axis")
	.attr("transform", "translate(" + width + ", 0)");

    data.forEach(function(d) {
	d.date = new Date(d.created_at);
	d.var = parseFloat(d[varname]);
	if (d.us_units == 0) {
	    if (varname == "outside_temp" || varname == "inside_temp")
		// TODO(dek): correct for pressure.
		d.var = d.var * 9 / 5. + 32.;
	    if (varname == "pressure")
		d.var = d.var / 33.86;
	}
	if (varname == "rssi")
            if (d.var < 0) d.var = -d.var;
    });

    data = data.filter(function(d) {
	if (varname == "pressure")
	    return d.var > 20 && d.var < 35;
	if (varname == "outside_temp" || varname == "inside_temp")
	    return d.var > 10 && d.var < 100;
	return true;
    });

    x.domain([new Date(2014, 10, 1), new Date(2014, 11, 1)]);
    y.domain(d3.extent(data, function(d) { return d.var; }));

    var dataNest = d3.nest()
	.key(function(d) { return d["station.uuid"]; })
	.entries(data);

    var color = d3.scale.category10();
    legendSpace = width/dataNest.length; // spacing for legend

    /*
      svg.append("clipPath")
      .attr("id", "clip")
      .append("rect")
      .attr("x", x(0))
      .attr("y", y(1))
      .attr("width", x(1) - x(0))
      .attr("height", y(0) - y(1));
    */
    dataNest.forEach(function(d, i) {
	svg.append("path")
	    .attr("class", "line")
	    .style("stroke", function() {
                return d.color = color(d.key); })
	    .attr("d", line(d.values))
	    .attr("clip-path", "url(#clip)");

	svg.append("text")
	    .attr("x", (legendSpace/2)+i*legendSpace) // spacing
	    .attr("y", height + (margin.bottom/2)+ 25)
	    .attr("class", "legend")
	    .style("fill", function() {
		return d.color = color(d.key); })
	    .text(d.key);
    }
		    );

    var draw = function() {
	svg.select("g.x.axis").call(xAxis).selectAll("text")
            .style("text-anchor", "end")
            .attr("dx", "-.8em")
            .attr("dy", ".15em")
            .attr("transform", function(d) {
                return "rotate(-90)";
            });
	svg.select("g.y.axis").call(yAxis);

	console.log(svg.selectAll("path.line"))
	svg.selectAll("path.line")[0].forEach(function(d, i) {
	    d.attributes['d'].value=line(dataNest[i].values);
	});
    }

    var zoom = d3.behavior.zoom()
	.on("zoom", draw);

    svg.append("rect")
	.attr("class", "pane")
	.attr("width", width)
	.attr("height", height)
	.call(zoom);
    zoom.x(x);
    draw();
}

<commit_msg>make  X domain be full extent of time series.  Make clip path work.<commit_after>function plot(data, varname, tag) {

    $(tag).empty();

    var margin = {top: 20, right: 30, bottom: 80, left: 10},
	width = 1500 - margin.left - margin.right,
	height = 500 - margin.top - margin.bottom;

    var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse,
	formatDate = d3.time.format("%Y-%m-%d");

    var x = d3.time.scale()
	.range([0, width]);

    var y = d3.scale.linear()
	.range([height, 0]);

    var xAxis = d3.svg.axis()
	.scale(x)
	.orient("bottom")

    var yAxis = d3.svg.axis()
	.scale(y)
	.orient("right")

    var line = d3.svg.line()
	.x(function(d) { return x(d.date); })
	.y(function(d) { return y(d.var); });

    var svg = d3.select(tag).append("svg")
	.attr("width", width + margin.left + margin.right)
	.attr("height", height + margin.top + margin.bottom)
	.append("g")
	.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    svg.append("g")
	.attr("class", "x axis")
	.attr("transform", "translate(0," + height + ")");

    svg.append("g")
	.attr("class", "y axis")
	.attr("transform", "translate(" + width + ", 0)");

    data.forEach(function(d) {
	d.date = new Date(d.created_at);
	d.var = parseFloat(d[varname]);
	if (d.us_units == 0) {
	    if (varname == "outside_temp" || varname == "inside_temp")
		// TODO(dek): correct for pressure.
		d.var = d.var * 9 / 5. + 32.;
	    if (varname == "pressure")
		d.var = d.var / 33.86;
	}
	if (varname == "rssi")
            if (d.var < 0) d.var = -d.var;
    });

    data = data.filter(function(d) {
	if (varname == "pressure")
	    return d.var > 20 && d.var < 35;
	if (varname == "outside_temp" || varname == "inside_temp")
	    return d.var > 10 && d.var < 100;
	return true;
    });

    x.domain(d3.extent(data, function(d) { return d.created_at; }));
    y.domain(d3.extent(data, function(d) { return d.var; }));

    var dataNest = d3.nest()
	.key(function(d) { return d["station.uuid"]; })
	.entries(data);

    var color = d3.scale.category10();
    legendSpace = width/dataNest.length; // spacing for legend

    svg.append("clipPath")
    	.attr("id", "clip")
    	.append("rect")
    	.attr("x", 0)
    	.attr("y", 0)
    	.attr("width", width)
    	.attr("height", height)

    dataNest.forEach(function(d, i) {
	svg.append("path")
	    .attr("class", "line")
	    .style("stroke", function() {
                return d.color = color(d.key); })
	    .attr("d", line(d.values))
	    .attr("clip-path", "url(#clip)");

	svg.append("text")
	    .attr("x", (legendSpace/2)+i*legendSpace) // spacing
	    .attr("y", height + (margin.bottom/2)+ 25)
	    .attr("class", "legend")
	    .style("fill", function() {
		return d.color = color(d.key); })
	    .text(d.key);
    }
		    );

    var draw = function() {
	svg.select("g.x.axis").call(xAxis).selectAll("text")
            .style("text-anchor", "end")
            .attr("dx", "-.8em")
            .attr("dy", ".15em")
            .attr("transform", function(d) {
                return "rotate(-90)";
            });
	svg.select("g.y.axis").call(yAxis);

	console.log(svg.selectAll("path.line"))
	svg.selectAll("path.line")[0].forEach(function(d, i) {
	    d.attributes['d'].value=line(dataNest[i].values);
	});
    }

    var zoom = d3.behavior.zoom()
	.on("zoom", draw);

    svg.append("rect")
	.attr("class", "pane")
	.attr("width", width)
	.attr("height", height)
	.call(zoom);
    zoom.x(x);
    draw();
}

<|endoftext|>


<commit_before>'use strict'

const { parseDependenciesFromAST } = require('./dependency')
const traceCircular = require('./circular')
const {
  matchExt,
  astFromSource
} = require('./utils')

const node_path = require('path')
const fs = require('fs')
const util = require('util')
const { EventEmitter } = require('events')

const resolve = require('resolve')
const mix = require('mix2')
const make_array = require('make-array')
const async = require('async')
const set = require('set-options')

// [ref](http://nodejs.org/api/modules.html#modules_file_modules)
const EXTS_NODE = ['.js', '.json', '.node']
const DEFAULT_WALKER_OPTIONS = {
  concurrency: 50,
  extensions: EXTS_NODE,
  allowCyclic: true,
  allowAbsoluteDependency: true
  // pathFilter
  // paths:
  // moduleDirectory: 'node_modules'
}

// @param {Object} options
// - concurrency
module.exports = class Walker extends EventEmitter {

  // @param {Object} options see walker-promise.js
  constructor (entries, options, callback) {
    super()

    this.options = set(options, DEFAULT_WALKER_OPTIONS)

    this.nodes = {}
    this.callback = callback

    this._init_queue()
    this._walk(entries)
  }

  _init_queue () {
    this.queue = async.queue((task, done) => {
      // `filename` will always be an absolute path.
      let filename = task.node.id
      this._parse_file(filename, task.type, err => {
        if (!err) {
          return done()
        }

        this.queue.kill()
        this.error = err
        this._done()
      })

    }, this.options.concurrency)

    this.queue.drain = () => {
      this._done()
    }
  }

  _walk (entries) {
    make_array(entries).forEach(entry => {
      entry = node_path.resolve(entry)
      let node = this._get_node(entry)
      if (node) {
        return
      }

      this._walk_one(this._create_node(entry))
    })
  }

  _walk_one (node, type) {
    type = type || 'require'

    // All ready parsed
    if (~node.type.indexOf(type)) {
      return
    }
    node.type.push(type)

    if (node.foreign) {
      return
    }

    this.queue.push({
      node: node,
      type: type
    })
  }

  _done () {
    this.callback(this.error || null, this.nodes)
  }

  _parse_file (path, type, callback) {
    let node = this._get_node(path)

    this._get_compiled_content(node, err => {
      if (err) {
        return callback(err)
      }

      if (!node.js || type !== 'require') {
        return callback(null)
      }

      parseDependenciesFromAST(node.ast, this.options).then(
        (data) => {
          async.each(['require', 'resolve', 'async'], (type, done) => {
            this._parse_dependencies_by_type(path, data[type], type, done)
          }, callback)
        },

        (err) => {
          err.message = `${path}: ${err.message}`
          callback(err)
        }
      )
    })
  }

  _get_compiled_content (node, callback) {
    if (node.content) {
      return callback(null)
    }

    let filename = node.id
    this._read(filename, (err, content) => {
      if (err) {
        return callback(err)
      }

      this._compile(filename, content, (err, compiled) => {
        if (err) {
          return callback(err)
        }

        mix(node, compiled)
        callback(null)
      })
    })
  }

  _read (path, callback) {
    fs.readFile(path, (err, content) => {
      if (err) {
        return callback({
          code: 'ERROR_READ_FILE',
          message: 'Error reading module "' + path + '": ' + err.stack,
          data: {
            path: path,
            error: err
          }
        })
      }

      callback(null, content.toString())
    })
  }

  // Applies all compilers to process the file content
  _compile (filename, content, callback) {
    let tasks = this.options.compilers.filter(c => {
      return c.test.test(filename)

    }).reduce((prev, c) => {
      let task = (compiled, done) => {
        let ast = compiled.ast

        // if no ast, try to generate ast
        if (!ast && compiled.js) {
          try {
            ast = astFromSource(compiled.code, this.options)
          } catch (e) {
            return done(e)
          }
        }

        let options = set({}, c.options)
        if (ast) {
          options.ast
        }

        if (compiled.map) {
          options.map = compiled.map
        }

        // adds `filename` to options of each compiler
        options.filename = filename
        c.compiler(compiled.code, options, done)
      }
      prev.push(task)
      return prev

    }, [init])

    // If no registered compilers, just return
    function init (done) {
      let node = matchExt(filename, 'node')
      let json = matchExt(filename, 'json')
      let js = matchExt(filename, 'js')

      done(null, {
        code: content,
        json,
        node,
        js
      })
    }

    async.waterfall(tasks, callback)
  }

  _parse_dependencies_by_type (path, paths, type, callback) {
    let node = this._get_node(path)

    async.each(paths, (dep, done) => {
      var origin = dep

      if (dep.indexOf('/') === 0) {
        var message = {
          code: 'NOT_ALLOW_ABSOLUTE_PATH',
          message: 'Requiring an absolute path "' + dep + '" is not allowed in "' + path + '"',
          data: {
            dependency: dep,
            path: path
          }
        }

        if (!this.options.allowAbsoluteDependency) {
          return done(message)
        } else {
          this.emit('warn', message)
        }
      }

      // if (!this._is_relative_path(dep)) {
      //   // we only map top level id for now
      //   dep = this._solve_aliased_dependency(options['as'][dep], path) || dep
      // }

      // package name, not a path
      if (!this._is_relative_path(dep)) {
        return this._deal_dependency(origin, dep, node, type, done)
      }

      let resolveOptions = {
        basedir: node_path.dirname(path),
        extensions: this.options.extensions
      }

      ;[
        'pathFilter',
        'paths',
        'moduleDirectory'

      ].forEach((key) => {
        if (key in this.options) {
          resolveOptions[key] = this.options[key]
        }
      })

      resolve(dep, resolveOptions, (err, real) => {
        if (err) {
          return done({
            code: 'MODULE_NOT_FOUND',
            message: err.message,
            stack: err.stack,
            data: {
              path: dep
            }
          })
        }

        this._deal_dependency(origin, real, node, type, done)
      })
    }, callback)
  }

  // // #17
  // // If we define an `as` field in cortex.json
  // // {
  // //   "as": {
  // //     "abc": './abc.js' // ./abc.js is relative to the root directory
  // //   }
  // // }
  // // @param {String} dep path of dependency
  // // @param {String} env_path the path of the current file
  // _solve_aliased_dependency (dep, env_path) {
  //   var cwd = this.options.cwd

  //   if (!dep || !cwd || !this._is_relative_path(dep)) {
  //     return dep
  //   }

  //   dep = node_path.join(cwd, dep)
  //   dep = node_path.relative(node_path.dirname(env_path), dep)
  //     // After join and relative, dep will contains `node_path.sep` which varies from operating system,
  //     // so normalize it
  //     .replace(/\\/g, '/')

  //   if (!~dep.indexOf('..')) {
  //     // 'abc.js' -> './abc.js'
  //     dep = './' + dep
  //   }

  //   return dep
  // }

  _deal_dependency (dep, real, node, type, callback) {
    node[type][dep] = real

    let sub_node = this._get_node(real)
    let new_node = sub_node || this._create_node(real)
    this._walk_one(new_node, type)

    // We only check the node if it meets the conditions below:
    // 1. already exists: all new nodes are innocent.
    // 2. but assigned as a dependency of anothor node
    // If one of the ancestor dependents of `node` is `current`, it forms a circle.

    // If newly created node, then skip checking.
    if (!sub_node) {
      return callback(null)
    }
    sub_node = new_node

    var circular_trace
    // node -> sub_node
    if (circular_trace = traceCircular(sub_node, node, this.nodes)) {
      var message = {
        code: 'CYCLIC_DEPENDENCY',
        message: 'Cyclic dependency found: \n' + this._print_cyclic(circular_trace),
        data: {
          trace: circular_trace,
          path: real
        }
      }

      if (!this.options.allowCyclic) {
        return callback(message)
      } else {
        this.emit('warn', message)
      }
    }
    callback(null)
  }

  _get_node (path) {
    return this.nodes[path]
  }

  // Creates the node by id if not exists.
  // No fault tolerance for the sake of private method
  // @param {string} id
  // - `path` must be absolute path if is a relative module
  // - package name for foreign module
  _create_node (id) {
    return this.nodes[id] = {
      id: id,
      foreign: this._is_foreign(id),
      type: [],
      require: {},
      resolve: {},
      async: {}
    }
  }

  _is_foreign (path) {
    return !this._is_absolute_path(path)
  }

  _is_absolute_path (path) {
    return node_path.resolve(path) === path.replace(/[\/\\]+$/, '')
  }

  _is_relative_path (path) {
    // Actually, this method is called after the parser.js,
    // and all paths are parsed from require(foo),
    // so `foo` will never be affected by windows,
    // so we should not use `'.' + node_path.sep` to test these paths
    return path === '.'
      || path === '..'
      || path.indexOf('./') === 0
      || path.indexOf('../') === 0
  }

  // 1. <path>
  // 2. <path>
  //
  _print_cyclic (trace) {
    var list = trace.map(function (node, index) {
      return index + 1 + ': ' + node.id
    })
    list.pop()

    var flow = trace.map(function (node, index) {
      ++ index
      return index === 1 || index === trace.length
        ? '[1]'
        : index
    })

    return list.join('\n') + '\n\n' + flow.join(' -> ')
  }
}
<commit_msg>refractor compiler workflow<commit_after>'use strict'

const { parseDependenciesFromAST } = require('./dependency')
const traceCircular = require('./circular')
const {
  matchExt,
  astFromSource
} = require('./utils')

const node_path = require('path')
const fs = require('fs')
const util = require('util')
const { EventEmitter } = require('events')

const resolve = require('resolve')
const mix = require('mix2')
const make_array = require('make-array')
const async = require('async')
const set = require('set-options')

// [ref](http://nodejs.org/api/modules.html#modules_file_modules)
const EXTS_NODE = ['.js', '.json', '.node']
const DEFAULT_WALKER_OPTIONS = {
  concurrency: 50,
  extensions: EXTS_NODE,
  allowCyclic: true,
  allowAbsoluteDependency: true
  // pathFilter
  // paths:
  // moduleDirectory: 'node_modules'
}

// @param {Object} options
// - concurrency
module.exports = class Walker extends EventEmitter {

  // @param {Object} options see walker-promise.js
  constructor (entries, options, callback) {
    super()

    this.options = set(options, DEFAULT_WALKER_OPTIONS)

    this.nodes = {}
    this.callback = callback

    this._init_queue()
    this._walk(entries)
  }

  _init_queue () {
    this.queue = async.queue((task, done) => {
      // `filename` will always be an absolute path.
      let filename = task.node.id
      this._parse_file(filename, task.type, err => {
        if (!err) {
          return done()
        }

        this.queue.kill()
        this.error = err
        this._done()
      })

    }, this.options.concurrency)

    this.queue.drain = () => {
      this._done()
    }
  }

  _walk (entries) {
    make_array(entries).forEach(entry => {
      entry = node_path.resolve(entry)
      let node = this._get_node(entry)
      if (node) {
        return
      }

      this._walk_one(this._create_node(entry))
    })
  }

  _walk_one (node, type) {
    type = type || 'require'

    // All ready parsed
    if (~node.type.indexOf(type)) {
      return
    }
    node.type.push(type)

    if (node.foreign) {
      return
    }

    this.queue.push({
      node: node,
      type: type
    })
  }

  _done () {
    this.callback(this.error || null, this.nodes)
  }

  _parse_file (path, type, callback) {
    let node = this._get_node(path)

    this._get_compiled_content(node, err => {
      if (err) {
        return callback(err)
      }

      if (!node.js || type !== 'require') {
        return callback(null)
      }

      parseDependenciesFromAST(node.ast, this.options).then(
        (data) => {
          async.each(['require', 'resolve', 'async'], (type, done) => {
            this._parse_dependencies_by_type(path, data[type], type, done)
          }, callback)
        },

        (err) => {
          err.message = `${path}: ${err.message}`
          callback(err)
        }
      )
    })
  }

  _get_compiled_content (node, callback) {
    if (node.content) {
      return callback(null)
    }

    let filename = node.id
    this._read(filename, (err, content) => {
      if (err) {
        return callback(err)
      }

      this._compile(filename, content, (err, compiled) => {
        if (err) {
          return callback(err)
        }

        mix(node, compiled)
        callback(null)
      })
    })
  }

  _read (path, callback) {
    fs.readFile(path, (err, content) => {
      if (err) {
        return callback({
          code: 'ERROR_READ_FILE',
          message: 'Error reading module "' + path + '": ' + err.stack,
          data: {
            path: path,
            error: err
          }
        })
      }

      callback(null, content.toString())
    })
  }

  // Applies all compilers to process the file content
  _compile (filename, content, callback) {
    let matchCompiler = (compiler, compiled) => {
      let test = compiler.test

      if (util.isRegExp(test)) {
        return test.test(compiled.filename)
      }

      // if compiler.test
      if (util.isFunction(test)) {
        return test(compiled)
      }
    }

    let compilers = this.options.compilers
    let length = compilers.length
    let i = 0
    let done = (err, compiled) => {
      if (err) {
        return callback(err)
      }

      // ensure compiled.filename
      compiled.filename = compiled.filename || filename

      let ast = compiled.ast

      // if no ast, try to generate ast
      if (!ast && compiled.js) {
        try {
          ast = astFromSource(compiled.code, this.options)
          compiled.ast = ast
        } catch (e) {
          return callback(e)
        }
      }

      let compiler
      while (i < length) {
        compiler = compilers[i ++]

        if (matchCompiler(compiler, compiled)) {
          return task(done, compiler, compiled)
        }
      }

      callback(null, compiled)
    }

    let task = (done, compiler, compiled) => {
      // the first task
      if (!compiler) {
        let node = matchExt(filename, 'node')
        let json = matchExt(filename, 'json')
        let js = matchExt(filename, 'js')

        return done(null, {
          code: content,
          json,
          node,
          js
        })
      }

      let compilerOptions = set({}, compiler.options)
      if (compiled.ast) {
        compilerOptions.ast
      }

      if (compiled.map) {
        compilerOptions.map = compiled.map
      }

      // adds `filename` to options of each compiler
      compilerOptions.filename = filename
      compiler.compiler(compiled.code, compilerOptions, done)
    }

    task(done)
  }

  _parse_dependencies_by_type (path, paths, type, callback) {
    let node = this._get_node(path)

    async.each(paths, (dep, done) => {
      var origin = dep

      if (dep.indexOf('/') === 0) {
        var message = {
          code: 'NOT_ALLOW_ABSOLUTE_PATH',
          message: 'Requiring an absolute path "' + dep + '" is not allowed in "' + path + '"',
          data: {
            dependency: dep,
            path: path
          }
        }

        if (!this.options.allowAbsoluteDependency) {
          return done(message)
        } else {
          this.emit('warn', message)
        }
      }

      // if (!this._is_relative_path(dep)) {
      //   // we only map top level id for now
      //   dep = this._solve_aliased_dependency(options['as'][dep], path) || dep
      // }

      // package name, not a path
      if (!this._is_relative_path(dep)) {
        return this._deal_dependency(origin, dep, node, type, done)
      }

      let resolveOptions = {
        basedir: node_path.dirname(path),
        extensions: this.options.extensions
      }

      ;[
        'pathFilter',
        'paths',
        'moduleDirectory'

      ].forEach((key) => {
        if (key in this.options) {
          resolveOptions[key] = this.options[key]
        }
      })

      resolve(dep, resolveOptions, (err, real) => {
        if (err) {
          return done({
            code: 'MODULE_NOT_FOUND',
            message: err.message,
            stack: err.stack,
            data: {
              path: dep
            }
          })
        }

        this._deal_dependency(origin, real, node, type, done)
      })
    }, callback)
  }

  // // #17
  // // If we define an `as` field in cortex.json
  // // {
  // //   "as": {
  // //     "abc": './abc.js' // ./abc.js is relative to the root directory
  // //   }
  // // }
  // // @param {String} dep path of dependency
  // // @param {String} env_path the path of the current file
  // _solve_aliased_dependency (dep, env_path) {
  //   var cwd = this.options.cwd

  //   if (!dep || !cwd || !this._is_relative_path(dep)) {
  //     return dep
  //   }

  //   dep = node_path.join(cwd, dep)
  //   dep = node_path.relative(node_path.dirname(env_path), dep)
  //     // After join and relative, dep will contains `node_path.sep` which varies from operating system,
  //     // so normalize it
  //     .replace(/\\/g, '/')

  //   if (!~dep.indexOf('..')) {
  //     // 'abc.js' -> './abc.js'
  //     dep = './' + dep
  //   }

  //   return dep
  // }

  _deal_dependency (dep, real, node, type, callback) {
    node[type][dep] = real

    let sub_node = this._get_node(real)
    let new_node = sub_node || this._create_node(real)
    this._walk_one(new_node, type)

    // We only check the node if it meets the conditions below:
    // 1. already exists: all new nodes are innocent.
    // 2. but assigned as a dependency of anothor node
    // If one of the ancestor dependents of `node` is `current`, it forms a circle.

    // If newly created node, then skip checking.
    if (!sub_node) {
      return callback(null)
    }
    sub_node = new_node

    var circular_trace
    // node -> sub_node
    if (circular_trace = traceCircular(sub_node, node, this.nodes)) {
      var message = {
        code: 'CYCLIC_DEPENDENCY',
        message: 'Cyclic dependency found: \n' + this._print_cyclic(circular_trace),
        data: {
          trace: circular_trace,
          path: real
        }
      }

      if (!this.options.allowCyclic) {
        return callback(message)
      } else {
        this.emit('warn', message)
      }
    }
    callback(null)
  }

  _get_node (path) {
    return this.nodes[path]
  }

  // Creates the node by id if not exists.
  // No fault tolerance for the sake of private method
  // @param {string} id
  // - `path` must be absolute path if is a relative module
  // - package name for foreign module
  _create_node (id) {
    return this.nodes[id] = {
      id: id,
      foreign: this._is_foreign(id),
      type: [],
      require: {},
      resolve: {},
      async: {}
    }
  }

  _is_foreign (path) {
    return !this._is_absolute_path(path)
  }

  _is_absolute_path (path) {
    return node_path.resolve(path) === path.replace(/[\/\\]+$/, '')
  }

  _is_relative_path (path) {
    // Actually, this method is called after the parser.js,
    // and all paths are parsed from require(foo),
    // so `foo` will never be affected by windows,
    // so we should not use `'.' + node_path.sep` to test these paths
    return path === '.'
      || path === '..'
      || path.indexOf('./') === 0
      || path.indexOf('../') === 0
  }

  // 1. <path>
  // 2. <path>
  //
  _print_cyclic (trace) {
    var list = trace.map(function (node, index) {
      return index + 1 + ': ' + node.id
    })
    list.pop()

    var flow = trace.map(function (node, index) {
      ++ index
      return index === 1 || index === trace.length
        ? '[1]'
        : index
    })

    return list.join('\n') + '\n\n' + flow.join(' -> ')
  }
}
<|endoftext|>


<commit_before>(function($) {
	"use strict";

	/**
	 * This namespace provides the models and collections for working with BC web applications and their items.
	 *
	 * @namespace BCAPI.Models.WebApp
	 * @memberOf BCAPI.Models
	 */
	BCAPI.Models.WebApp = {};

    /**
     * This class provides the model for interacting with web apps.
     *
     * ## Load webapps list
     *
     * ```javascript
     * var apps = new BCAPI.Models.WebApp.AppCollection();
     * apps.fetch({
     *		success: function(webAppItems) {
	 * 			// handle success
	 * 		},
	 * 		error: function(webAppItems, xhr) {
	 * 			// handle errors
	 * 		}
	 * });
     *
     * apps.each(function(webApp) {
	 * 		// display logic
	 * });
     * ```
     *
     * ## Create webapp
     *
     * ```javascript
     * var app = new BCAPI.Models.WebApp.App({
	 * 		"name": "Test app"
	 * });
     *
     * var response = app.save({
	 * 		success: function(webAppItem) {
	 * 			// handle success
	 * 		},
	 * 		error: function(webAppItem, xhr) {
	 * 			// handle errors
	 * 		}
	 * });
     * ```
     *
     * If you want to refresh collections which rely on Item model please refresh those collections.
     *
     * ## Remove app
     *
     * ```javascript
     * var app = new BCAPI.Models.WebApp.Item({name: "Test app"});
     * item.destroy({
	 * 	success: function(webAppItem, response) {
	 * 		// handle success here.
	 *  },
	 *  error: function(webAppItem, xhr, options) {
	 * 		// handle error scenario.
	 *  }
	 * });
     * ```
     *
     * ## Supported attributes
     *
     * var app = new BCAPI.Models.WebApp.Item({
	 *	templateId: -1,
     *  uploadFolder: "images",
     *  requiresApproval: true,
     *  allowFileUpload: true,
     *  customerCanAdd: false,
     *  customerCanDelete: false,
     *  customerCanEdit: false,
     *  anyoneCanEdit: false,
     *  requiresPayment: false,
     *  validDays: -1, // never expire
     *  roleId: 0,
     *  hasAddress: false,
     *  disableDetailPages: false,
     *  locationEnabled: false
     * });
     *
     * @class
     */
	BCAPI.Models.WebApp.App = BCAPI.Models.Model.extend({
        idAttribute: 'name',

        /**
         * Set tot true if you want to save or delete an existing item before fetching it
         *
         * @type {boolean}
         * @memberOf WebApp
         */
        isNotNew: null,

        isNew: function() {
            return this.isNotNew ? false : !this.get('id');
        },

        endpoint: function() {
            return '/api/v2/admin/sites/current/webapps';
        },

        fetch: function() {
            this.isNotNew = true;
            return Backbone.Model.prototype.fetch.apply(this, arguments);
        },

        destroy: function() {
            this.isNotNew = true;
            return Backbone.Model.prototype.destroy.apply(this, arguments);
        }
    });

    /**
     * This class provides a collection that can be used to list all webapps from a site. For
     * more information about a webapp see {@link BCAPI.Models.WebApp.App}.
     *
     * @name AppCollection
     * @class
     * @constructor
     * @memberOf BCAPI.Models.WebApp
     * @example
     * // fetch all available webapps with custom fields structure in place.
     * var appCollection = new BCAPI.Models.WebApp.AppCollection();
     *
     * appCollection.fetch({fetchFields: true,
     *  success: function(webapps) {
     *  	webapps.each(function(webapp) {
     *  		// here you also have access to webapp.fields.
     *  	});
     *  }
     * });
     *
     * @example
     * // fetch all available webapps without custom fields structure in place.
     * var appCollection = new BCAPI.Models.WebApp.AppCollection();
     *
     * appCollection.fetch({fetchFields: false,
     *  success: function(webapps) {
     *  	webapps.each(function(webapp) {
     *  		// here webapp.fields is an empty array.
     *  	});
     *  }
     */
    BCAPI.Models.WebApp.AppCollection = BCAPI.Models.Collection.extend({
        model: BCAPI.Models.WebApp.App,
        fetch: function(options) {
        	options = options || {};

        	oldSuccess = options.success;

        	options.success = function(collection, xhr, options) {
        		var currFetchedFields = 0;

        		collection.each(function(webapp) {
        			var fieldsCollection = new BCAPI.Models.WebApp.CustomFieldCollection(webapp.get("name"));

        			webapp.set({"fields": []});

        			if(!options.fetchFields) {
        				return oldSuccess(collection, xhr, options);
        			}

        			fieldsCollection.fetch({
        				success: function(fields) {
        					fields.each(function(field) {
        						webapp.get("fields").push(field);
        					});

        					if(++currFetchedFields == fieldsCollection.length) {
        						oldSuccess(collection, xhr, options);
        					}
        				}
        			});
        		});
        	};

        	return BCAPI.Models.Collection.prototype.fetch.call(this, options);
        },
    	/**
    	 * We override this method in order to transform each returned item into a strong typed
    	 * {@link BCAPI.Models.WebApp.App} models.
    	 *
    	 * @method
    	 * @instance
    	 * @param {Object} response The JSON response received from Web apps api.
    	 * @returns A list of web app items.
    	 * @memberOf BCAPI.Models.WebApp.AppCollection
         */
        parse: function(response) {
        	var webapps = [],
        		self = this;

        	response = BCAPI.Models.Collection.prototype.parse.call(this, response);

        	_.each(response, function(webapp) {
        		webapps.push(self.model(webapp));
        	});

        	return webapps;
        }
    });
})(jQuery);<commit_msg>Fixed scope for local variable<commit_after>(function($) {
	"use strict";

	/**
	 * This namespace provides the models and collections for working with BC web applications and their items.
	 *
	 * @namespace BCAPI.Models.WebApp
	 * @memberOf BCAPI.Models
	 */
	BCAPI.Models.WebApp = {};

    /**
     * This class provides the model for interacting with web apps.
     *
     * ## Load webapps list
     *
     * ```javascript
     * var apps = new BCAPI.Models.WebApp.AppCollection();
     * apps.fetch({
     *		success: function(webAppItems) {
	 * 			// handle success
	 * 		},
	 * 		error: function(webAppItems, xhr) {
	 * 			// handle errors
	 * 		}
	 * });
     *
     * apps.each(function(webApp) {
	 * 		// display logic
	 * });
     * ```
     *
     * ## Create webapp
     *
     * ```javascript
     * var app = new BCAPI.Models.WebApp.App({
	 * 		"name": "Test app"
	 * });
     *
     * var response = app.save({
	 * 		success: function(webAppItem) {
	 * 			// handle success
	 * 		},
	 * 		error: function(webAppItem, xhr) {
	 * 			// handle errors
	 * 		}
	 * });
     * ```
     *
     * If you want to refresh collections which rely on Item model please refresh those collections.
     *
     * ## Remove app
     *
     * ```javascript
     * var app = new BCAPI.Models.WebApp.Item({name: "Test app"});
     * item.destroy({
	 * 	success: function(webAppItem, response) {
	 * 		// handle success here.
	 *  },
	 *  error: function(webAppItem, xhr, options) {
	 * 		// handle error scenario.
	 *  }
	 * });
     * ```
     *
     * ## Supported attributes
     *
     * var app = new BCAPI.Models.WebApp.Item({
	 *	templateId: -1,
     *  uploadFolder: "images",
     *  requiresApproval: true,
     *  allowFileUpload: true,
     *  customerCanAdd: false,
     *  customerCanDelete: false,
     *  customerCanEdit: false,
     *  anyoneCanEdit: false,
     *  requiresPayment: false,
     *  validDays: -1, // never expire
     *  roleId: 0,
     *  hasAddress: false,
     *  disableDetailPages: false,
     *  locationEnabled: false
     * });
     *
     * @class
     */
	BCAPI.Models.WebApp.App = BCAPI.Models.Model.extend({
        idAttribute: 'name',

        /**
         * Set tot true if you want to save or delete an existing item before fetching it
         *
         * @type {boolean}
         * @memberOf WebApp
         */
        isNotNew: null,

        isNew: function() {
            return this.isNotNew ? false : !this.get('id');
        },

        endpoint: function() {
            return '/api/v2/admin/sites/current/webapps';
        },

        fetch: function() {
            this.isNotNew = true;
            return Backbone.Model.prototype.fetch.apply(this, arguments);
        },

        destroy: function() {
            this.isNotNew = true;
            return Backbone.Model.prototype.destroy.apply(this, arguments);
        }
    });

    /**
     * This class provides a collection that can be used to list all webapps from a site. For
     * more information about a webapp see {@link BCAPI.Models.WebApp.App}.
     *
     * @name AppCollection
     * @class
     * @constructor
     * @memberOf BCAPI.Models.WebApp
     * @example
     * // fetch all available webapps with custom fields structure in place.
     * var appCollection = new BCAPI.Models.WebApp.AppCollection();
     *
     * appCollection.fetch({fetchFields: true,
     *  success: function(webapps) {
     *  	webapps.each(function(webapp) {
     *  		// here you also have access to webapp.fields.
     *  	});
     *  }
     * });
     *
     * @example
     * // fetch all available webapps without custom fields structure in place.
     * var appCollection = new BCAPI.Models.WebApp.AppCollection();
     *
     * appCollection.fetch({fetchFields: false,
     *  success: function(webapps) {
     *  	webapps.each(function(webapp) {
     *  		// here webapp.fields is an empty array.
     *  	});
     *  }
     */
    BCAPI.Models.WebApp.AppCollection = BCAPI.Models.Collection.extend({
        model: BCAPI.Models.WebApp.App,
        fetch: function(options) {
        	options = options || {};

        	var oldSuccess = options.success;

        	options.success = function(collection, xhr, options) {
        		var currFetchedFields = 0;

        		collection.each(function(webapp) {
        			var fieldsCollection = new BCAPI.Models.WebApp.CustomFieldCollection(webapp.get("name"));

        			webapp.set({"fields": []});

        			if(!options.fetchFields) {
        				return oldSuccess(collection, xhr, options);
        			}

        			fieldsCollection.fetch({
        				success: function(fields) {
        					fields.each(function(field) {
        						webapp.get("fields").push(field);
        					});

        					if(++currFetchedFields == fieldsCollection.length) {
        						oldSuccess(collection, xhr, options);
        					}
        				}
        			});
        		});
        	};

        	return BCAPI.Models.Collection.prototype.fetch.call(this, options);
        },
    	/**
    	 * We override this method in order to transform each returned item into a strong typed
    	 * {@link BCAPI.Models.WebApp.App} models.
    	 *
    	 * @method
    	 * @instance
    	 * @param {Object} response The JSON response received from Web apps api.
    	 * @returns A list of web app items.
    	 * @memberOf BCAPI.Models.WebApp.AppCollection
         */
        parse: function(response) {
        	var webapps = [],
        		self = this;

        	response = BCAPI.Models.Collection.prototype.parse.call(this, response);

        	_.each(response, function(webapp) {
        		webapps.push(self.model(webapp));
        	});

        	return webapps;
        }
    });
})(jQuery);<|endoftext|>


<commit_before>'use strict';

var $ = require('jquery');
var instantsearch = require('instantsearch.js');

function uniq(a) {
  return a.reduce(function(p, c) {
    if (p.indexOf(c) < 0) {
      p.push(c);
    }
    return p;
  }, []);
}

function slider(options) {
  if (!options.attributeName || !options.container) {
    throw new Error('ion.rangeSlider: usage: ionRangeSlider({container, attributeName, ionRangeSliderOptions})');
  }
  var $container = $(options.container);
  if ($container.length === 0) {
    throw new Error('ion.rangeSlider: cannot select \'' + options.container + '\'');
  }
  if (!$.fn.ionRangeSlider) {
    throw new Error('The ion.rangeSlider jQuery plugin is missing. Did you include ion.rangeSlider.min.js?');
  }

  var lowerBoundAttributeName = options.attributeName.lowerBound || options.attributeName;
  var upperBoundAttributeName = options.attributeName.upperBound || options.attributeName;
  var ionRangeSliderOptions = options.ionRangeSliderOptions || {};

  var needFacet = typeof options.min === 'undefined' || typeof options.max === 'undefined';
  var ionRangeSlider;

  return {
    getConfiguration: function() {
      return needFacet ? {
        disjunctiveFacets: uniq([lowerBoundAttributeName, upperBoundAttributeName])
      } : {};
    },

    init: function(args) {
      var helper = args.helper;

      if (typeof options.min !== 'undefined') {
        helper.addNumericRefinement(lowerBoundAttributeName, '>=', options.min);
      }
      if (typeof options.max !== 'undefined') {
        helper.addNumericRefinement(upperBoundAttributeName, '<=', options.max);
      }
    },

    render: function(args) {
      var helper = args.helper;

      var from = helper.state.getNumericRefinement(lowerBoundAttributeName, '>=');
      from = from && from[0];

      var to = helper.state.getNumericRefinement(upperBoundAttributeName, '<=');
      to = to && to[0];

      var min;
      var max;
      if (needFacet) {
        min = args.results.getFacetStats(lowerBoundAttributeName) ?
          args.results.getFacetStats(lowerBoundAttributeName).min : 0;
        max = args.results.getFacetStats(upperBoundAttributeName) ?
          args.results.getFacetStats(upperBoundAttributeName).max : 0;
      } else {
        min = options.min;
        max = options.max;
      }
      from = from || min;
      to = to || max;

      var sliderOptions = {
        type: 'double',
        grid: true,
        min: min,
        max: max,
        from: from,
        to: to,
        onFinish: function(data) {
          if (data.from !== from) {
            helper.removeNumericRefinement(lowerBoundAttributeName, '>=');
            helper.addNumericRefinement(lowerBoundAttributeName, '>=', data.from);
            helper.search();
          }
          if (data.to !== to) {
            helper.removeNumericRefinement(upperBoundAttributeName, '<=');
            helper.addNumericRefinement(upperBoundAttributeName, '<=', data.to);
            helper.search();
          }
        }
      };

      sliderOptions = $.extend({}, sliderOptions, ionRangeSliderOptions);
      $container.show();
      if (ionRangeSlider) {
        ionRangeSlider.update(sliderOptions);
      } else {
        $container.ionRangeSlider(sliderOptions);
        ionRangeSlider = $container.data('ionRangeSlider');
      }
    }
  };
}

module.exports = instantsearch.widgets.ionRangeSlider = slider;
<commit_msg>Rewrite that to fix tests and decrease the number of call to getFacetStats<commit_after>'use strict';

var $ = require('jquery');
var instantsearch = require('instantsearch.js');

function uniq(a) {
  return a.reduce(function(p, c) {
    if (p.indexOf(c) < 0) {
      p.push(c);
    }
    return p;
  }, []);
}

function slider(options) {
  if (!options.attributeName || !options.container) {
    throw new Error('ion.rangeSlider: usage: ionRangeSlider({container, attributeName, ionRangeSliderOptions})');
  }
  var $container = $(options.container);
  if ($container.length === 0) {
    throw new Error('ion.rangeSlider: cannot select \'' + options.container + '\'');
  }
  if (!$.fn.ionRangeSlider) {
    throw new Error('The ion.rangeSlider jQuery plugin is missing. Did you include ion.rangeSlider.min.js?');
  }

  var lowerBoundAttributeName = options.attributeName.lowerBound || options.attributeName;
  var upperBoundAttributeName = options.attributeName.upperBound || options.attributeName;
  var ionRangeSliderOptions = options.ionRangeSliderOptions || {};

  var needFacet = typeof options.min === 'undefined' || typeof options.max === 'undefined';
  var ionRangeSlider;

  return {
    getConfiguration: function() {
      return needFacet ? {
        disjunctiveFacets: uniq([lowerBoundAttributeName, upperBoundAttributeName])
      } : {};
    },

    init: function(args) {
      var helper = args.helper;

      if (typeof options.min !== 'undefined') {
        helper.addNumericRefinement(lowerBoundAttributeName, '>=', options.min);
      }
      if (typeof options.max !== 'undefined') {
        helper.addNumericRefinement(upperBoundAttributeName, '<=', options.max);
      }
    },

    render: function(args) {
      var helper = args.helper;

      var from = helper.state.getNumericRefinement(lowerBoundAttributeName, '>=');
      from = from && from[0];

      var to = helper.state.getNumericRefinement(upperBoundAttributeName, '<=');
      to = to && to[0];

      var min;
      var max;
      if (needFacet) {
        var lowerFacetStats = args.results.getFacetStats(lowerBoundAttributeName);
        var upperFacetStats = args.results.getFacetStats(upperBoundAttributeName);
        min = lowerFacetStats ? lowerFacetStats.min : 0;
        max = upperFacetStats ? upperFacetStats.max : 0;
      } else {
        min = options.min;
        max = options.max;
      }
      from = from || min;
      to = to || max;

      var sliderOptions = {
        type: 'double',
        grid: true,
        min: min,
        max: max,
        from: from,
        to: to,
        onFinish: function(data) {
          if (data.from !== from) {
            helper.removeNumericRefinement(lowerBoundAttributeName, '>=');
            helper.addNumericRefinement(lowerBoundAttributeName, '>=', data.from);
            helper.search();
          }
          if (data.to !== to) {
            helper.removeNumericRefinement(upperBoundAttributeName, '<=');
            helper.addNumericRefinement(upperBoundAttributeName, '<=', data.to);
            helper.search();
          }
        }
      };

      sliderOptions = $.extend({}, sliderOptions, ionRangeSliderOptions);
      $container.show();
      if (ionRangeSlider) {
        ionRangeSlider.update(sliderOptions);
      } else {
        $container.ionRangeSlider(sliderOptions);
        ionRangeSlider = $container.data('ionRangeSlider');
      }
    }
  };
}

module.exports = instantsearch.widgets.ionRangeSlider = slider;
<|endoftext|>