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

/**
 * General constants for Fraction Matcher.  While constants, they are extended by MixedNumberConstants, so they should be instantiated
 * like instance objects.  See https://github.com/phetsims/fraction-matcher/issues/43
 *
 * @author Andrey Zelenkov (Mlearner)
 */
define( function( require ) {
  'use strict';

  // modules
  var fractionMatcher = require( 'FRACTION_MATCHER/fractionMatcher' );
  var FillType = require( 'FRACTION_MATCHER/model/FillType' );
  var Fraction = require( 'PHETCOMMON/model/Fraction' );

  //colors
  var COLORS = {
    LIGHT_GREEN: 'rgb(140,198,63)',
    LIGHT_BLUE: '#6acef5',//to match the PhET Logo
    LIGHT_RED: 'rgb(233,69,69)',
    LIGHT_PINK: 'rgb(255,175,175)',
    ORANGE: 'rgb(255,200,0)',
    YELLOW: '#f2e916',//to match the PhET Logo
    GREEN: 'rgb(0,255,0)',
    PINK: 'rgb(255,0,255)'
  };
  COLORS.CIRCLE_COLOR = COLORS.LIGHT_GREEN;
  COLORS.HORIZONTAL_SLICE_COLOR = COLORS.LIGHT_RED;
  COLORS.VERTICAL_SLICE_COLOR = COLORS.LIGHT_BLUE;
  COLORS.NUMBER_LINE = COLORS.LIGHT_GREEN;

  //list of all possible shapes
  var SHAPES = [ 'PIES', 'HORIZONTAL_BARS', 'VERTICAL_BARS', 'PLUSES', 'GRID', 'PYRAMID', 'POLYGON', 'TETRIS', 'FLOWER', 'LETTER_L_SHAPES', 'INTERLEAVED_L_SHAPES', 'RING_OF_HEXAGONS', 'NINJA_STAR' ];

  function Constants() {
    // color constants
    this.COLORS = COLORS;

    // shapes type constants
    this.SHAPES = SHAPES;

    this.LEVEL_DESCRIPTION = [
    /**
     * Level 1
     * No mixed numbers
     * Only “exact” matches will be present. So for instance if there is a 3/6  and a pie with 6 divisions and 3 shaded slices, there will not be a ½  present .  In other words, the numerical representation on this level will exactly match the virtual manipulative.
     * Only numbers/representations ≦ 1 possible on this level
     * “Easy” shapes on this level (not some of the more abstract representations)
     */
      {
        fractions: [
          new Fraction( 1, 2 ),
          new Fraction( 1, 3 ),
          new Fraction( 2, 3 ),
          new Fraction( 1, 4 ),
          new Fraction( 3, 4 ),
          new Fraction( 1, 1 )
        ],
        numericScaleFactors: [ 1 ],
        fillType: [ FillType.SEQUENTIAL ],
        shapes: SHAPES.slice( 0, 3 )
      },
    /**
     * Level 2
     * Reduced fractions possible on this level. So, for instance 3/6 and ½  could both be present.  Or a virtual representation of 3/6 could have the numerical of ½ be its only possible match
     * Still only numbers/representations ≦ 1 possible
     * More shapes can be introduced
     */
      {
        fractions: [
          new Fraction( 1, 2 ),
          new Fraction( 1, 3 ),
          new Fraction( 2, 3 ),
          new Fraction( 2, 4 ),
          new Fraction( 3, 4 ),
          new Fraction( 2, 6 ),
          new Fraction( 3, 6 )
        ],
        numericScaleFactors: [ 1 ],
        fillType: [ FillType.SEQUENTIAL ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 3:
     * Reduced fractions possible on this level. So, for instance 3/6 and ½  could both be present.  Or a virtual representation of 3/6 could have the numerical of ½ be its only possible match
     * Still only numbers/representations ≦ 1 possible
     * More shapes can be introduced
     */
      {
        fractions: [
          new Fraction( 3, 2 ),
          new Fraction( 4, 3 ),
          new Fraction( 5, 4 ),
          new Fraction( 6, 4 ),
          new Fraction( 7, 4 ),
          new Fraction( 4, 5 ),
          new Fraction( 2, 6 ),
          new Fraction( 3, 6 ),
          new Fraction( 4, 6 ),
          new Fraction( 5, 6 ),
          new Fraction( 7, 6 ),
          new Fraction( 3, 8 ),
          new Fraction( 4, 8 ),
          new Fraction( 5, 8 ),
          new Fraction( 6, 8 ),
          new Fraction( 7, 8 )
        ],
        numericScaleFactors: [ 1 ],
        fillType: [ FillType.SEQUENTIAL ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 4:
     * All representations possible as well as complicated mixed/improper numbers
     */
      {
        fractions: [
          new Fraction( 13, 7 ),
          new Fraction( 13, 7 ),
          new Fraction( 6, 3 ),
          new Fraction( 9, 5 ),
          new Fraction( 9, 7 ),
          new Fraction( 9, 8 ),
          new Fraction( 14, 8 ),
          new Fraction( 2, 9 ),
          new Fraction( 3, 9 ),
          new Fraction( 4, 9 ),
          new Fraction( 6, 9 ),
          new Fraction( 8, 9 )
        ],
        numericScaleFactors: [ 1, 2 ],
        fillType: [ FillType.SEQUENTIAL ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 5:
     * All representations possible as well as complicated mixed/improper numbers.  Same fractions as level 4 but different representations.
     */
      {
        fractions: [
          new Fraction( 13, 7 ),
          new Fraction( 13, 7 ),
          new Fraction( 6, 3 ),
          new Fraction( 9, 5 ),
          new Fraction( 9, 7 ),
          new Fraction( 9, 8 ),
          new Fraction( 14, 8 ),
          new Fraction( 2, 9 ),
          new Fraction( 3, 9 ),
          new Fraction( 4, 9 ),
          new Fraction( 6, 9 ),
          new Fraction( 8, 9 )
        ],
        numericScaleFactors: [ 1, 2, 3 ],
        fillType: [ FillType.SEQUENTIAL, FillType.MIXED ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 6:
     * All representations possible as well as complicated mixed/improper numbers
     */
      {
        fractions: [
          new Fraction( 6, 5 ),
          new Fraction( 7, 5 ),
          new Fraction( 8, 5 ),
          new Fraction( 9, 5 ),
          new Fraction( 7, 6 ),
          new Fraction( 8, 6 ),
          new Fraction( 9, 6 ),
          new Fraction( 9, 7 ),
          new Fraction( 10, 7 ),
          new Fraction( 13, 7 ),
          new Fraction( 9, 8 ),
          new Fraction( 10, 8 ),
          new Fraction( 11, 8 ),
          new Fraction( 14, 8 ),
          new Fraction( 4, 9 ),
          new Fraction( 6, 9 ),
          new Fraction( 8, 9 ),
          new Fraction( 10, 9 ),
          new Fraction( 11, 9 )
        ],
        numericScaleFactors: [ 1, 4, 5 ],
        fillType: [ FillType.SEQUENTIAL, FillType.RANDOM ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 7:
     * All representations possible as well as complicated mixed/improper numbers
     */
      {
        fractions: [
          new Fraction( 3, 2 ),
          new Fraction( 4, 3 ),
          new Fraction( 5, 3 ),
          new Fraction( 5, 4 ),
          new Fraction( 7, 4 ),
          new Fraction( 6, 5 ),
          new Fraction( 7, 5 ),
          new Fraction( 8, 5 ),
          new Fraction( 9, 5 ),
          new Fraction( 7, 6 ),
          new Fraction( 11, 6 )
        ],
        numericScaleFactors: [ 1, 6, 7 ],
        fillType: [ FillType.SEQUENTIAL, FillType.RANDOM ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 8:
     * All representations possible as well as complicated mixed/improper numbers
     */
      {
        fractions: [
          new Fraction( 8, 7 ),
          new Fraction( 9, 7 ),
          new Fraction( 10, 7 ),
          new Fraction( 11, 7 ),
          new Fraction( 12, 7 ),
          new Fraction( 13, 7 ),
          new Fraction( 9, 8 ),
          new Fraction( 10, 8 ),
          new Fraction( 11, 8 ),
          new Fraction( 12, 8 ),
          new Fraction( 13, 8 ),
          new Fraction( 14, 8 ),
          new Fraction( 15, 8 ),
          new Fraction( 10, 9 ),
          new Fraction( 11, 9 ),
          new Fraction( 12, 9 ),
          new Fraction( 13, 9 ),
          new Fraction( 14, 9 ),
          new Fraction( 15, 9 ),
          new Fraction( 16, 9 ),
          new Fraction( 17, 9 )
        ],
        numericScaleFactors: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
        fillType: [ FillType.SEQUENTIAL, FillType.RANDOM ],
        shapes: SHAPES.slice( 0 )
      }
    ];
  }

  fractionMatcher.register( 'Constants', Constants );

  return Constants;
} );<commit_msg>Factored out PhET logo yellow, see https://github.com/phetsims/scenery-phet/issues/270<commit_after>// Copyright 2013-2015, University of Colorado Boulder

/**
 * General constants for Fraction Matcher.  While constants, they are extended by MixedNumberConstants, so they should be instantiated
 * like instance objects.  See https://github.com/phetsims/fraction-matcher/issues/43
 *
 * @author Andrey Zelenkov (Mlearner)
 */
define( function( require ) {
  'use strict';

  // modules
  var fractionMatcher = require( 'FRACTION_MATCHER/fractionMatcher' );
  var FillType = require( 'FRACTION_MATCHER/model/FillType' );
  var Fraction = require( 'PHETCOMMON/model/Fraction' );
  var PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' );

  //colors
  var COLORS = {
    LIGHT_GREEN: 'rgb(140,198,63)',
    LIGHT_BLUE: '#6acef5',//to match the PhET Logo
    LIGHT_RED: 'rgb(233,69,69)',
    LIGHT_PINK: 'rgb(255,175,175)',
    ORANGE: 'rgb(255,200,0)',
    YELLOW: PhetColorScheme.PHET_YELLOW,//to match the PhET Logo
    GREEN: 'rgb(0,255,0)',
    PINK: 'rgb(255,0,255)'
  };
  COLORS.CIRCLE_COLOR = COLORS.LIGHT_GREEN;
  COLORS.HORIZONTAL_SLICE_COLOR = COLORS.LIGHT_RED;
  COLORS.VERTICAL_SLICE_COLOR = COLORS.LIGHT_BLUE;
  COLORS.NUMBER_LINE = COLORS.LIGHT_GREEN;

  //list of all possible shapes
  var SHAPES = [ 'PIES', 'HORIZONTAL_BARS', 'VERTICAL_BARS', 'PLUSES', 'GRID', 'PYRAMID', 'POLYGON', 'TETRIS', 'FLOWER', 'LETTER_L_SHAPES', 'INTERLEAVED_L_SHAPES', 'RING_OF_HEXAGONS', 'NINJA_STAR' ];

  function Constants() {
    // color constants
    this.COLORS = COLORS;

    // shapes type constants
    this.SHAPES = SHAPES;

    this.LEVEL_DESCRIPTION = [
    /**
     * Level 1
     * No mixed numbers
     * Only “exact” matches will be present. So for instance if there is a 3/6  and a pie with 6 divisions and 3 shaded slices, there will not be a ½  present .  In other words, the numerical representation on this level will exactly match the virtual manipulative.
     * Only numbers/representations ≦ 1 possible on this level
     * “Easy” shapes on this level (not some of the more abstract representations)
     */
      {
        fractions: [
          new Fraction( 1, 2 ),
          new Fraction( 1, 3 ),
          new Fraction( 2, 3 ),
          new Fraction( 1, 4 ),
          new Fraction( 3, 4 ),
          new Fraction( 1, 1 )
        ],
        numericScaleFactors: [ 1 ],
        fillType: [ FillType.SEQUENTIAL ],
        shapes: SHAPES.slice( 0, 3 )
      },
    /**
     * Level 2
     * Reduced fractions possible on this level. So, for instance 3/6 and ½  could both be present.  Or a virtual representation of 3/6 could have the numerical of ½ be its only possible match
     * Still only numbers/representations ≦ 1 possible
     * More shapes can be introduced
     */
      {
        fractions: [
          new Fraction( 1, 2 ),
          new Fraction( 1, 3 ),
          new Fraction( 2, 3 ),
          new Fraction( 2, 4 ),
          new Fraction( 3, 4 ),
          new Fraction( 2, 6 ),
          new Fraction( 3, 6 )
        ],
        numericScaleFactors: [ 1 ],
        fillType: [ FillType.SEQUENTIAL ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 3:
     * Reduced fractions possible on this level. So, for instance 3/6 and ½  could both be present.  Or a virtual representation of 3/6 could have the numerical of ½ be its only possible match
     * Still only numbers/representations ≦ 1 possible
     * More shapes can be introduced
     */
      {
        fractions: [
          new Fraction( 3, 2 ),
          new Fraction( 4, 3 ),
          new Fraction( 5, 4 ),
          new Fraction( 6, 4 ),
          new Fraction( 7, 4 ),
          new Fraction( 4, 5 ),
          new Fraction( 2, 6 ),
          new Fraction( 3, 6 ),
          new Fraction( 4, 6 ),
          new Fraction( 5, 6 ),
          new Fraction( 7, 6 ),
          new Fraction( 3, 8 ),
          new Fraction( 4, 8 ),
          new Fraction( 5, 8 ),
          new Fraction( 6, 8 ),
          new Fraction( 7, 8 )
        ],
        numericScaleFactors: [ 1 ],
        fillType: [ FillType.SEQUENTIAL ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 4:
     * All representations possible as well as complicated mixed/improper numbers
     */
      {
        fractions: [
          new Fraction( 13, 7 ),
          new Fraction( 13, 7 ),
          new Fraction( 6, 3 ),
          new Fraction( 9, 5 ),
          new Fraction( 9, 7 ),
          new Fraction( 9, 8 ),
          new Fraction( 14, 8 ),
          new Fraction( 2, 9 ),
          new Fraction( 3, 9 ),
          new Fraction( 4, 9 ),
          new Fraction( 6, 9 ),
          new Fraction( 8, 9 )
        ],
        numericScaleFactors: [ 1, 2 ],
        fillType: [ FillType.SEQUENTIAL ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 5:
     * All representations possible as well as complicated mixed/improper numbers.  Same fractions as level 4 but different representations.
     */
      {
        fractions: [
          new Fraction( 13, 7 ),
          new Fraction( 13, 7 ),
          new Fraction( 6, 3 ),
          new Fraction( 9, 5 ),
          new Fraction( 9, 7 ),
          new Fraction( 9, 8 ),
          new Fraction( 14, 8 ),
          new Fraction( 2, 9 ),
          new Fraction( 3, 9 ),
          new Fraction( 4, 9 ),
          new Fraction( 6, 9 ),
          new Fraction( 8, 9 )
        ],
        numericScaleFactors: [ 1, 2, 3 ],
        fillType: [ FillType.SEQUENTIAL, FillType.MIXED ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 6:
     * All representations possible as well as complicated mixed/improper numbers
     */
      {
        fractions: [
          new Fraction( 6, 5 ),
          new Fraction( 7, 5 ),
          new Fraction( 8, 5 ),
          new Fraction( 9, 5 ),
          new Fraction( 7, 6 ),
          new Fraction( 8, 6 ),
          new Fraction( 9, 6 ),
          new Fraction( 9, 7 ),
          new Fraction( 10, 7 ),
          new Fraction( 13, 7 ),
          new Fraction( 9, 8 ),
          new Fraction( 10, 8 ),
          new Fraction( 11, 8 ),
          new Fraction( 14, 8 ),
          new Fraction( 4, 9 ),
          new Fraction( 6, 9 ),
          new Fraction( 8, 9 ),
          new Fraction( 10, 9 ),
          new Fraction( 11, 9 )
        ],
        numericScaleFactors: [ 1, 4, 5 ],
        fillType: [ FillType.SEQUENTIAL, FillType.RANDOM ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 7:
     * All representations possible as well as complicated mixed/improper numbers
     */
      {
        fractions: [
          new Fraction( 3, 2 ),
          new Fraction( 4, 3 ),
          new Fraction( 5, 3 ),
          new Fraction( 5, 4 ),
          new Fraction( 7, 4 ),
          new Fraction( 6, 5 ),
          new Fraction( 7, 5 ),
          new Fraction( 8, 5 ),
          new Fraction( 9, 5 ),
          new Fraction( 7, 6 ),
          new Fraction( 11, 6 )
        ],
        numericScaleFactors: [ 1, 6, 7 ],
        fillType: [ FillType.SEQUENTIAL, FillType.RANDOM ],
        shapes: SHAPES.slice( 0 )
      },
    /**
     * Level 8:
     * All representations possible as well as complicated mixed/improper numbers
     */
      {
        fractions: [
          new Fraction( 8, 7 ),
          new Fraction( 9, 7 ),
          new Fraction( 10, 7 ),
          new Fraction( 11, 7 ),
          new Fraction( 12, 7 ),
          new Fraction( 13, 7 ),
          new Fraction( 9, 8 ),
          new Fraction( 10, 8 ),
          new Fraction( 11, 8 ),
          new Fraction( 12, 8 ),
          new Fraction( 13, 8 ),
          new Fraction( 14, 8 ),
          new Fraction( 15, 8 ),
          new Fraction( 10, 9 ),
          new Fraction( 11, 9 ),
          new Fraction( 12, 9 ),
          new Fraction( 13, 9 ),
          new Fraction( 14, 9 ),
          new Fraction( 15, 9 ),
          new Fraction( 16, 9 ),
          new Fraction( 17, 9 )
        ],
        numericScaleFactors: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
        fillType: [ FillType.SEQUENTIAL, FillType.RANDOM ],
        shapes: SHAPES.slice( 0 )
      }
    ];
  }

  fractionMatcher.register( 'Constants', Constants );

  return Constants;
} );<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

import {EventEmitter} from "events";

/*
 * The browser-specific function to request an animation frame.
 */
var requestAnimationFrame =
        window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame;

var perf = window.performance && window.performance.now ? window.performance : Date;

/*
 * The default time step.
 * For browsers that do not support animation frames.
 */
var TIME_STEP_MS = 40;

/*
 * The handle provided by setInterval().
 * For browsers that do not support animation frames.
 */
var timer;

// The number of running animators.
var runningAnimators = 0;

/*
 * The list of managed animators.
 */
var animatorList = [];

/*
 * The main animation loop.
 *
 * This function is called periodically and triggers the
 * animation steps in all running animators.
 *
 * If all animators are removed from the list of running animators,
 * then the periodic calling is disabled.
 *
 * This function can be called either through requestAnimationFrame()
 * or through setInterval().
 */
function loop() {
    if (runningAnimators > 0) {
        // If there is at least one animator,
        // and if the browser provides animation frames,
        // schedule this function to be called again in the next frame.
        if (requestAnimationFrame) {
            requestAnimationFrame(loop);
        }

        // Step all animators. We iterate over a copy of the animator list
        // in case the step() method removes an animator from the list.
        animatorList.forEach(animator => {
            if (animator.running) {
                animator.step();
            }
        });
    }
    else if (!requestAnimationFrame) {
        // If all animators have been removed,
        // and if this function is called periodically
        // by setInterval(), disable the periodic calling.
        window.clearInterval(timer);
    }
}

/*
 * Start the animation loop.
 *
 * This function delegates the periodic update of all animators
 * to the loop() function, either using requestAnimationFrame()
 * if the browser supports it, or using setInterval().
 */
function start() {
    if (requestAnimationFrame) {
        requestAnimationFrame(loop);
    }
    else {
        timer = window.setInterval(loop, TIME_STEP_MS);
    }
}

/*
 * An animator provides the logic for animating other objects.
 *
 * The main purpose of an animator is to schedule the update
 * operations in the animated objects.
 */
export var Animator = Object.create(EventEmitter.prototype);

Animator.init = function () {
    EventEmitter.call(this);
    this.durationMs = 500;
    this.initialTime = 0;
    this.running = false;
    animatorList.push(this);
    return this;
};

/*
 * Start the current animator.
 *
 * The "step" event is fired once before starting the animation.
 */
Animator.start = function (durationMs) {
    this.durationMs = durationMs || 500;
    this.initialTime = perf.now();
    this.emit("step", 0);
    if (!this.running) {
        this.running = true;
        runningAnimators ++;
        if (runningAnimators === 1) {
            start();
        }
    }
};

/*
 * Stop the current animator.
 */
Animator.stop = function () {
    if (this.running) {
        this.running = false;
        runningAnimators --;
        this.emit("done");
    }
};

/*
 * Perform one animation step.
 *
 * This function is called automatically by the loop() function.
 * It fires the "step" event with the current progress (elapsed time / duration).
 * If the animation duration has elapsed, the "done" event is fired.
 */
Animator.step = function () {
    var elapsedTime = perf.now() - this.initialTime;
    if (elapsedTime >= this.durationMs) {
        this.emit("step", 1);
        this.stop();
    } else {
        this.emit("step", elapsedTime / this.durationMs);
    }
};
<commit_msg>Fixed transition duration 0<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

import {EventEmitter} from "events";

/*
 * The browser-specific function to request an animation frame.
 */
var requestAnimationFrame =
        window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame;

var perf = window.performance && window.performance.now ? window.performance : Date;

/*
 * The default time step.
 * For browsers that do not support animation frames.
 */
var TIME_STEP_MS = 40;

/*
 * The handle provided by setInterval().
 * For browsers that do not support animation frames.
 */
var timer;

// The number of running animators.
var runningAnimators = 0;

/*
 * The list of managed animators.
 */
var animatorList = [];

/*
 * The main animation loop.
 *
 * This function is called periodically and triggers the
 * animation steps in all running animators.
 *
 * If all animators are removed from the list of running animators,
 * then the periodic calling is disabled.
 *
 * This function can be called either through requestAnimationFrame()
 * or through setInterval().
 */
function loop() {
    if (runningAnimators > 0) {
        // If there is at least one animator,
        // and if the browser provides animation frames,
        // schedule this function to be called again in the next frame.
        if (requestAnimationFrame) {
            requestAnimationFrame(loop);
        }

        // Step all animators. We iterate over a copy of the animator list
        // in case the step() method removes an animator from the list.
        animatorList.forEach(animator => {
            if (animator.running) {
                animator.step();
            }
        });
    }
    else if (!requestAnimationFrame) {
        // If all animators have been removed,
        // and if this function is called periodically
        // by setInterval(), disable the periodic calling.
        window.clearInterval(timer);
    }
}

/*
 * Start the animation loop.
 *
 * This function delegates the periodic update of all animators
 * to the loop() function, either using requestAnimationFrame()
 * if the browser supports it, or using setInterval().
 */
function start() {
    if (requestAnimationFrame) {
        requestAnimationFrame(loop);
    }
    else {
        timer = window.setInterval(loop, TIME_STEP_MS);
    }
}

/*
 * An animator provides the logic for animating other objects.
 *
 * The main purpose of an animator is to schedule the update
 * operations in the animated objects.
 */
export var Animator = Object.create(EventEmitter.prototype);

Animator.init = function () {
    EventEmitter.call(this);
    this.durationMs = 500;
    this.initialTime = 0;
    this.running = false;
    animatorList.push(this);
    return this;
};

/*
 * Start the current animator.
 *
 * The "step" event is fired once before starting the animation.
 */
Animator.start = function (durationMs) {
    this.durationMs = durationMs;
    this.initialTime = perf.now();
    this.emit("step", 0);
    if (!this.running) {
        this.running = true;
        runningAnimators ++;
        if (runningAnimators === 1) {
            start();
        }
    }
};

/*
 * Stop the current animator.
 */
Animator.stop = function () {
    if (this.running) {
        this.running = false;
        runningAnimators --;
        this.emit("done");
    }
};

/*
 * Perform one animation step.
 *
 * This function is called automatically by the loop() function.
 * It fires the "step" event with the current progress (elapsed time / duration).
 * If the animation duration has elapsed, the "done" event is fired.
 */
Animator.step = function () {
    var elapsedTime = perf.now() - this.initialTime;
    if (elapsedTime >= this.durationMs) {
        this.emit("step", 1);
        this.stop();
    } else {
        this.emit("step", elapsedTime / this.durationMs);
    }
};
<|endoftext|>


<commit_before>'use strict';

var _ = require('./underscore_ext');
var Rx = require('./rx');
var widgets = require('./columnWidgets');
var colorScales = require('./colorScales');
var util = require('./util');
var Legend = require('./views/Legend');
var React = require('react');
var CanvasDrawing = require('./CanvasDrawing');
var {deepPureRenderMixin, rxEvents} = require('./react-utils');
var {drawHeatmap} = require('./drawHeatmap');

// Since we don't set module.exports, but instead register ourselves
// with columWidgets, react-hot-loader can't handle the updates automatically.
// Accept hot loading here.
if (module.hot) {
	module.hot.accept();
}

// Since there are multiple components in the file we have to use makeHot
// explicitly.
function hotOrNot(component) {
	return module.makeHot ? module.makeHot(component) : component;
}

var colorFns = vs => _.map(vs, colorScales.colorScale);

//
// Tooltip
//

var prec = (function () {
	var precision = 6,
		factor = Math.pow(10, precision);
	return val => (val == null) ? 'NA' : Math.round((val * factor)) / factor;
}());

// We're getting events with coords < 0. Not sure if this
// is a side-effect of the react event system. This will
// restrict values to the given range.
function bounded(min, max, x) {
	return x < min ? min : (x > max ? max : x);
}

var posString = p => `${p.chrom}:${util.addCommas(p.chromstart)}-${util.addCommas(p.chromend)}`;
//gb position string of the segment with 15bp extra on each side, centered at segment
var posRegionString = p => {
	var pad = Math.round((p.chromend - p.chromstart) / 2);
	return `${p.chrom}:${util.addCommas(p.chromstart - pad)}-${util.addCommas(p.chromend + pad)}`;
};
var gbURL = (assembly, pos) => {
	var assemblyString = encodeURIComponent(assembly),
		positionString = encodeURIComponent(posString(pos)),
		regionString = encodeURIComponent(posRegionString(pos));
	return `http://genome.ucsc.edu/cgi-bin/hgTracks?db=${assemblyString}&highlight=${assemblyString}.${positionString}&position=${regionString}`;
};

function tooltip(heatmap, assembly, fields, sampleFormat, fieldFormat, codes, position, width, zoom, samples, ev) {
	var coord = util.eventOffset(ev),
		sampleIndex = bounded(0, samples.length, Math.floor((coord.y * zoom.count / zoom.height) + zoom.index)),
		sampleID = samples[sampleIndex],
		fieldIndex = bounded(0, fields.length, Math.floor(coord.x * fields.length / width)),
		pos = _.get(position, fieldIndex),
		field = fields[fieldIndex];

	var val = _.getIn(heatmap, [fieldIndex, sampleIndex]),
		code = _.get(codes, val),
		label = fieldFormat(field);

	val = code ? code : prec(val);
	let mean = prec(_.meannull(heatmap[fieldIndex]));
	let median = prec(_.medianNull(heatmap[fieldIndex]));
	return {
		sampleID: sampleFormat(sampleID),
		rows: [
			[['labelValue', label, val]],
			...(pos && assembly ? [[['url', `${assembly} ${posString(pos)}`, gbURL(assembly, pos)]]] : []),
			...(!code && (mean !== 'NA') && (median !== 'NA')) ?
				[[['labelValue', 'Mean (Median)', mean + ' (' +
				 median + ')' ]]] : []]
	};
}

//
// Legends
//

function categoryLegend(dataIn, colorScale, codes) {
	if (!colorScale) {
		return {colors: [], labels: []};
	}
	// only finds categories for the current data in the column
	var data = _.reject(_.uniq(dataIn), x => x == null).sort((v1, v2) =>  v1 - v2),
		colors = _.map(data, colorScale),
		labels = _.map(data, d => codes[d]);

	return {colors: colors, labels: labels};
}

// Color scale cases
// Use the domain of the scale as the label.
// If using thresholded scales, add '<' '>' to labels.

var cases = ([tag], arg, c) => c[tag](arg);

function legendForColorscale(colorSpec) {
	var scale = colorScales.colorScale(colorSpec),
		values = scale.domain(),
		colors = _.map(values, scale);

	var labels = cases(colorSpec, values, {
		'no-data': () => [],
		'float': _.identity,
		'float-pos': _.identity,
		'float-neg': _.identity,
		'float-thresh': ([nl, nh, pl, ph]) => [nl, nh, pl, ph],
		'float-thresh-pos': ([low, high]) => [low, high],
		'float-thresh-neg': ([low, high]) => [low, high],
		'float-thresh-log-pos': ([low, high]) => [low, high],
		'float-thresh-log-neg': ([low, high]) => [low, high],
		'float-log': ([low, high]) => [low, high],
	});

	return {colors, labels};
}

// We never want to draw multiple legends. We only draw the 1st scale
// passed in. The caller should provide labels/colors in the 'legend' prop
// if there are multiple scales.
function renderFloatLegend(props) {
	var {units, colors, vizSettings, data} = props;

	if (_.isEmpty(data)) {
		return <Legend colors={[]} labels={''} footnotes={[]}/>;;
	}
	var subColColor = _.max(colors, colorList => _.uniq(colorList.slice(Math.ceil(colorList.length / 2.0))).length),
		{labels, colors: legendColors} = legendForColorscale(subColColor),
		unitText = units[0],
		footnotes = [units && units[0] ? {unitText} : null],
		hasViz = vizSettings => !isNaN(_.getIn(vizSettings, ['min'])),
		multiScaled = colors && colors.length > 1 && !hasViz(vizSettings);

	if (multiScaled) {
		labels = labels.map((label, i) => {
			if (i === 0) {return "lower";}
			else if(i === labels.length - 1) {return "higher";}
			else {return "";}
		});
	}

	return <Legend colors={legendColors} labels={labels} footnotes={footnotes}/>;
}

// Might have colorScale but no data (phenotype), no data & no colorScale,
// or data & colorScale, no colorScale &  data?
function renderCodedLegend(props) {
	var {data: [data] = [], codes, colors = []} = props;
	var legendProps;
	var colorfn = _.first(colorFns(colors.slice(0, 1)));

	// We can use domain() for categorical, but we want to filter out
	// values not in the plot. Also, we build the categorical from all
	// values in the db (even those not in the plot) so that colors will
	// match in other datasets.
	if (data && colorfn) { // category
		legendProps = categoryLegend(data, colorfn, codes);
	} else {
		return ;
	}

	return <Legend {...legendProps} />;
}

var HeatmapLegend = hotOrNot(React.createClass({
	mixins: [deepPureRenderMixin],
	render: function() {
		var {column, data} = this.props,
			{units, heatmap, colors, valueType, vizSettings, defaultNormalization} = column,
			props = {
				units,
				colors,
				vizSettings,
				defaultNormalization,
				data: heatmap,
				coded: valueType === 'coded',
				codes: _.get(data, 'codes'),
			};
		return (props.coded ? renderCodedLegend : renderFloatLegend)(props);
	}
}));

//
// plot rendering
//

var HeatmapColumn = hotOrNot(React.createClass({
	mixins: [deepPureRenderMixin],
	componentWillMount: function () {
		var events = rxEvents(this, 'mouseout', 'mousemove', 'mouseover');

		// Compute tooltip events from mouse events.
		this.ttevents = events.mouseover.filter(ev => util.hasClass(ev.currentTarget, 'Tooltip-target'))
			.flatMap(() => {
				return events.mousemove
					.takeUntil(events.mouseout)
					.map(ev => ({
						data: this.tooltip(ev),
						open: true
					})) // look up current data
					.concat(Rx.Observable.of({open: false}));
			}).subscribe(this.props.tooltip);
	},
	componentWillUnmount: function () {
		this.ttevents.unsubscribe();
	},
	tooltip: function (ev) {
		var {samples, data, column, zoom, sampleFormat, fieldFormat, id} = this.props,
			codes = _.get(data, 'codes'),
			position = _.getIn(data, ['req', 'position']),
			{assembly, fields, heatmap, width} = column;
		return tooltip(heatmap, assembly, fields, sampleFormat, fieldFormat(id), codes, position, width, zoom, samples, ev);
	},
	// To reduce this set of properties, we could
	//    - Drop data & move codes into the 'display' obj, outside of data
	// Might also want to copy fields into 'display', so we can drop req probes
	render: function () {
		var {data, column, zoom} = this.props,
			{heatmap, colors} = column,
			codes = _.get(data, 'codes');

		return (
			<CanvasDrawing
					ref='plot'
					draw={drawHeatmap}
					wrapperProps={{
						className: 'Tooltip-target',
						onMouseMove: this.on.mousemove,
						onMouseOut: this.on.mouseout,
						onMouseOver: this.on.mouseover,
						onClick: this.props.onClick
					}}
					codes={codes}
					width={_.get(column, 'width')}
					zoom={zoom}
					colors={colors}
					heatmapData={heatmap}/>);
	}
}));

var getColumn = props => <HeatmapColumn {...props} />;

widgets.column.add("probes", getColumn);
widgets.column.add("geneProbes", getColumn);
widgets.column.add("genes", getColumn);
widgets.column.add("clinical", getColumn);

var getLegend = props => <HeatmapLegend {...props} />;

widgets.legend.add('probes', getLegend);
widgets.legend.add('geneProbes', getLegend);
widgets.legend.add('genes', getLegend);
widgets.legend.add('clinical', getLegend);
<commit_msg>Skip mean/median if no data yet.<commit_after>'use strict';

var _ = require('./underscore_ext');
var Rx = require('./rx');
var widgets = require('./columnWidgets');
var colorScales = require('./colorScales');
var util = require('./util');
var Legend = require('./views/Legend');
var React = require('react');
var CanvasDrawing = require('./CanvasDrawing');
var {deepPureRenderMixin, rxEvents} = require('./react-utils');
var {drawHeatmap} = require('./drawHeatmap');

// Since we don't set module.exports, but instead register ourselves
// with columWidgets, react-hot-loader can't handle the updates automatically.
// Accept hot loading here.
if (module.hot) {
	module.hot.accept();
}

// Since there are multiple components in the file we have to use makeHot
// explicitly.
function hotOrNot(component) {
	return module.makeHot ? module.makeHot(component) : component;
}

var colorFns = vs => _.map(vs, colorScales.colorScale);

//
// Tooltip
//

var prec = (function () {
	var precision = 6,
		factor = Math.pow(10, precision);
	return val => (val == null) ? 'NA' : Math.round((val * factor)) / factor;
}());

// We're getting events with coords < 0. Not sure if this
// is a side-effect of the react event system. This will
// restrict values to the given range.
function bounded(min, max, x) {
	return x < min ? min : (x > max ? max : x);
}

var posString = p => `${p.chrom}:${util.addCommas(p.chromstart)}-${util.addCommas(p.chromend)}`;
//gb position string of the segment with 15bp extra on each side, centered at segment
var posRegionString = p => {
	var pad = Math.round((p.chromend - p.chromstart) / 2);
	return `${p.chrom}:${util.addCommas(p.chromstart - pad)}-${util.addCommas(p.chromend + pad)}`;
};
var gbURL = (assembly, pos) => {
	var assemblyString = encodeURIComponent(assembly),
		positionString = encodeURIComponent(posString(pos)),
		regionString = encodeURIComponent(posRegionString(pos));
	return `http://genome.ucsc.edu/cgi-bin/hgTracks?db=${assemblyString}&highlight=${assemblyString}.${positionString}&position=${regionString}`;
};

function tooltip(heatmap, assembly, fields, sampleFormat, fieldFormat, codes, position, width, zoom, samples, ev) {
	var coord = util.eventOffset(ev),
		sampleIndex = bounded(0, samples.length, Math.floor((coord.y * zoom.count / zoom.height) + zoom.index)),
		sampleID = samples[sampleIndex],
		fieldIndex = bounded(0, fields.length, Math.floor(coord.x * fields.length / width)),
		pos = _.get(position, fieldIndex),
		field = fields[fieldIndex];

	var val = _.getIn(heatmap, [fieldIndex, sampleIndex]),
		code = _.get(codes, val),
		label = fieldFormat(field);

	val = code ? code : prec(val);
	let mean = heatmap && prec(_.meannull(heatmap[fieldIndex])),
		median = heatmap && prec(_.medianNull(heatmap[fieldIndex]));
	return {
		sampleID: sampleFormat(sampleID),
		rows: [
			[['labelValue', label, val]],
			...(pos && assembly ? [[['url', `${assembly} ${posString(pos)}`, gbURL(assembly, pos)]]] : []),
			...(!code && (mean !== 'NA') && (median !== 'NA')) ?
				[[['labelValue', 'Mean (Median)', mean + ' (' +
				 median + ')' ]]] : []]
	};
}

//
// Legends
//

function categoryLegend(dataIn, colorScale, codes) {
	if (!colorScale) {
		return {colors: [], labels: []};
	}
	// only finds categories for the current data in the column
	var data = _.reject(_.uniq(dataIn), x => x == null).sort((v1, v2) =>  v1 - v2),
		colors = _.map(data, colorScale),
		labels = _.map(data, d => codes[d]);

	return {colors: colors, labels: labels};
}

// Color scale cases
// Use the domain of the scale as the label.
// If using thresholded scales, add '<' '>' to labels.

var cases = ([tag], arg, c) => c[tag](arg);

function legendForColorscale(colorSpec) {
	var scale = colorScales.colorScale(colorSpec),
		values = scale.domain(),
		colors = _.map(values, scale);

	var labels = cases(colorSpec, values, {
		'no-data': () => [],
		'float': _.identity,
		'float-pos': _.identity,
		'float-neg': _.identity,
		'float-thresh': ([nl, nh, pl, ph]) => [nl, nh, pl, ph],
		'float-thresh-pos': ([low, high]) => [low, high],
		'float-thresh-neg': ([low, high]) => [low, high],
		'float-thresh-log-pos': ([low, high]) => [low, high],
		'float-thresh-log-neg': ([low, high]) => [low, high],
		'float-log': ([low, high]) => [low, high],
	});

	return {colors, labels};
}

// We never want to draw multiple legends. We only draw the 1st scale
// passed in. The caller should provide labels/colors in the 'legend' prop
// if there are multiple scales.
function renderFloatLegend(props) {
	var {units, colors, vizSettings, data} = props;

	if (_.isEmpty(data)) {
		return <Legend colors={[]} labels={''} footnotes={[]}/>;;
	}
	var subColColor = _.max(colors, colorList => _.uniq(colorList.slice(Math.ceil(colorList.length / 2.0))).length),
		{labels, colors: legendColors} = legendForColorscale(subColColor),
		unitText = units[0],
		footnotes = [units && units[0] ? {unitText} : null],
		hasViz = vizSettings => !isNaN(_.getIn(vizSettings, ['min'])),
		multiScaled = colors && colors.length > 1 && !hasViz(vizSettings);

	if (multiScaled) {
		labels = labels.map((label, i) => {
			if (i === 0) {return "lower";}
			else if(i === labels.length - 1) {return "higher";}
			else {return "";}
		});
	}

	return <Legend colors={legendColors} labels={labels} footnotes={footnotes}/>;
}

// Might have colorScale but no data (phenotype), no data & no colorScale,
// or data & colorScale, no colorScale &  data?
function renderCodedLegend(props) {
	var {data: [data] = [], codes, colors = []} = props;
	var legendProps;
	var colorfn = _.first(colorFns(colors.slice(0, 1)));

	// We can use domain() for categorical, but we want to filter out
	// values not in the plot. Also, we build the categorical from all
	// values in the db (even those not in the plot) so that colors will
	// match in other datasets.
	if (data && colorfn) { // category
		legendProps = categoryLegend(data, colorfn, codes);
	} else {
		return ;
	}

	return <Legend {...legendProps} />;
}

var HeatmapLegend = hotOrNot(React.createClass({
	mixins: [deepPureRenderMixin],
	render: function() {
		var {column, data} = this.props,
			{units, heatmap, colors, valueType, vizSettings, defaultNormalization} = column,
			props = {
				units,
				colors,
				vizSettings,
				defaultNormalization,
				data: heatmap,
				coded: valueType === 'coded',
				codes: _.get(data, 'codes'),
			};
		return (props.coded ? renderCodedLegend : renderFloatLegend)(props);
	}
}));

//
// plot rendering
//

var HeatmapColumn = hotOrNot(React.createClass({
	mixins: [deepPureRenderMixin],
	componentWillMount: function () {
		var events = rxEvents(this, 'mouseout', 'mousemove', 'mouseover');

		// Compute tooltip events from mouse events.
		this.ttevents = events.mouseover.filter(ev => util.hasClass(ev.currentTarget, 'Tooltip-target'))
			.flatMap(() => {
				return events.mousemove
					.takeUntil(events.mouseout)
					.map(ev => ({
						data: this.tooltip(ev),
						open: true
					})) // look up current data
					.concat(Rx.Observable.of({open: false}));
			}).subscribe(this.props.tooltip);
	},
	componentWillUnmount: function () {
		this.ttevents.unsubscribe();
	},
	tooltip: function (ev) {
		var {samples, data, column, zoom, sampleFormat, fieldFormat, id} = this.props,
			codes = _.get(data, 'codes'),
			position = _.getIn(data, ['req', 'position']),
			{assembly, fields, heatmap, width} = column;
		return tooltip(heatmap, assembly, fields, sampleFormat, fieldFormat(id), codes, position, width, zoom, samples, ev);
	},
	// To reduce this set of properties, we could
	//    - Drop data & move codes into the 'display' obj, outside of data
	// Might also want to copy fields into 'display', so we can drop req probes
	render: function () {
		var {data, column, zoom} = this.props,
			{heatmap, colors} = column,
			codes = _.get(data, 'codes');

		return (
			<CanvasDrawing
					ref='plot'
					draw={drawHeatmap}
					wrapperProps={{
						className: 'Tooltip-target',
						onMouseMove: this.on.mousemove,
						onMouseOut: this.on.mouseout,
						onMouseOver: this.on.mouseover,
						onClick: this.props.onClick
					}}
					codes={codes}
					width={_.get(column, 'width')}
					zoom={zoom}
					colors={colors}
					heatmapData={heatmap}/>);
	}
}));

var getColumn = props => <HeatmapColumn {...props} />;

widgets.column.add("probes", getColumn);
widgets.column.add("geneProbes", getColumn);
widgets.column.add("genes", getColumn);
widgets.column.add("clinical", getColumn);

var getLegend = props => <HeatmapLegend {...props} />;

widgets.legend.add('probes', getLegend);
widgets.legend.add('geneProbes', getLegend);
widgets.legend.add('genes', getLegend);
widgets.legend.add('clinical', getLegend);
<|endoftext|>


<commit_before>function spotPosting() {
	this.commentForm = null;
	this.reportForm = null;
	this.uiStart = null;
	this.uiDone = null;

	this.cbHashcashCalculated = function (self, hash) {
	    self.commentForm['postcommentform[newmessageid]'].value = hash;
	    self.commentForm['postcommentform[submitpost]'].value = 'Post';
		self.uiDone();

		// convert the processed form to post values
		var dataString = $(self.commentForm).serialize()

		// and actually process the call
		$.ajax({
			type: "POST",
			url: "?page=postcomment",
			dataType: "xml",
			data: dataString,
			success: function(xml) {
				var result = $(xml).find('result').text();
				if(result == 'success') {
					var user = $(xml).find('user').text();
					var spotterid = $(xml).find('spotterid').text();
					var rating = $(xml).find('rating').text();
					var text = $(xml).find('body').text();
					var spotteridurl = 'http://'+window.location.hostname+window.location.pathname+'?search[tree]=&search[type]=SpotterID&search[text]='+spotterid;
					var commentimage = $(xml).find('commentimage').text();

					var data = "
   <t>Posted by %1</t>".replace("%1", ""+user+"") + " (<a class='spotterid' target='_parent' href='"+spotteridurl+"' title='<t>Search spots from %1</t>".replace("%1", spotterid) + "'>"+spotterid+") @ <t>just now</t>
"+text+"
";

					$("li.nocomments").remove();
					$("li.firstComment").removeClass("firstComment");
					$("li.addComment").after(data).next().hide().addClass("firstComment").fadeIn(function(){
						$("#commentslist > li").removeClass("even");
						$("#commentslist > li:nth-child(even)").addClass('even');
						$("span.commentcount").html('# '+$("#commentslist").children().not(".addComment").size());
					});
				}
			},
			error: function(xml) {
				console.log('error: '+((new XMLSerializer()).serializeToString(xml)));
			}
		});
	} // cbHashcashCalculated

	this.rpHashcashCalculated = function (self, hash) {
			self.reportForm['postreportform[newmessageid]'].value = hash;
			self.reportForm['postreportform[submitpost]'].value = 'Post';
			self.uiDone();

			var dataString2 = $(self.reportForm).serialize()

			$.ajax({
				type: "POST",
				url: "?page=reportpost",
				dataType: "xml",
				data: dataString2,
				success: function(xml) {
					var result = $(xml).find('result').text();
					var errors = $(xml).find('errors').text();

					if(result != 'success') {
						console.log('error: '+((new XMLSerializer()).serializeToString(xml)));

						$(".spamreport-button").attr('title', result + ': ' + errors);
						alert('<t>Marking as spam was not successfull:</t> ' + errors);
					} // else
				},
				error: function(xml) {
					console.log('error: '+((new XMLSerializer()).serializeToString(xml)));
				}
			});
	} // callback rpHashcashCalculated

	this.spotHashcashCalculated = function (self, hash) {
			// and enter the form's inputfields
			self.newSpotForm['newspotform[newmessageid]'].value = hash;
			self.newSpotForm['newspotform[submitpost]'].value = 'Post';
			self.uiDone();

			$(self.newSpotForm).ajaxSubmit({
				type: "POST",
				url: "?page=postspot",
				dataType: "xml",
				success: function(xml) {
					var $dialdiv = $("#editdialogdiv")
					var result = $(xml).find('result').text();

					var $formerrors = $dialdiv.find("ul.formerrors");
					$formerrors.empty();
					var $forminfo = $dialdiv.find("ul.forminformation");
					$forminfo.empty();

					if (result == 'success') {
						// zet de information van het formulier in de infolijst
						$('info', xml).each(function() {
							$forminfo.append("
" + $(this).text() + "
");
						}); // each

						/**
						 * We succesfully posted a new spot, now clear the title field
						 * and body. This makes the other field stay in tact, so if a
						 * user is posting several of the same type of spots, he doesn't
						 * have to enter everything again
						 */
						$("input[name='newspotform[title]']").val('');
						$("textarea[name='newspotform[body]']").val('');
						$("input[name='newspotform[nzbfile]']").val('');
						$("input[name='newspotform[imagefile]']").val('');
					} else {
						// voeg nu de errors in de html
						// zet de errors van het formulier in de errorlijst
						$('errors', xml).each(function() {
							$formerrors.append("
" + $(this).text() + "
");
						}); // each
					} // if post was not succesful
				}, // success()
				error: function(xml) {
					console.log('error: '+((new XMLSerializer()).serializeToString(xml)));
				}
			});
	} // callback spotHashcashCalculated

	this.postComment = function(commentForm, uiStart, uiDone) {
		this.commentForm = commentForm;
		this.uiStart = uiStart;
		this.uiDone = uiDone;

		// update the UI
		this.uiStart();

		// First retrieve some values from the form we are submitting
		var randomstr = commentForm['postcommentform[randomstr]'].value;
		var rating = commentForm['postcommentform[rating]'].value;

		// inreplyto is the whole messageid, we strip off the @ part to add
		// our own stuff
		var inreplyto = commentForm['postcommentform[inreplyto]'].value;
		inreplyto = inreplyto.substring(0, inreplyto.indexOf('@'));

		/* Nu vragen we om, asynchroon, een hashcash te berekenen. Zie comments van calculateCommentHashCash()
		   waarom dit asynhcroon verloopt */
		this.calculateCommentHashCash('<' + inreplyto + '.' + rating + '.' + randomstr + '.', '@spot.net>', 0, this.cbHashcashCalculated);
	} // postComment

	this.postReport = function(reportForm, uiStart, uiDone) {
		this.reportForm = reportForm;
		this.uiStart = uiStart;
		this.uiDone = uiDone;

		this.uiStart();

		var randomstr = reportForm['postreportform[randomstr]'].value;

		var inreplyto = reportForm['postreportform[inreplyto]'].value;
		inreplyto = inreplyto.substring(0, inreplyto.indexOf('@'));

		this.calculateCommentHashCash('<' + inreplyto + '.' + randomstr + '.', '@spot.net>', 0, this.rpHashcashCalculated);
	} // postReport

	this.postNewSpot = function(newSpotForm, uiStart, uiDone) {
		this.newSpotForm = newSpotForm;
		this.uiStart = uiStart;
		this.uiDone = uiDone;

		/* Clear the errors */
		var $dialdiv = $("#editdialogdiv")
		$dialdiv.find("ul.formerrors").empty();
		$dialdiv.find("ul.forminformation").empty();

		this.uiStart();

		var randomstr = reportForm['postreportform[randomstr]'].value;

		this.calculateCommentHashCash('<' + randomstr, '@spot.net>', 0, this.spotHashcashCalculated);
	} // postNewSpot

	//
	// We breken de make expensive hash op in stukken omdat
	// anders IE8 gaat zeuren over scripts die te lang lopen.
	//
	this.calculateCommentHashCash = function(prefix, suffix, runCount, cbWhenFound) {
		var possibleChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
		var hash = prefix + suffix;
		var validHash = false;

		do {
			runCount++;
			uniquePart = '';

			for(i = 0; i < 15; i++) {
				var irand = Math.round(Math.random() * (possibleChars.length - 1));
				uniquePart += possibleChars.charAt(irand);
			} // for

			hash = $.sha1(prefix + uniquePart + suffix);
			validHash = (hash.substr(0, 4) == '0000');
		} while ((!validHash) && ((runCount % 500) != 0));

		if (validHash) {
			cbWhenFound(this, prefix + uniquePart + suffix);
		} else {
			if (runCount > 400000) {
				alert("<t>Calculation of SHA1 hash was not successfull:</t> " + runCount);
				cbWhenFound(this, '');
			} else {
				var _this = this;
				window.setTimeout(function() { _this.calculateCommentHashCash(prefix, suffix, runCount, cbWhenFound); }, 0);
			} // else
		} // if

	} // calculateCommentHashCash
};
<commit_msg>Fix typo in posting of new spots. Fixes #1523<commit_after>function spotPosting() {
	this.commentForm = null;
	this.reportForm = null;
	this.uiStart = null;
	this.uiDone = null;

	this.cbHashcashCalculated = function (self, hash) {
	    self.commentForm['postcommentform[newmessageid]'].value = hash;
	    self.commentForm['postcommentform[submitpost]'].value = 'Post';
		self.uiDone();

		// convert the processed form to post values
		var dataString = $(self.commentForm).serialize()

		// and actually process the call
		$.ajax({
			type: "POST",
			url: "?page=postcomment",
			dataType: "xml",
			data: dataString,
			success: function(xml) {
				var result = $(xml).find('result').text();
				if(result == 'success') {
					var user = $(xml).find('user').text();
					var spotterid = $(xml).find('spotterid').text();
					var rating = $(xml).find('rating').text();
					var text = $(xml).find('body').text();
					var spotteridurl = 'http://'+window.location.hostname+window.location.pathname+'?search[tree]=&search[type]=SpotterID&search[text]='+spotterid;
					var commentimage = $(xml).find('commentimage').text();

					var data = "
   <t>Posted by %1</t>".replace("%1", ""+user+"") + " (<a class='spotterid' target='_parent' href='"+spotteridurl+"' title='<t>Search spots from %1</t>".replace("%1", spotterid) + "'>"+spotterid+") @ <t>just now</t>
"+text+"
";

					$("li.nocomments").remove();
					$("li.firstComment").removeClass("firstComment");
					$("li.addComment").after(data).next().hide().addClass("firstComment").fadeIn(function(){
						$("#commentslist > li").removeClass("even");
						$("#commentslist > li:nth-child(even)").addClass('even');
						$("span.commentcount").html('# '+$("#commentslist").children().not(".addComment").size());
					});
				}
			},
			error: function(xml) {
				console.log('error: '+((new XMLSerializer()).serializeToString(xml)));
			}
		});
	} // cbHashcashCalculated

	this.rpHashcashCalculated = function (self, hash) {
			self.reportForm['postreportform[newmessageid]'].value = hash;
			self.reportForm['postreportform[submitpost]'].value = 'Post';
			self.uiDone();

			var dataString2 = $(self.reportForm).serialize()

			$.ajax({
				type: "POST",
				url: "?page=reportpost",
				dataType: "xml",
				data: dataString2,
				success: function(xml) {
					var result = $(xml).find('result').text();
					var errors = $(xml).find('errors').text();

					if(result != 'success') {
						console.log('error: '+((new XMLSerializer()).serializeToString(xml)));

						$(".spamreport-button").attr('title', result + ': ' + errors);
						alert('<t>Marking as spam was not successfull:</t> ' + errors);
					} // else
				},
				error: function(xml) {
					console.log('error: '+((new XMLSerializer()).serializeToString(xml)));
				}
			});
	} // callback rpHashcashCalculated

	this.spotHashcashCalculated = function (self, hash) {
			// and enter the form's inputfields
			self.newSpotForm['newspotform[newmessageid]'].value = hash;
			self.newSpotForm['newspotform[submitpost]'].value = 'Post';
			self.uiDone();

			$(self.newSpotForm).ajaxSubmit({
				type: "POST",
				url: "?page=postspot",
				dataType: "xml",
				success: function(xml) {
					var $dialdiv = $("#editdialogdiv")
					var result = $(xml).find('result').text();

					var $formerrors = $dialdiv.find("ul.formerrors");
					$formerrors.empty();
					var $forminfo = $dialdiv.find("ul.forminformation");
					$forminfo.empty();

					if (result == 'success') {
						// zet de information van het formulier in de infolijst
						$('info', xml).each(function() {
							$forminfo.append("
" + $(this).text() + "
");
						}); // each

						/**
						 * We succesfully posted a new spot, now clear the title field
						 * and body. This makes the other field stay in tact, so if a
						 * user is posting several of the same type of spots, he doesn't
						 * have to enter everything again
						 */
						$("input[name='newspotform[title]']").val('');
						$("textarea[name='newspotform[body]']").val('');
						$("input[name='newspotform[nzbfile]']").val('');
						$("input[name='newspotform[imagefile]']").val('');
					} else {
						// voeg nu de errors in de html
						// zet de errors van het formulier in de errorlijst
						$('errors', xml).each(function() {
							$formerrors.append("
" + $(this).text() + "
");
						}); // each
					} // if post was not succesful
				}, // success()
				error: function(xml) {
					console.log('error: '+((new XMLSerializer()).serializeToString(xml)));
				}
			});
	} // callback spotHashcashCalculated

	this.postComment = function(commentForm, uiStart, uiDone) {
		this.commentForm = commentForm;
		this.uiStart = uiStart;
		this.uiDone = uiDone;

		// update the UI
		this.uiStart();

		// First retrieve some values from the form we are submitting
		var randomstr = commentForm['postcommentform[randomstr]'].value;
		var rating = commentForm['postcommentform[rating]'].value;

		// inreplyto is the whole messageid, we strip off the @ part to add
		// our own stuff
		var inreplyto = commentForm['postcommentform[inreplyto]'].value;
		inreplyto = inreplyto.substring(0, inreplyto.indexOf('@'));

		/* Nu vragen we om, asynchroon, een hashcash te berekenen. Zie comments van calculateCommentHashCash()
		   waarom dit asynhcroon verloopt */
		this.calculateCommentHashCash('<' + inreplyto + '.' + rating + '.' + randomstr + '.', '@spot.net>', 0, this.cbHashcashCalculated);
	} // postComment

	this.postReport = function(reportForm, uiStart, uiDone) {
		this.reportForm = reportForm;
		this.uiStart = uiStart;
		this.uiDone = uiDone;

		this.uiStart();

		var randomstr = reportForm['postreportform[randomstr]'].value;

		var inreplyto = reportForm['postreportform[inreplyto]'].value;
		inreplyto = inreplyto.substring(0, inreplyto.indexOf('@'));

		this.calculateCommentHashCash('<' + inreplyto + '.' + randomstr + '.', '@spot.net>', 0, this.rpHashcashCalculated);
	} // postReport

	this.postNewSpot = function(newSpotForm, uiStart, uiDone) {
		this.newSpotForm = newSpotForm;
		this.uiStart = uiStart;
		this.uiDone = uiDone;

		/* Clear the errors */
		var $dialdiv = $("#editdialogdiv")
		$dialdiv.find("ul.formerrors").empty();
		$dialdiv.find("ul.forminformation").empty();

		this.uiStart();

		var randomstr = newSpotForm['postreportform[randomstr]'].value;

		this.calculateCommentHashCash('<' + randomstr, '@spot.net>', 0, this.spotHashcashCalculated);
	} // postNewSpot

	//
	// We breken de make expensive hash op in stukken omdat
	// anders IE8 gaat zeuren over scripts die te lang lopen.
	//
	this.calculateCommentHashCash = function(prefix, suffix, runCount, cbWhenFound) {
		var possibleChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
		var hash = prefix + suffix;
		var validHash = false;

		do {
			runCount++;
			uniquePart = '';

			for(i = 0; i < 15; i++) {
				var irand = Math.round(Math.random() * (possibleChars.length - 1));
				uniquePart += possibleChars.charAt(irand);
			} // for

			hash = $.sha1(prefix + uniquePart + suffix);
			validHash = (hash.substr(0, 4) == '0000');
		} while ((!validHash) && ((runCount % 500) != 0));

		if (validHash) {
			cbWhenFound(this, prefix + uniquePart + suffix);
		} else {
			if (runCount > 400000) {
				alert("<t>Calculation of SHA1 hash was not successfull:</t> " + runCount);
				cbWhenFound(this, '');
			} else {
				var _this = this;
				window.setTimeout(function() { _this.calculateCommentHashCash(prefix, suffix, runCount, cbWhenFound); }, 0);
			} // else
		} // if

	} // calculateCommentHashCash
};
<|endoftext|>


<commit_before>var request = require('request');
var qs = require('querystring');
var pkgMeta = require('../package.json');

var helper = require('./helper');
var ECKey = require('./key');

var BlockIo = module.exports = function (config, pin, version, options) {

  this.options = {
    allowNoPin: false
  };

  if (typeof(config) === 'string') {

    this.key = config;
    this.version = version || BlockIo.DEFAULT_VERSION;
    this.server = BlockIo.DEFAULT_SERVER;
    this.port = BlockIo.DEFAULT_PORT;

    if (pin) {
      this.pin = pin;
      this.aesKey = helper.pinToKey(this.pin);
    }

    if (options && typeof(options) == 'object') this._cloneOptions(options);

  } else if (config && typeof(config) == 'object') {
    this.key = config.api_key;
    this.version = config.version || BlockIo.DEFAULT_VERSION;
    this.server = config.server || BlockIo.DEFAULT_SERVER;
    this.port = config.port || BlockIo.DEFAULT_PORT;

    if (config.pin) {
      this.pin = config.pin;
      this.aesKey = helper.pinToKey(this.pin);
    }

    if (config.options) this._cloneOptions(config.options);
  }
};

// link submodules / subclasses
BlockIo.ECKey = ECKey;
BlockIo.helper = helper;

BlockIo.DEFAULT_VERSION = 2;
BlockIo.DEFAULT_SERVER = '';
BlockIo.DEFAULT_PORT = '';
BlockIo.HOST = 'block.io';

// Error messages
var ERR_KEY_INV = 'Error occurred validating key';
var ERR_PIN_MIS = 'Missing "pin", please supply as argument';
var ERR_PIN_INV = 'Public key mismatch. Invalid Secret PIN detected.';
var ERR_PK_EXTR = 'Could not extract privkey';
var ERR_WIF_MIS = 'Missing mandatory private_key argument';
var ERR_WIF_INV = 'Could not parse private_key as WIF';
var ERR_DEST_MIS = 'Missing mandatory to_address argument';
var ERR_UNKNOWN = 'Unknown error occured';

// IF YOU EDIT THIS LIB, PLEASE BE A DARLING AND CHANGE THE USER AGENT :)
BlockIo.USER_AGENT = ['npm','block_io', pkgMeta.version].join(':');

// simple uniform methods that do not need special processing
BlockIo.PASSTHROUGH_METHODS = [
  'get_balance', 'get_new_address', 'get_my_addresses', 'get_address_received',
  'get_address_by_label', 'get_address_balance', 'create_user', 'get_users',
  'get_user_balance', 'get_user_address', 'get_user_received',
  'get_current_price', 'is_green_address', 'is_green_transaction',
  'get_transactions', 'sign_and_finalize_withdrawal', 'get_new_dtrust_address',
  'get_my_dtrust_addresses', 'get_dtrust_address_by_label',
  'get_dtrust_transactions', 'get_dtrust_address_balance',
  'get_network_fee_estimate', 'archive_address', 'unarchive_address',
  'get_my_archived_addresses', 'archive_dtrust_address',
  'unarchive_dtrust_address', 'get_my_archived_dtrust_addresses',
  'get_dtrust_network_fee_estimate', 'create_notification', 'disable_notification',
  'enable_notification', 'get_notifications', 'get_recent_notification_events',
  'delete_notification', 'validate_api_key', 'sign_transaction', 'finalize_transaction'
];

// withdrawal methods that need local signing
BlockIo.WITHDRAWAL_METHODS = [
  'withdraw', 'withdraw_from_user', 'withdraw_from_label',
  'withdraw_from_address', 'withdraw_from_labels', 'withdraw_from_addresses',
  'withdraw_from_users', 'withdraw_from_dtrust_address',
  'withdraw_from_dtrust_labels'
];

BlockIo.SWEEP_METHODS = ['sweep_from_address'];

BlockIo.prototype._withdraw = function (method, path, args, cb) {
  if (!args || typeof(args) !== 'object') args = {};

  // check for pin
  var pin = null;
  var pinSupplied = (typeof(args.pin) !== 'undefined');

  if (pinSupplied || typeof(this.pin) != 'undefined')
    pin = pinSupplied ? args.pin : this.pin;

  // add pin to args for v1, remove for v2;
  if (this.version == 1) {
    if (!pin) return cb(new Error(ERR_PIN_MIS));
    args.pin = pin;
  } else {
    delete args.pin;
  }

  var self = this;
  return this._request(method, path, args, function (e, res) {

    if (e) return cb(e, res);

    if (typeof(res) !== 'object' ||
      typeof(res.data) !== 'object' ||
      !res.data.hasOwnProperty('reference_id')) return cb(e, res);

    //if we're doing dtrust, return the intermediate object for manual signing.
    if (!res.data.encrypted_passphrase ||
      !res.data.encrypted_passphrase.passphrase)
        return cb(e, res);

    // if no pin was supplied and allowNoPin flag was set,
    // return the response for signing asynchronously,
    // else return pin error
    if (!pin)
      return (self.options.allowNoPin) ? cb(e, res) : cb(new Error(ERR_PIN_INV));

    // If we get here, Block.io's asking us to provide client-side signatures
    var encrypted_passphrase = res.data.encrypted_passphrase.passphrase;
    var aesKey = self.aesKey || helper.pinToKey(pin);
    var privkey = helper.extractKey(encrypted_passphrase, aesKey);

    if (!(privkey instanceof ECKey))
      return cb(new Error(ERR_PK_EXTR));

    var pubkey = privkey.pub.toHex();
    if (pubkey != res.data.encrypted_passphrase.signer_public_key)
      return cb(new Error(ERR_PIN_INV));

    res.data.inputs = helper.signInputs(privkey, res.data.inputs);

    aesKey = '';
    privkey = null;

    self._request(method, 'sign_and_finalize_withdrawal', {
      signature_data: JSON.stringify(res.data)
    }, cb);

  });

};

BlockIo.prototype._sweep = function (method, path, args, cb) {
  var key = null;

  if (!args || typeof(args) !== 'object' || typeof(args.private_key) !== 'string')
    return cb(new Error(ERR_WIF_MIS), args);

  if (!args.to_address) return cb(new Error(ERR_DEST_MIS));

  try {
    key = ECKey.fromWIF(args.private_key);
  } catch (ex) {
    return cb(ex);
  }

  if (!(key instanceof ECKey)) return cb(new Error(ERR_WIF_INV));

  args.public_key = key.pub.toHex();
  delete args.private_key;

  var self = this;
  this._request(method, path, args, function (e, res) {
    if (e) return cb(e, res);

    if (typeof(res) !== 'object' ||
      typeof(res.data) !== 'object' ||
      !res.data.hasOwnProperty('reference_id')) return cb(e, res);

    res.data.inputs = helper.signInputs(key, res.data.inputs);

    key = null;

    self._request(method, 'sign_and_finalize_sweep', {
      signature_data: JSON.stringify(res.data)
    }, cb);

  });

};

BlockIo.prototype._constructURL = function (path, query) {
  return [
    'https://',
    this.server, (this.server) ? '.' : '',            // eg: 'dev.'
    BlockIo.HOST,                                     // block.io
    (this.port) ? ':' : '', this.port,                // eg: :80
    '/api/v', (this.version).toString(10), '/',       // eg: /api/v1/
    path,                                             // eg: get_balance
    query ? ['?', qs.stringify(query)].join('') : ''  // eg: ?api_key=abc
  ].join('');
};

BlockIo.prototype._request = function (method, path, args, cb) {
  var opts = {
    method: method,
    secureProtocol: 'TLSv1_method',
    headers: { 'User-Agent': BlockIo.USER_AGENT }
  };

  switch (method) {
    case 'POST':
      opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
      opts.url = this._constructURL(path, {api_key: this.key}),
      opts.body = qs.stringify(args);
      break;
    default:
      opts.url = this._constructURL(path),
      args.api_key = this.key;
      opts.qs = args;
  }

  return cb ? request(opts, BlockIo.parseResponse(cb)) : request(opts);
};

BlockIo.parseResponse = function (cb) {

  return function (err, res, body) {
    if (err) return cb(err);

    var errOut = null;
    var errMsg = ERR_UNKNOWN;
    var data = null;

    try {
      data = JSON.parse(body);
    } catch (ex) {
      errOut = ex;
    }

    if (typeof(data) !== 'object' || data.status !== 'success') {
      if (data.error_message) errMsg = data.error_message;
      if (data.data && typeof(data.data) === 'object' &&
        data.data.error_message) errMsg = data.data.error_message;
      errOut = new Error(errMsg);
    }

    return cb(errOut, data);
  };

};

BlockIo.prototype.validate_key = function (cb) {
  // Test if the key is valid by doing a simple balance check

  this._request('GET', 'get_balance', {}, function (err, doc) {
    var errOut = err ||
      (doc.status === 'success') ? null : new Error(ERR_KEY_INV);
    return cb(errOut, (errOut === null));
  });

};

BlockIo.prototype._cloneOptions = function (options) {
  var self = this;
  Object.keys(options).forEach(function (k) {
    self.options[k] = options[k];
  });
};

BlockIo._constructMethod = function (type, callMethod) {
  var fn = ['_', type].join('');
  return function (args, cb) {

    // handle overload for function (cb), without args
    if (!cb && typeof(args) === 'function') {
      cb = args;
      args = {};
    }

    this[fn]('POST', callMethod, args, cb);
  };
};

// generate methods for each valid call method
BlockIo.PASSTHROUGH_METHODS.forEach(function (method) {
  BlockIo.prototype[method] = BlockIo._constructMethod('request', method);
});

BlockIo.WITHDRAWAL_METHODS.forEach(function (method) {
  BlockIo.prototype[method] = BlockIo._constructMethod('withdraw', method);
});

BlockIo.SWEEP_METHODS.forEach(function (method) {
  BlockIo.prototype[method] = BlockIo._constructMethod('sweep', method);
});
<commit_msg>Add nil check to exception checker<commit_after>var request = require('request');
var qs = require('querystring');
var pkgMeta = require('../package.json');

var helper = require('./helper');
var ECKey = require('./key');

var BlockIo = module.exports = function (config, pin, version, options) {

  this.options = {
    allowNoPin: false
  };

  if (typeof(config) === 'string') {

    this.key = config;
    this.version = version || BlockIo.DEFAULT_VERSION;
    this.server = BlockIo.DEFAULT_SERVER;
    this.port = BlockIo.DEFAULT_PORT;

    if (pin) {
      this.pin = pin;
      this.aesKey = helper.pinToKey(this.pin);
    }

    if (options && typeof(options) == 'object') this._cloneOptions(options);

  } else if (config && typeof(config) == 'object') {
    this.key = config.api_key;
    this.version = config.version || BlockIo.DEFAULT_VERSION;
    this.server = config.server || BlockIo.DEFAULT_SERVER;
    this.port = config.port || BlockIo.DEFAULT_PORT;

    if (config.pin) {
      this.pin = config.pin;
      this.aesKey = helper.pinToKey(this.pin);
    }

    if (config.options) this._cloneOptions(config.options);
  }
};

// link submodules / subclasses
BlockIo.ECKey = ECKey;
BlockIo.helper = helper;

BlockIo.DEFAULT_VERSION = 2;
BlockIo.DEFAULT_SERVER = '';
BlockIo.DEFAULT_PORT = '';
BlockIo.HOST = 'block.io';

// Error messages
var ERR_KEY_INV = 'Error occurred validating key';
var ERR_PIN_MIS = 'Missing "pin", please supply as argument';
var ERR_PIN_INV = 'Public key mismatch. Invalid Secret PIN detected.';
var ERR_PK_EXTR = 'Could not extract privkey';
var ERR_WIF_MIS = 'Missing mandatory private_key argument';
var ERR_WIF_INV = 'Could not parse private_key as WIF';
var ERR_DEST_MIS = 'Missing mandatory to_address argument';
var ERR_UNKNOWN = 'Unknown error occured';

// IF YOU EDIT THIS LIB, PLEASE BE A DARLING AND CHANGE THE USER AGENT :)
BlockIo.USER_AGENT = ['npm','block_io', pkgMeta.version].join(':');

// simple uniform methods that do not need special processing
BlockIo.PASSTHROUGH_METHODS = [
  'get_balance', 'get_new_address', 'get_my_addresses', 'get_address_received',
  'get_address_by_label', 'get_address_balance', 'create_user', 'get_users',
  'get_user_balance', 'get_user_address', 'get_user_received',
  'get_current_price', 'is_green_address', 'is_green_transaction',
  'get_transactions', 'sign_and_finalize_withdrawal', 'get_new_dtrust_address',
  'get_my_dtrust_addresses', 'get_dtrust_address_by_label',
  'get_dtrust_transactions', 'get_dtrust_address_balance',
  'get_network_fee_estimate', 'archive_address', 'unarchive_address',
  'get_my_archived_addresses', 'archive_dtrust_address',
  'unarchive_dtrust_address', 'get_my_archived_dtrust_addresses',
  'get_dtrust_network_fee_estimate', 'create_notification', 'disable_notification',
  'enable_notification', 'get_notifications', 'get_recent_notification_events',
  'delete_notification', 'validate_api_key', 'sign_transaction', 'finalize_transaction'
];

// withdrawal methods that need local signing
BlockIo.WITHDRAWAL_METHODS = [
  'withdraw', 'withdraw_from_user', 'withdraw_from_label',
  'withdraw_from_address', 'withdraw_from_labels', 'withdraw_from_addresses',
  'withdraw_from_users', 'withdraw_from_dtrust_address',
  'withdraw_from_dtrust_labels'
];

BlockIo.SWEEP_METHODS = ['sweep_from_address'];

BlockIo.prototype._withdraw = function (method, path, args, cb) {
  if (!args || typeof(args) !== 'object') args = {};

  // check for pin
  var pin = null;
  var pinSupplied = (typeof(args.pin) !== 'undefined');

  if (pinSupplied || typeof(this.pin) != 'undefined')
    pin = pinSupplied ? args.pin : this.pin;

  // add pin to args for v1, remove for v2;
  if (this.version == 1) {
    if (!pin) return cb(new Error(ERR_PIN_MIS));
    args.pin = pin;
  } else {
    delete args.pin;
  }

  var self = this;
  return this._request(method, path, args, function (e, res) {

    if (e) return cb(e, res);

    if (typeof(res) !== 'object' ||
      typeof(res.data) !== 'object' ||
      !res.data.hasOwnProperty('reference_id')) return cb(e, res);

    //if we're doing dtrust, return the intermediate object for manual signing.
    if (!res.data.encrypted_passphrase ||
      !res.data.encrypted_passphrase.passphrase)
        return cb(e, res);

    // if no pin was supplied and allowNoPin flag was set,
    // return the response for signing asynchronously,
    // else return pin error
    if (!pin)
      return (self.options.allowNoPin) ? cb(e, res) : cb(new Error(ERR_PIN_INV));

    // If we get here, Block.io's asking us to provide client-side signatures
    var encrypted_passphrase = res.data.encrypted_passphrase.passphrase;
    var aesKey = self.aesKey || helper.pinToKey(pin);
    var privkey = helper.extractKey(encrypted_passphrase, aesKey);

    if (!(privkey instanceof ECKey))
      return cb(new Error(ERR_PK_EXTR));

    var pubkey = privkey.pub.toHex();
    if (pubkey != res.data.encrypted_passphrase.signer_public_key)
      return cb(new Error(ERR_PIN_INV));

    res.data.inputs = helper.signInputs(privkey, res.data.inputs);

    aesKey = '';
    privkey = null;

    self._request(method, 'sign_and_finalize_withdrawal', {
      signature_data: JSON.stringify(res.data)
    }, cb);

  });

};

BlockIo.prototype._sweep = function (method, path, args, cb) {
  var key = null;

  if (!args || typeof(args) !== 'object' || typeof(args.private_key) !== 'string')
    return cb(new Error(ERR_WIF_MIS), args);

  if (!args.to_address) return cb(new Error(ERR_DEST_MIS));

  try {
    key = ECKey.fromWIF(args.private_key);
  } catch (ex) {
    return cb(ex);
  }

  if (!(key instanceof ECKey)) return cb(new Error(ERR_WIF_INV));

  args.public_key = key.pub.toHex();
  delete args.private_key;

  var self = this;
  this._request(method, path, args, function (e, res) {
    if (e) return cb(e, res);

    if (typeof(res) !== 'object' ||
      typeof(res.data) !== 'object' ||
      !res.data.hasOwnProperty('reference_id')) return cb(e, res);

    res.data.inputs = helper.signInputs(key, res.data.inputs);

    key = null;

    self._request(method, 'sign_and_finalize_sweep', {
      signature_data: JSON.stringify(res.data)
    }, cb);

  });

};

BlockIo.prototype._constructURL = function (path, query) {
  return [
    'https://',
    this.server, (this.server) ? '.' : '',            // eg: 'dev.'
    BlockIo.HOST,                                     // block.io
    (this.port) ? ':' : '', this.port,                // eg: :80
    '/api/v', (this.version).toString(10), '/',       // eg: /api/v1/
    path,                                             // eg: get_balance
    query ? ['?', qs.stringify(query)].join('') : ''  // eg: ?api_key=abc
  ].join('');
};

BlockIo.prototype._request = function (method, path, args, cb) {
  var opts = {
    method: method,
    secureProtocol: 'TLSv1_method',
    headers: { 'User-Agent': BlockIo.USER_AGENT }
  };

  switch (method) {
    case 'POST':
      opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
      opts.url = this._constructURL(path, {api_key: this.key}),
      opts.body = qs.stringify(args);
      break;
    default:
      opts.url = this._constructURL(path),
      args.api_key = this.key;
      opts.qs = args;
  }

  return cb ? request(opts, BlockIo.parseResponse(cb)) : request(opts);
};

BlockIo.parseResponse = function (cb) {

  return function (err, res, body) {
    if (err) return cb(err);

    var errOut = null;
    var errMsg = ERR_UNKNOWN;
    var data = null;

    try {
      data = JSON.parse(body);
    } catch (ex) {
      errOut = ex;
    }

    if (data === null || typeof(data) !== 'object' || data.status !== 'success') {
      if (data.error_message) errMsg = data.error_message;
      if (data.data && typeof(data.data) === 'object' &&
        data.data.error_message) errMsg = data.data.error_message;
      errOut = new Error(errMsg);
    }

    return cb(errOut, data);
  };

};

BlockIo.prototype.validate_key = function (cb) {
  // Test if the key is valid by doing a simple balance check

  this._request('GET', 'get_balance', {}, function (err, doc) {
    var errOut = err ||
      (doc.status === 'success') ? null : new Error(ERR_KEY_INV);
    return cb(errOut, (errOut === null));
  });

};

BlockIo.prototype._cloneOptions = function (options) {
  var self = this;
  Object.keys(options).forEach(function (k) {
    self.options[k] = options[k];
  });
};

BlockIo._constructMethod = function (type, callMethod) {
  var fn = ['_', type].join('');
  return function (args, cb) {

    // handle overload for function (cb), without args
    if (!cb && typeof(args) === 'function') {
      cb = args;
      args = {};
    }

    this[fn]('POST', callMethod, args, cb);
  };
};

// generate methods for each valid call method
BlockIo.PASSTHROUGH_METHODS.forEach(function (method) {
  BlockIo.prototype[method] = BlockIo._constructMethod('request', method);
});

BlockIo.WITHDRAWAL_METHODS.forEach(function (method) {
  BlockIo.prototype[method] = BlockIo._constructMethod('withdraw', method);
});

BlockIo.SWEEP_METHODS.forEach(function (method) {
  BlockIo.prototype[method] = BlockIo._constructMethod('sweep', method);
});
<|endoftext|>


<commit_before>(function(exports) {

function ResizableTable($table) {
	var self = this;

	var $_table_container = $("<div/>");
	var $_table = $table;

	var _onResize = undefined;

	// Resize the table with given columns dimensions (try to apply these dimensions)
	// @param sizes Array of dimensions that should be applied to columns
	//     if the number of given dimensions differ from the number of columns: unspecified behaviour
	//     if one of the dimensions is negative: unspecified behaviour
	//     if sum of dimensions different from table's size, will apply a ratio to have the new values
	// @param no_callback Deactivate onResize callback for this call, Default=false
	self.resize = function(sizes, no_callback) {
		console.log("in: " + sizes);
		// move cursors towards expected result
		var $columns = $_table.find("> thead > tr > th");
		if (sizes.length != $columns.length) {
			console.warn("ResizableTable::resize: the number of given dimensions differ from the number of columns");
			return;
		}
		var total_size = 0;
		for (var i = 0 ; i != sizes.length ; ++i) {
			total_size += sizes[i];
		}
		var ratio = $_table_container.outerWidth() / total_size;
		var $cursors = $_table_container.find("> div.resizable-table-cursor");
		var last = 0;
		for (var i = 0 ; i != sizes.length ; ++i) {
			last += ratio * sizes[i];
			$cursors.eq(i).css("left", last);
		}

		// resize the table itself to follow the cursors
		self.resizeToLastKnown(no_callback);

		// move the cursor to their new positions (table not be exactly inlined with cursors that is why we move the cursors)
		self.refresh();
	};

	// Resize the table to fit with last known cursors
	// Table adapts to cursors
	// @param no_callback Deactivate onResize callback for this call, Default=false
	// @return true if it had impacts on table's columns
	self.resizeToLastKnown = function(no_callback) {
		var $cursors = $_table_container.find("> div.resizable-table-cursor");
		var $columns = $_table.find("> thead > tr > th");
		var previous = 0;
		for (var i = 0 ; i != $cursors.length ; ++i) {
			var left = $cursors.eq(i).position().left;
			$columns.eq(i).outerWidth(left-previous);
			previous = left;
		}

		if (no_callback !== true && _onResize !== undefined) {
			var sizes = new Array();
			for (var i = 0 ; i != $columns.length ; ++i) {
				sizes.push($columns.eq(i).outerWidth());
			}
			_onResize(self, $_table, sizes);
		}
	};

	// Refresh the cursors' position to fit with current table's state
	// Cursors adapt to table
	self.refresh = function() {
		// remove existing cursors
		$_table_container.find("> div.resizable-table-cursor").remove();

		// scan table's columns
		var $columns = $_table.find("> thead > tr > th");
		for (var i = 1 ; i < $columns.length ; ++i) {
			var $column = $columns.eq(i);
			var $cursor = $("<div/>");
			$cursor.addClass("resizable-table-cursor");
			$cursor.css("left", $column.position().left);
			$cursor.css("top", 0);
			$cursor.on("mousedown", (function($cursor) {
				return function(e) {
					var move_handler = function(e) {
						var x = e.pageX;
						var newPosition = x - $_table_container.position().left;
						var maxPosition = $_table_container.outerWidth();
						if (newPosition >= 0 && newPosition < maxPosition) {
							$cursor.css("left", newPosition);
							self.resizeToLastKnown();
						}
					};
					$("body").bind("mousemove", move_handler);
					$("body").on("mouseup", function() {
						$("body").unbind("mousemove", move_handler);
						self.refresh();
					});
				};
			})($cursor));
			$_table_container.append($cursor);
		}
	};

	// Callback will be called each time a resize operation handled by ResizableTable
	// imply changes columns's dimension
	// Callback receives the parameters:
	//  - self:   instance of ResizableTable
	//  - $table: jQuery DOM of the table
	//  - sizes:  column's dimensions
	self.registerOnResize = function(callback) {
		_onResize = callback;
		return self;
	};

	{
		$_table_container.addClass("resizable-table-container");
		$_table.before($_table_container); // create table container to wrap the table (created before the table element)
		$_table_container.append($_table); // move table into the container
		self.refresh();
	}
}

exports.ResizableTable = ResizableTable;

}(typeof exports === 'undefined'
		? (this['JsTools'] === undefined ? this['JsTools']={} : this['JsTools'])
		: exports));

var ResizableTable = JsTools.ResizableTable;
<commit_msg>[resizable] Better handling of same dimensions between tables<commit_after>(function(exports) {

function ResizableTable($table) {
	var self = this;

	var $_table_container = $("<div/>");
	var $_table = $table;

	var _onResize = undefined;

	// Resize the table with given columns dimensions (try to apply these dimensions)
	// @param sizes Array of dimensions that should be applied to columns
	//     if the number of given dimensions differ from the number of columns: unspecified behaviour
	//     if one of the dimensions is negative: unspecified behaviour
	//     if sum of dimensions different from table's size, will apply a ratio to have the new values
	// @param no_callback Deactivate onResize callback for this call, Default=false
	self.resize = function(sizes, no_callback) {
		// move cursors towards expected result
		var $columns = $_table.find("> thead > tr > th");
		if (sizes.length != $columns.length) {
			console.warn("ResizableTable::resize: the number of given dimensions differ from the number of columns");
			return;
		}
		var total_size = 0;
		for (var i = 0 ; i != sizes.length ; ++i) {
			total_size += sizes[i];
		}
		var ratio = $_table_container.outerWidth() / total_size;
		var $cursors = $_table_container.find("> div.resizable-table-cursor");
		var last = 0;
		for (var i = 0 ; i != sizes.length ; ++i) {
			last += ratio * sizes[i];
			$cursors.eq(i).css("left", last);
		}

		// resize the table itself to follow the cursors
		self.resizeToLastKnown(true);

		// move the cursor to their new positions (table not be exactly inlined with cursors that is why we move the cursors)
		self.refresh(no_callback);
	};

	// Resize the table to fit with last known cursors
	// Table adapts to cursors
	// @param no_callback Deactivate onResize callback for this call, Default=false
	self.resizeToLastKnown = function(no_callback) {
		var $cursors = $_table_container.find("> div.resizable-table-cursor");
		var $columns = $_table.find("> thead > tr > th");
		var previous = 0;
		for (var i = 0 ; i != $cursors.length ; ++i) {
			var left = $cursors.eq(i).position().left;
			$columns.eq(i).outerWidth(left-previous);
			previous = left;
		}

		if (no_callback !== true && _onResize !== undefined) {
			var sizes = new Array();
			for (var i = 0 ; i != $columns.length ; ++i) {
				sizes.push($columns.eq(i).outerWidth());
			}
			_onResize(self, $_table, sizes);
		}
	};

	// Refresh the cursors' position to fit with current table's state
	// Cursors adapt to table
	self.refresh = function(no_callback) {
		// remove existing cursors
		$_table_container.find("> div.resizable-table-cursor").remove();

		// scan table's columns
		var $columns = $_table.find("> thead > tr > th");
		for (var i = 1 ; i < $columns.length ; ++i) {
			var $column = $columns.eq(i);
			var $cursor = $("<div/>");
			$cursor.addClass("resizable-table-cursor");
			$cursor.css("left", $column.position().left);
			$cursor.css("top", 0);
			$cursor.on("mousedown", (function($cursor) {
				return function(e) {
					var move_handler = function(e) {
						var x = e.pageX;
						var newPosition = x - $_table_container.position().left;
						var maxPosition = $_table_container.outerWidth();
						if (newPosition >= 0 && newPosition < maxPosition) {
							$cursor.css("left", newPosition);
							self.resizeToLastKnown();
						}
					};
					$("body").bind("mousemove", move_handler);
					$("body").on("mouseup", function() {
						$("body").unbind("mousemove", move_handler);
						self.refresh();
					});
				};
			})($cursor));
			$_table_container.append($cursor);
		}

		if (no_callback !== true && _onResize !== undefined) {
			var sizes = new Array();
			for (var i = 0 ; i != $columns.length ; ++i) {
				sizes.push($columns.eq(i).outerWidth());
			}
			_onResize(self, $_table, sizes);
		}
	};

	// Callback will be called each time a resize operation handled by ResizableTable
	// imply changes columns's dimension
	// Callback receives the parameters:
	//  - self:   instance of ResizableTable
	//  - $table: jQuery DOM of the table
	//  - sizes:  column's dimensions
	self.registerOnResize = function(callback) {
		_onResize = callback;
		return self;
	};

	{
		$_table_container.addClass("resizable-table-container");
		$_table.before($_table_container); // create table container to wrap the table (created before the table element)
		$_table_container.append($_table); // move table into the container
		self.refresh();
	}
}

exports.ResizableTable = ResizableTable;

}(typeof exports === 'undefined'
		? (this['JsTools'] === undefined ? this['JsTools']={} : this['JsTools'])
		: exports));

var ResizableTable = JsTools.ResizableTable;
<|endoftext|>


<commit_before>import React from 'react'
import {Alert, Linking, Platform, Share, StyleSheet, Text, TouchableWithoutFeedback, View, WebView} from 'react-native'

import {Images} from '/images'
import {Styles} from '/styles'
import {BaseComponent, ImageButton} from '/widgets'

import {ItemMedia} from './itemMedia'

const styles = {
    headerActions: {
        flexDirection: 'row',
    },
    page: {
        flex: 1,
    },
    titleContainer: {
        justifyContent: 'center',
        marginRight: 56, // NOTE: hack around floating title component.
    },
    title: {
        backgroundColor: Styles.Color.Clear,
        color: Styles.Color.White,
        fontFamily: Styles.Font.Family.RobotoMedium,
        fontSize: Styles.Font.Size.Small,
    },
    subTitle: {
        backgroundColor: Styles.Color.Clear,
        color: Styles.Color.White,
        fontFamily: Styles.Font.Family.RobotoRegular,
        fontSize: Styles.Font.Size.XSmall,
    },
}

const stylesheet = StyleSheet.create(styles)

/**
 * A component that summarizes the item (e.g. title + url) in the navigation header.
 */
class DetailsScreenHeader extends BaseComponent {
    showInfo = () => {
        Alert.alert(
            'Details',
            (this.props.title + '\n\nFrom: ' + this.props.subTitle),
            [ {text: 'Close',  style: 'cancel'} ],
            { cancelable: true }
        )
    }

    render() {
        return (
            <TouchableWithoutFeedback onPress={this.showInfo}>
                <View style={stylesheet.titleContainer}>
                    <Text numberOfLines={1}
                          style={stylesheet.title}>
                        {this.props.title}
                    </Text>
                    <Text numberOfLines={1}
                          style={stylesheet.subTitle}>
                        {this.props.subTitle}
                    </Text>
                </View>
            </TouchableWithoutFeedback>
        )
    }
}

/**
 * Component for the action (e.g. share, open source) area of the detail screen's navigation header.
 */
class HeaderActions extends BaseComponent {
    shareItem = () => {
        Share.share({
            title: this.props.item.title,
            message: this.props.item.url,
        })
    }

    openItem = () => {
        Linking.openURL(this.props.item.url)
    }

    render() {
        return (
            <View style={stylesheet.headerActions}>
                <ImageButton imageSource={Images.share}
                             onPress={this.shareItem} />

                <ImageButton imageSource={Images.openNew}
                             onPress={this.openItem} />
            </View>
        )
    }
}

/**
 * Screen used to display the details on an item (e.g. full screen media preview).
 *
 * Required navigation params:
 *   title: The title of the item
 *   url: The final URL (e.g. reddit post) of the item
 */
class DetailsScreen extends BaseComponent {
    static navigationOptions = ({navigation}) => {
        const item = navigation.state.params.item

        const subTitle = [item.packTitle, item.sourceTitle]
            .filter((item) => (item))
            .join(' / ')

        return {
            gesturesEnabled: true,
            headerTitle: (
                <DetailsScreenHeader subTitle={subTitle}
                                     title={item.title} />
            ),
            headerRight: (<HeaderActions item={item} />),
        }
    }

    render() {
        const item = this.props.navigation.state.params.item

        return (
            <ItemMedia mediaType={item.mediaType}
                       url={item.mediaUrl} />
        )
    }
}

export {
    DetailsScreen,
}
<commit_msg>Small improvement to sharing copy<commit_after>import React from 'react'
import {Alert, Linking, Platform, Share, StyleSheet, Text, TouchableWithoutFeedback, View, WebView} from 'react-native'

import {Images} from '/images'
import {Styles} from '/styles'
import {BaseComponent, ImageButton} from '/widgets'

import {ItemMedia} from './itemMedia'

const styles = {
    headerActions: {
        flexDirection: 'row',
    },
    page: {
        flex: 1,
    },
    titleContainer: {
        justifyContent: 'center',
        marginRight: 56, // NOTE: hack around floating title component.
    },
    title: {
        backgroundColor: Styles.Color.Clear,
        color: Styles.Color.White,
        fontFamily: Styles.Font.Family.RobotoMedium,
        fontSize: Styles.Font.Size.Small,
    },
    subTitle: {
        backgroundColor: Styles.Color.Clear,
        color: Styles.Color.White,
        fontFamily: Styles.Font.Family.RobotoRegular,
        fontSize: Styles.Font.Size.XSmall,
    },
}

const stylesheet = StyleSheet.create(styles)

/**
 * A component that summarizes the item (e.g. title + url) in the navigation header.
 */
class DetailsScreenHeader extends BaseComponent {
    showInfo = () => {
        Alert.alert(
            'Details',
            (this.props.title + '\n\nFrom: ' + this.props.subTitle),
            [ {text: 'Close',  style: 'cancel'} ],
            { cancelable: true }
        )
    }

    render() {
        return (
            <TouchableWithoutFeedback onPress={this.showInfo}>
                <View style={stylesheet.titleContainer}>
                    <Text numberOfLines={1}
                          style={stylesheet.title}>
                        {this.props.title}
                    </Text>
                    <Text numberOfLines={1}
                          style={stylesheet.subTitle}>
                        {this.props.subTitle}
                    </Text>
                </View>
            </TouchableWithoutFeedback>
        )
    }
}

/**
 * Component for the action (e.g. share, open source) area of the detail screen's navigation header.
 */
class HeaderActions extends BaseComponent {
    shareItem = () => {
        Share.share({
            title: this.props.item.title,
            message: this.props.item.url + ' (via NewsCats)',
        })
    }

    openItem = () => {
        Linking.openURL(this.props.item.url)
    }

    render() {
        return (
            <View style={stylesheet.headerActions}>
                <ImageButton imageSource={Images.share}
                             onPress={this.shareItem} />

                <ImageButton imageSource={Images.openNew}
                             onPress={this.openItem} />
            </View>
        )
    }
}

/**
 * Screen used to display the details on an item (e.g. full screen media preview).
 *
 * Required navigation params:
 *   title: The title of the item
 *   url: The final URL (e.g. reddit post) of the item
 */
class DetailsScreen extends BaseComponent {
    static navigationOptions = ({navigation}) => {
        const item = navigation.state.params.item

        const subTitle = [item.packTitle, item.sourceTitle]
            .filter((item) => (item))
            .join(' / ')

        return {
            gesturesEnabled: true,
            headerTitle: (
                <DetailsScreenHeader subTitle={subTitle}
                                     title={item.title} />
            ),
            headerRight: (<HeaderActions item={item} />),
        }
    }

    render() {
        const item = this.props.navigation.state.params.item

        return (
            <ItemMedia mediaType={item.mediaType}
                       url={item.mediaUrl} />
        )
    }
}

export {
    DetailsScreen,
}
<|endoftext|>