<commit_before>// Copyright 2021, University of Colorado Boulder

/**
 * Return an object of data about the simulation and the browser
 * much of the code was largely copied and expanded on from SimTroubleshootPage.html in the website repo. Note that
 * key names in the info object are used by the PhET-iO API, do not change without great consideration.
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

import Utils from '../../scenery/js/util/Utils.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import Tandem from '../../tandem/js/Tandem.js';
import ArrayIO from '../../tandem/js/types/ArrayIO.js';
import IOType from '../../tandem/js/types/IOType.js';
import NullableIO from '../../tandem/js/types/NullableIO.js';
import NumberIO from '../../tandem/js/types/NumberIO.js';
import ObjectLiteralIO from '../../tandem/js/types/ObjectLiteralIO.js';
import StringIO from '../../tandem/js/types/StringIO.js';
import joist from './joist.js';
import packageJSON from './packageJSON.js';

class SimInfo extends PhetioObject {

  constructor( sim ) {
    super( {
      tandem: Tandem.GENERAL_MODEL.createTandem( 'simInfo' ),
      phetioType: SimInfo.SimInfoIO,
      phetioReadOnly: true,
      phetioDocumentation: 'A collection of data about the runtime and simulation. Available in the simStarted PhET-iO ' +
                           'data stream event, as well as on demand in the PhET-iO state.'
    } );

    assert && assert( Array.isArray( sim.screens ), 'screens should be set and an array' );

    // @public (SimInfoIO)
    this.info = {};

    // globals
    this.putInfo( 'url', window.location.href );
    this.putInfo( 'randomSeed', window.phet.chipper.queryParameters.randomSeed );
    this.putInfo( 'userAgent', window.navigator.userAgent );
    this.putInfo( 'language', window.navigator.language );
    this.putInfo( 'window', `${window.innerWidth}x${window.innerHeight}` );
    this.putInfo( 'referrer', document.referrer );

    // from Scenery Utils
    this.putInfo( 'checkIE11StencilSupport', Utils.checkIE11StencilSupport() );
    if ( phet.chipper.queryParameters.webgl ) {
      this.putInfo( 'isWebGLSupported', Utils.isWebGLSupported );
    }

    let canvas;
    let context;
    let backingStorePixelRatio;

    try {
      canvas = document.createElement( 'canvas' );
      context = canvas.getContext( '2d' );
      backingStorePixelRatio = Utils.backingStorePixelRatio( context );
    }
    catch( e ) {} // eslint-disable-line

    this.putInfo( 'pixelRatio', `${window.devicePixelRatio || 1}/${backingStorePixelRatio}` );

    const flags = [];
    if ( window.navigator.pointerEnabled ) { flags.push( 'pointerEnabled' ); }
    if ( window.navigator.msPointerEnabled ) { flags.push( 'msPointerEnabled' ); }
    if ( !window.navigator.onLine ) { flags.push( 'offline' ); }
    if ( ( window.devicePixelRatio || 1 ) / backingStorePixelRatio !== 1 ) { flags.push( 'pixelRatioScaling' ); }
    this.putInfo( 'flags', flags.join( ', ' ) );

    canvas = null; // dispose only reference

    // from Sim.js
    this.putInfo( 'simName', sim.simNameProperty.value );
    this.putInfo( 'simVersion', sim.version );
    this.putInfo( 'repoName', packageJSON.name );
    this.putInfo( 'screens', sim.screens.map( screen => {
      const screenObject = {

        // likely null for single screen sims, so use the sim name as a default
        name: screen.nameProperty.value || sim.simNameProperty.value
      };
      if ( Tandem.PHET_IO_ENABLED ) {
        screenObject.phetioID = screen.tandem.phetioID;
      }
      return screenObject;
    } ) );

    // From PhET-iO code
    if ( Tandem.PHET_IO_ENABLED ) {
      this.putInfo( 'screenPropertyValue', sim.screenProperty.value.tandem.phetioID );
      this.putInfo( 'wrapperMetadata', phet.preloads.phetio.simStartedMetadata );
      this.putInfo( 'dataStreamVersion', phet.phetio.dataStream.VERSION );
      this.putInfo( 'phetioCommandProcessorProtocol', phet.phetio.phetioCommandProcessor.PHET_IO_PROTOCOL );
    }
  }

  /**
   * @private
   * @param {string} key
   * @param {*} value
   */
  putInfo( key, value ) {
    if ( value === undefined ) {
      value = '{{undefined}}';
    }
    assert && assert( !this.info.hasOwnProperty( key ), `key already defined: ${key}` );
    this.info[ key ] = value;
  }
}

// @private
SimInfo.SimInfoIO = new IOType( 'SimInfoIO', {
  valueType: SimInfo,
  toStateObject: simInfo => {
    return {
      simName: simInfo.info.simName,
      screens: simInfo.info.screens,
      repoName: simInfo.info.repoName,

      screenPropertyValue: simInfo.info.screenPropertyValue,
      wrapperMetadata: simInfo.info.wrapperMetadata,
      dataStreamVersion: simInfo.info.dataStreamVersion,
      phetioCommandProcessorProtocol: simInfo.info.phetioCommandProcessorProtocol,

      simVersion: Tandem.API_GENERATION ? null : simInfo.info.simVersion,
      randomSeed: Tandem.API_GENERATION ? null : window.phet.chipper.queryParameters.randomSeed,
      url: Tandem.API_GENERATION ? null : window.location.href,
      userAgent: Tandem.API_GENERATION ? null : window.navigator.userAgent,
      window: Tandem.API_GENERATION ? null : `${window.innerWidth}x${window.innerHeight}`,
      referrer: Tandem.API_GENERATION ? null : document.referrer,
      flags: Tandem.API_GENERATION ? null : simInfo.flags || null
    };
  },
  stateSchema: {
    simName: StringIO,
    screens: ArrayIO( ObjectLiteralIO ),
    repoName: StringIO,

    screenPropertyValue: StringIO,
    wrapperMetadata: NullableIO( ObjectLiteralIO ),
    dataStreamVersion: StringIO,
    phetioCommandProcessorProtocol: StringIO,

    // Parts that are omitted in API generation
    simVersion: NullableIO( StringIO ),
    randomSeed: NullableIO( NumberIO ),
    url: NullableIO( StringIO ),
    userAgent: NullableIO( StringIO ),
    window: NullableIO( StringIO ),
    referrer: NullableIO( StringIO ),
    flags: NullableIO( StringIO )
  }
} );

joist.register( 'SimInfo', SimInfo );
export default SimInfo;<commit_msg>Include more information in SimInfo state (when not generating API), see https://github.com/phetsims/gravity-and-orbits/issues/387<commit_after>// Copyright 2021, University of Colorado Boulder

/**
 * Return an object of data about the simulation and the browser
 * much of the code was largely copied and expanded on from SimTroubleshootPage.html in the website repo. Note that
 * key names in the info object are used by the PhET-iO API, do not change without great consideration.
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

import Utils from '../../scenery/js/util/Utils.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import Tandem from '../../tandem/js/Tandem.js';
import ArrayIO from '../../tandem/js/types/ArrayIO.js';
import BooleanIO from '../../tandem/js/types/BooleanIO.js';
import IOType from '../../tandem/js/types/IOType.js';
import NullableIO from '../../tandem/js/types/NullableIO.js';
import NumberIO from '../../tandem/js/types/NumberIO.js';
import ObjectLiteralIO from '../../tandem/js/types/ObjectLiteralIO.js';
import StringIO from '../../tandem/js/types/StringIO.js';
import joist from './joist.js';
import packageJSON from './packageJSON.js';

class SimInfo extends PhetioObject {

  constructor( sim ) {
    super( {
      tandem: Tandem.GENERAL_MODEL.createTandem( 'simInfo' ),
      phetioType: SimInfo.SimInfoIO,
      phetioReadOnly: true,
      phetioDocumentation: 'A collection of data about the runtime and simulation. Available in the simStarted PhET-iO ' +
                           'data stream event, as well as on demand in the PhET-iO state.'
    } );

    assert && assert( Array.isArray( sim.screens ), 'screens should be set and an array' );

    // @public (SimInfoIO)
    this.info = {};

    // globals
    this.putInfo( 'url', window.location.href );
    this.putInfo( 'randomSeed', window.phet.chipper.queryParameters.randomSeed );
    this.putInfo( 'userAgent', window.navigator.userAgent );
    this.putInfo( 'language', window.navigator.language );
    this.putInfo( 'window', `${window.innerWidth}x${window.innerHeight}` );
    this.putInfo( 'referrer', document.referrer );

    // from Scenery Utils
    this.putInfo( 'checkIE11StencilSupport', Utils.checkIE11StencilSupport() );
    if ( phet.chipper.queryParameters.webgl ) {
      this.putInfo( 'isWebGLSupported', Utils.isWebGLSupported );
    }

    let canvas;
    let context;
    let backingStorePixelRatio;

    try {
      canvas = document.createElement( 'canvas' );
      context = canvas.getContext( '2d' );
      backingStorePixelRatio = Utils.backingStorePixelRatio( context );
    }
    catch( e ) {} // eslint-disable-line

    this.putInfo( 'pixelRatio', `${window.devicePixelRatio || 1}/${backingStorePixelRatio}` );

    const flags = [];
    if ( window.navigator.pointerEnabled ) { flags.push( 'pointerEnabled' ); }
    if ( window.navigator.msPointerEnabled ) { flags.push( 'msPointerEnabled' ); }
    if ( !window.navigator.onLine ) { flags.push( 'offline' ); }
    if ( ( window.devicePixelRatio || 1 ) / backingStorePixelRatio !== 1 ) { flags.push( 'pixelRatioScaling' ); }
    this.putInfo( 'flags', flags.join( ', ' ) );

    canvas = null; // dispose only reference

    // from Sim.js
    this.putInfo( 'simName', sim.simNameProperty.value );
    this.putInfo( 'simVersion', sim.version );
    this.putInfo( 'repoName', packageJSON.name );
    this.putInfo( 'screens', sim.screens.map( screen => {
      const screenObject = {

        // likely null for single screen sims, so use the sim name as a default
        name: screen.nameProperty.value || sim.simNameProperty.value
      };
      if ( Tandem.PHET_IO_ENABLED ) {
        screenObject.phetioID = screen.tandem.phetioID;
      }
      return screenObject;
    } ) );

    // From PhET-iO code
    if ( Tandem.PHET_IO_ENABLED ) {
      this.putInfo( 'screenPropertyValue', sim.screenProperty.value.tandem.phetioID );
      this.putInfo( 'wrapperMetadata', phet.preloads.phetio.simStartedMetadata );
      this.putInfo( 'dataStreamVersion', phet.phetio.dataStream.VERSION );
      this.putInfo( 'phetioCommandProcessorProtocol', phet.phetio.phetioCommandProcessor.PHET_IO_PROTOCOL );
    }
  }

  /**
   * @private
   * @param {string} key
   * @param {*} value
   */
  putInfo( key, value ) {
    if ( value === undefined ) {
      value = '{{undefined}}';
    }
    assert && assert( !this.info.hasOwnProperty( key ), `key already defined: ${key}` );
    this.info[ key ] = value;
  }
}

// @private
SimInfo.SimInfoIO = new IOType( 'SimInfoIO', {
  valueType: SimInfo,
  toStateObject: simInfo => {
    return {
      simName: simInfo.info.simName,
      screens: simInfo.info.screens,
      repoName: simInfo.info.repoName,

      screenPropertyValue: simInfo.info.screenPropertyValue,
      dataStreamVersion: simInfo.info.dataStreamVersion,
      phetioCommandProcessorProtocol: simInfo.info.phetioCommandProcessorProtocol,

      simVersion: Tandem.API_GENERATION ? null : simInfo.info.simVersion,
      wrapperMetadata: Tandem.API_GENERATION ? null : simInfo.info.wrapperMetadata,
      randomSeed: Tandem.API_GENERATION ? null : simInfo.info.randomSeed,
      url: Tandem.API_GENERATION ? null : simInfo.info.url,
      userAgent: Tandem.API_GENERATION ? null : simInfo.info.userAgent,
      window: Tandem.API_GENERATION ? null : simInfo.info.window,
      referrer: Tandem.API_GENERATION ? null : simInfo.info.referrer,
      language: Tandem.API_GENERATION ? null : simInfo.info.language,
      pixelRatio: Tandem.API_GENERATION ? null : simInfo.info.pixelRatio,
      isWebGLSupported: Tandem.API_GENERATION ? null : simInfo.info.isWebGLSupported,
      checkIE11StencilSupport: Tandem.API_GENERATION ? null : simInfo.info.checkIE11StencilSupport,
      flags: Tandem.API_GENERATION ? null : simInfo.info.flags || null
    };
  },
  stateSchema: {
    simName: StringIO,
    screens: ArrayIO( ObjectLiteralIO ),
    repoName: StringIO,

    screenPropertyValue: StringIO,
    wrapperMetadata: NullableIO( ObjectLiteralIO ),
    dataStreamVersion: StringIO,
    phetioCommandProcessorProtocol: StringIO,

    // Parts that are omitted in API generation
    simVersion: NullableIO( StringIO ),
    randomSeed: NullableIO( NumberIO ),
    url: NullableIO( StringIO ),
    userAgent: NullableIO( StringIO ),
    window: NullableIO( StringIO ),
    referrer: NullableIO( StringIO ),
    language: NullableIO( StringIO ),
    pixelRatio: NullableIO( StringIO ),
    isWebGLSupported: NullableIO( BooleanIO ),
    checkIE11StencilSupport: NullableIO( BooleanIO ),
    flags: NullableIO( StringIO )
  }
} );

joist.register( 'SimInfo', SimInfo );
export default SimInfo;<|endoftext|>


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

/**
 * Basic 2-dimensional vector
 *
 * @author Jonathan Olson <olsonsjc@gmail.com>
 */

define( function( require ) {
  'use strict';

  var assert = require( 'ASSERT/assert' )( 'dot' );

  var dot = require( 'DOT/dot' );

  var inherit = require( 'PHET_CORE/inherit' );
  require( 'DOT/Util' );
  // require( 'DOT/Vector3' ); // commented out since Require.js complains about the circular dependency

  dot.Vector2 = function Vector2( x, y ) {
    // allow optional parameters
    this.x = x || 0;
    this.y = y || 0;

    assert && assert( typeof this.x === 'number', 'x needs to be a number' );
    assert && assert( typeof this.y === 'number', 'y needs to be a number' );
  };
  var Vector2 = dot.Vector2;

  Vector2.createPolar = function( magnitude, angle ) {
    return new Vector2( magnitude * Math.cos( angle ), magnitude * Math.sin( angle ) );
  };

  Vector2.prototype = {
    constructor: Vector2,

    isVector2: true,

    dimension: 2,

    magnitude: function() {
      return Math.sqrt( this.magnitudeSquared() );
    },

    magnitudeSquared: function() {
      return this.dot( this );
    },

    // the distance between this vector (treated as a point) and another point
    distance: function( point ) {
      return this.minus( point ).magnitude();
    },

    // the squared distance between this vector (treated as a point) and another point
    distanceSquared: function( point ) {
      return this.minus( point ).magnitudeSquared();
    },

    dot: function( v ) {
      return this.x * v.x + this.y * v.y;
    },

    equals: function( other ) {
      return this.x === other.x && this.y === other.y;
    },

    equalsEpsilon: function( other, epsilon ) {
      if ( !epsilon ) {
        epsilon = 0;
      }
      return Math.max( Math.abs( this.x - other.x ), Math.abs( this.y - other.y ) ) <= epsilon;
    },

    isFinite: function() {
      return isFinite( this.x ) && isFinite( this.y );
    },

    /*---------------------------------------------------------------------------*
     * Immutables
     *----------------------------------------------------------------------------*/

    copy: function() {
      return new Vector2( this.x, this.y );
    },

    // z component of the equivalent 3-dimensional cross product (this.x, this.y,0) x (v.x, v.y, 0)
    crossScalar: function( v ) {
      return this.x * v.y - this.y * v.x;
    },

    normalized: function() {
      var mag = this.magnitude();
      if ( mag === 0 ) {
        throw new Error( "Cannot normalize a zero-magnitude vector" );
      }
      else {
        return new Vector2( this.x / mag, this.y / mag );
      }
    },

    timesScalar: function( scalar ) {
      return new Vector2( this.x * scalar, this.y * scalar );
    },

    times: function( scalar ) {
      // make sure it's not a vector!
      assert && assert( scalar.dimension === undefined );
      return this.timesScalar( scalar );
    },

    componentTimes: function( v ) {
      return new Vector2( this.x * v.x, this.y * v.y );
    },

    plus: function( v ) {
      return new Vector2( this.x + v.x, this.y + v.y );
    },

    plusScalar: function( scalar ) {
      return new Vector2( this.x + scalar, this.y + scalar );
    },

    minus: function( v ) {
      return new Vector2( this.x - v.x, this.y - v.y );
    },

    minusScalar: function( scalar ) {
      return new Vector2( this.x - scalar, this.y - scalar );
    },

    dividedScalar: function( scalar ) {
      return new Vector2( this.x / scalar, this.y / scalar );
    },

    negated: function() {
      return new Vector2( -this.x, -this.y );
    },

    angle: function() {
      return Math.atan2( this.y, this.x );
    },

    // equivalent to a -PI/2 rotation (right hand rotation)
    perpendicular: function() {
      return new Vector2( this.y, -this.x );
    },

    angleBetween: function( v ) {
      return Math.acos( dot.clamp( this.normalized().dot( v.normalized() ), -1, 1 ) );
    },

    rotated: function( angle ) {
      var newAngle = this.angle() + angle;
      return new Vector2( Math.cos( newAngle ), Math.sin( newAngle ) ).timesScalar( this.magnitude() );
    },

    // linear interpolation from this (ratio=0) to vector (ratio=1)
    blend: function( vector, ratio ) {
      return new Vector2( this.x + (vector.x - this.x) * ratio, this.y + (vector.y - this.y) * ratio );
    },

    toString: function() {
      return "Vector2(" + this.x + ", " + this.y + ")";
    },

    toVector3: function() {
      return new dot.Vector3( this.x, this.y );
    },

    /*---------------------------------------------------------------------------*
     * Mutables
     *----------------------------------------------------------------------------*/

    set: function( x, y ) {
      this.x = x;
      this.y = y;
      return this;
    },

    setX: function( x ) {
      this.x = x;
      return this;
    },

    setY: function( y ) {
      this.y = y;
      return this;
    },

    add: function( v ) {
      this.x += v.x;
      this.y += v.y;
      return this;
    },

    addScalar: function( scalar ) {
      this.x += scalar;
      this.y += scalar;
      return this;
    },

    subtract: function( v ) {
      this.x -= v.x;
      this.y -= v.y;
      return this;
    },

    subtractScalar: function( scalar ) {
      this.x -= scalar;
      this.y -= scalar;
      return this;
    },

    componentMultiply: function( v ) {
      this.x *= v.x;
      this.y *= v.y;
      return this;
    },

    divideScalar: function( scalar ) {
      this.x /= scalar;
      this.y /= scalar;
      return this;
    },

    negate: function() {
      this.x = -this.x;
      this.y = -this.y;
      return this;
    }

  };

  /*---------------------------------------------------------------------------*
   * Immutable Vector form
   *----------------------------------------------------------------------------*/
  Vector2.Immutable = function ImmutableVector2( x, y ) {
    Vector2.call( this, x, y );
  };
  var Immutable = Vector2.Immutable;

  inherit( Vector2, Immutable );

  // throw errors whenever a mutable method is called on our immutable vector
  Immutable.mutableOverrideHelper = function( mutableFunctionName ) {
    Immutable.prototype[mutableFunctionName] = function() {
      throw new Error( "Cannot call mutable method '" + mutableFunctionName + "' on immutable Vector2" );
    };
  };

  // TODO: better way to handle this list?
  Immutable.mutableOverrideHelper( 'set' );
  Immutable.mutableOverrideHelper( 'setX' );
  Immutable.mutableOverrideHelper( 'setY' );
  Immutable.mutableOverrideHelper( 'copy' );
  Immutable.mutableOverrideHelper( 'add' );
  Immutable.mutableOverrideHelper( 'addScalar' );
  Immutable.mutableOverrideHelper( 'subtract' );
  Immutable.mutableOverrideHelper( 'subtractScalar' );
  Immutable.mutableOverrideHelper( 'componentMultiply' );
  Immutable.mutableOverrideHelper( 'divideScalar' );
  Immutable.mutableOverrideHelper( 'negate' );

  // helpful immutable constants
  Vector2.ZERO = new Immutable( 0, 0 );
  Vector2.X_UNIT = new Immutable( 1, 0 );
  Vector2.Y_UNIT = new Immutable( 0, 1 );

  return Vector2;
} );
<commit_msg>Dot micro-optimization to remove instance allocation<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Basic 2-dimensional vector
 *
 * @author Jonathan Olson <olsonsjc@gmail.com>
 */

define( function( require ) {
  'use strict';

  var assert = require( 'ASSERT/assert' )( 'dot' );

  var dot = require( 'DOT/dot' );

  var inherit = require( 'PHET_CORE/inherit' );
  require( 'DOT/Util' );
  // require( 'DOT/Vector3' ); // commented out since Require.js complains about the circular dependency

  dot.Vector2 = function Vector2( x, y ) {
    // allow optional parameters
    this.x = x || 0;
    this.y = y || 0;

    assert && assert( typeof this.x === 'number', 'x needs to be a number' );
    assert && assert( typeof this.y === 'number', 'y needs to be a number' );
  };
  var Vector2 = dot.Vector2;

  Vector2.createPolar = function( magnitude, angle ) {
    return new Vector2( magnitude * Math.cos( angle ), magnitude * Math.sin( angle ) );
  };

  Vector2.prototype = {
    constructor: Vector2,

    isVector2: true,

    dimension: 2,

    magnitude: function() {
      return Math.sqrt( this.magnitudeSquared() );
    },

    magnitudeSquared: function() {
      return this.x * this.x + this.y * this.y;
    },

    // the distance between this vector (treated as a point) and another point
    distance: function( point ) {
      return Math.sqrt( this.distanceSquared( point ) );
    },

    // the squared distance between this vector (treated as a point) and another point
    distanceSquared: function( point ) {
      var dx = this.x - point.x;
      var dy = this.y - point.y;
      return dx * dx + dy * dy;
    },

    dot: function( v ) {
      return this.x * v.x + this.y * v.y;
    },

    equals: function( other ) {
      return this.x === other.x && this.y === other.y;
    },

    equalsEpsilon: function( other, epsilon ) {
      if ( !epsilon ) {
        epsilon = 0;
      }
      return Math.max( Math.abs( this.x - other.x ), Math.abs( this.y - other.y ) ) <= epsilon;
    },

    isFinite: function() {
      return isFinite( this.x ) && isFinite( this.y );
    },

    /*---------------------------------------------------------------------------*
     * Immutables
     *----------------------------------------------------------------------------*/

    copy: function() {
      return new Vector2( this.x, this.y );
    },

    // z component of the equivalent 3-dimensional cross product (this.x, this.y,0) x (v.x, v.y, 0)
    crossScalar: function( v ) {
      return this.x * v.y - this.y * v.x;
    },

    normalized: function() {
      var mag = this.magnitude();
      if ( mag === 0 ) {
        throw new Error( "Cannot normalize a zero-magnitude vector" );
      }
      else {
        return new Vector2( this.x / mag, this.y / mag );
      }
    },

    timesScalar: function( scalar ) {
      return new Vector2( this.x * scalar, this.y * scalar );
    },

    times: function( scalar ) {
      // make sure it's not a vector!
      assert && assert( scalar.dimension === undefined );
      return this.timesScalar( scalar );
    },

    componentTimes: function( v ) {
      return new Vector2( this.x * v.x, this.y * v.y );
    },

    plus: function( v ) {
      return new Vector2( this.x + v.x, this.y + v.y );
    },

    plusScalar: function( scalar ) {
      return new Vector2( this.x + scalar, this.y + scalar );
    },

    minus: function( v ) {
      return new Vector2( this.x - v.x, this.y - v.y );
    },

    minusScalar: function( scalar ) {
      return new Vector2( this.x - scalar, this.y - scalar );
    },

    dividedScalar: function( scalar ) {
      return new Vector2( this.x / scalar, this.y / scalar );
    },

    negated: function() {
      return new Vector2( -this.x, -this.y );
    },

    angle: function() {
      return Math.atan2( this.y, this.x );
    },

    // equivalent to a -PI/2 rotation (right hand rotation)
    perpendicular: function() {
      return new Vector2( this.y, -this.x );
    },

    angleBetween: function( v ) {
      return Math.acos( dot.clamp( this.normalized().dot( v.normalized() ), -1, 1 ) );
    },

    rotated: function( angle ) {
      var newAngle = this.angle() + angle;
      return new Vector2( Math.cos( newAngle ), Math.sin( newAngle ) ).timesScalar( this.magnitude() );
    },

    // linear interpolation from this (ratio=0) to vector (ratio=1)
    blend: function( vector, ratio ) {
      return new Vector2( this.x + (vector.x - this.x) * ratio, this.y + (vector.y - this.y) * ratio );
    },

    toString: function() {
      return "Vector2(" + this.x + ", " + this.y + ")";
    },

    toVector3: function() {
      return new dot.Vector3( this.x, this.y );
    },

    /*---------------------------------------------------------------------------*
     * Mutables
     *----------------------------------------------------------------------------*/

    set: function( x, y ) {
      this.x = x;
      this.y = y;
      return this;
    },

    setX: function( x ) {
      this.x = x;
      return this;
    },

    setY: function( y ) {
      this.y = y;
      return this;
    },

    add: function( v ) {
      this.x += v.x;
      this.y += v.y;
      return this;
    },

    addScalar: function( scalar ) {
      this.x += scalar;
      this.y += scalar;
      return this;
    },

    subtract: function( v ) {
      this.x -= v.x;
      this.y -= v.y;
      return this;
    },

    subtractScalar: function( scalar ) {
      this.x -= scalar;
      this.y -= scalar;
      return this;
    },

    componentMultiply: function( v ) {
      this.x *= v.x;
      this.y *= v.y;
      return this;
    },

    divideScalar: function( scalar ) {
      this.x /= scalar;
      this.y /= scalar;
      return this;
    },

    negate: function() {
      this.x = -this.x;
      this.y = -this.y;
      return this;
    }

  };

  /*---------------------------------------------------------------------------*
   * Immutable Vector form
   *----------------------------------------------------------------------------*/
  Vector2.Immutable = function ImmutableVector2( x, y ) {
    Vector2.call( this, x, y );
  };
  var Immutable = Vector2.Immutable;

  inherit( Vector2, Immutable );

  // throw errors whenever a mutable method is called on our immutable vector
  Immutable.mutableOverrideHelper = function( mutableFunctionName ) {
    Immutable.prototype[mutableFunctionName] = function() {
      throw new Error( "Cannot call mutable method '" + mutableFunctionName + "' on immutable Vector2" );
    };
  };

  // TODO: better way to handle this list?
  Immutable.mutableOverrideHelper( 'set' );
  Immutable.mutableOverrideHelper( 'setX' );
  Immutable.mutableOverrideHelper( 'setY' );
  Immutable.mutableOverrideHelper( 'copy' );
  Immutable.mutableOverrideHelper( 'add' );
  Immutable.mutableOverrideHelper( 'addScalar' );
  Immutable.mutableOverrideHelper( 'subtract' );
  Immutable.mutableOverrideHelper( 'subtractScalar' );
  Immutable.mutableOverrideHelper( 'componentMultiply' );
  Immutable.mutableOverrideHelper( 'divideScalar' );
  Immutable.mutableOverrideHelper( 'negate' );

  // helpful immutable constants
  Vector2.ZERO = new Immutable( 0, 0 );
  Vector2.X_UNIT = new Immutable( 1, 0 );
  Vector2.Y_UNIT = new Immutable( 0, 1 );

  return Vector2;
} );
<|endoftext|>


<commit_before>createshow.addEventListener('submit', add_show);

function fillVenueDropdown(){
  var venueDropdown = document.getElementById('venueDropdown');
  for (var i = 0; i < venues.length; i++) {
    var venueOption = document.createElement('option');
    venueOption.value = i;
    venueOption.innerText = venues[i].venue_name;
    venueDropdown.appendChild(venueOption);
  }
}
fillVenueDropdown();

function add_show(event) {
  console.log(event);
  event.preventDefault();
  var new_show = {
    in_name: event.target.show_name.value,
    start_date: event.target.start_date.value,
    end_date: event.target.end_date.value,
    price: event.target.price.value,
    in_time: event.target.time.value,
    in_description: event.target.in_description.value,
    venue: event.target.venue.value,
    in_type: event.target.in_type.value,
    pic: event.target.showPic.value,
    ticketPage: event.target.ticketPage.value
  };
  console.log(new_show);
  shows.push(new Show(new_show.in_name, new_show.in_type, new_show.in_description, new_show.pic));
  scheduled_events.push(new ScheduledEvent(shows[shows.length - 1], venues[new_show.venue], new_show.start_date, new_show.date_end, new_show.in_time, new_show.price, new_show.ticketPage))  ;
  localStorage.setItem('ls_show_data', JSON.stringify(shows));
  localStorage.setItem('ls_event_data', JSON.stringify(scheduled_events));

  event.target.show_name.value = null;
  event.target.start_date.value = null;
  event.target.end_date.value = null;
  event.target.price.value = null;
  event.target.time.value = null;
  event.target.in_description = null;
  event.target.venue.value = null;
  event.target.in_type.value = null;
  event.target.ticketPage.value = null;
  event.target.showPic.value = null;
}
<commit_msg>end date works now<commit_after>createshow.addEventListener('submit', add_show);

function fillVenueDropdown(){
  var venueDropdown = document.getElementById('venueDropdown');
  for (var i = 0; i < venues.length; i++) {
    var venueOption = document.createElement('option');
    venueOption.value = i;
    venueOption.innerText = venues[i].venue_name;
    venueDropdown.appendChild(venueOption);
  }
}
fillVenueDropdown();

function add_show(event) {
  console.log(event);
  event.preventDefault();
  var new_show = {
    in_name: event.target.show_name.value,
    start_date: event.target.start_date.value,
    end_date: event.target.end_date.value,
    price: event.target.price.value,
    in_time: event.target.time.value,
    in_description: event.target.in_description.value,
    venue: event.target.venue.value,
    in_type: event.target.in_type.value,
    pic: event.target.showPic.value,
    ticketPage: event.target.ticketPage.value
  };
  console.log(new_show);
  shows.push(new Show(new_show.in_name, new_show.in_type, new_show.in_description, new_show.pic));
  scheduled_events.push(new ScheduledEvent(shows[shows.length - 1], venues[new_show.venue], new_show.start_date, new_show.end_date, new_show.in_time, new_show.price, new_show.ticketPage))  ;
  localStorage.setItem('ls_show_data', JSON.stringify(shows));
  localStorage.setItem('ls_event_data', JSON.stringify(scheduled_events));

  event.target.show_name.value = null;
  event.target.start_date.value = null;
  event.target.end_date.value = null;
  event.target.price.value = null;
  event.target.time.value = null;
  event.target.in_description = null;
  event.target.venue.value = null;
  event.target.in_type.value = null;
  event.target.ticketPage.value = null;
  event.target.showPic.value = null;
}
<|endoftext|>


<commit_before>function logout(){
	 $.ajax({
       url: './server/logout.php',
       type: 'GET',
       success: function(data){
        location.href = "/tienda";
},
       error: function(data){
          console.log("Ha fallado la petición HTTP. "+data.responseText);
       }
     });
}

function saludar(){
	 $.ajax({
       url: './server/session.php',
       type: 'GET',
       success: function(data){
       	var sesionObjeto = JSON.parse(data);
        $('#fraseSesion').html('¡Hola, '+sesionObjeto.nombre + '! Bienvenido al panel de administración.');
        $('#cabeceraLogout').prepend('Has iniciado sesión como '+sesionObjeto.email + ".")
},
       error: function(data){
          console.log("Ha fallado la petición HTTP. "+data.responseText);
       }
     });
}

function mostrarCategorias(){
$('#capaGridArticulos').hide();
$('#capaGridClientes').hide();
$('#capaGridPedidos').hide();
$('#capaGridCategorias').show();
$('#a1Cat').show();
$('#a2Cat').show();
$('#a3Cat').show();
$('#hCat').show();
jQuery("#jqGridCategorias").jqGrid({
    url: 'jqgrid/categoria.php',
    datatype: "json",
    height: "auto",
    colNames: ['idCategoria','nombre'],
    colModel: [{
        name: 'idCategoria',
        index: 'idCategoria',
        width: 200
    }, {
        name: 'nombre',
        index: 'nombre',
        width: 400
    }],
    rowNum: 10,
    rowList: [10, 20, 30],
    pager: '#paginadorCategorias',
    sortname: 'idCategoria',
    viewrecords: true,
    sortorder: "desc",
    caption: "Categorias"
});

$('#a1Cat').css("display","initial");
$('#a2Cat').css("display","initial");

$('#a1Cat').on('click',abrirFormularioInsertCategoria);

jQuery("#a2Cat").click(function() {
    var id = jQuery("#jqGridCategorias").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridCategorias").jqGrid('getRowData', id);
        ret.accion = "d";
     	var retJson = JSON.stringify(ret);
     	deleteCategoria(retJson);
    } else {
        alert("Por favor selecciona una fila.");
    }
});

jQuery("#a3Cat").click(function() {
    var id = jQuery("#jqGridCategorias").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridCategorias").jqGrid('getRowData', id);
        ret.accion = "d";
     	var retJson = JSON.stringify(ret);
     	$('#nombreCategoriaTFU').val(ret.nombre);
     	$('#actualizarCategoriaBT').html("Actualizar");
		$('#actualizarCategoriaBT').attr("idcategoria",ret.idCategoria);

     	abrirFormularioUpdateCategoria();
    } else {
        alert("Por favor selecciona una fila.");
    }
});

}

function mostrarArticulos(){

$('#a1Cat').hide();
$('#a2Cat').hide();
$('#capaGridCategorias').hide();
$('#capaGridPedidos').hide();
$('#capaGridClientes').hide();
$('#hArt').show();
$('#capaGridArticulos').show();
$('#a1Art').show();
$('#a2Art').show();
$('#a3Art').show();
	jQuery("#jqGridArticulos").jqGrid({
    url: 'jqgrid/articulo.php',
    datatype: "json",
    height: "auto",
    colNames: ['idArticulo','nombre','descripcion','precio','imagen','stock','categoria'],
    colModel: [
    {
        name: 'idArticulo',
        index: 'idArticulo',
        width: 100
    },
    {
        name: 'nombre',
        index: 'nombre',
        width: 100
    },
    {
    	name: 'descripcion',
    	index: 'descripcion',
    	width: 100
    },
    {
    	name: 'precio',
    	index: 'precio',
    	width: 100
    },
    {
    	name: 'imagen',
    	index: 'imagen',
    	width: 100
    },
    {
    	name: 'stock',
    	index: 'stock',
    	width: 100
    },
    {
    	name: 'categoria',
    	index: 'categoria',
    	width: 100
    }],
    rowNum: 30,
    rowList: [10, 20, 30],
    pager: '#paginadorArticulos',
    sortname: 'idArticulo',
    viewrecords: true,
    sortorder: "asc",
    caption: "Articulos",
    loadonce: true
});
	$('#a1Art').on('click',abrirFormularioInsertArticulo);
	jQuery("#a2Art").click(function() {
    var id = jQuery("#jqGridArticulos").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridArticulos").jqGrid('getRowData', id);
        ret.accion = "d";
     	var retJson = JSON.stringify(ret);
     	deleteArticulo(retJson);
    } else {
        alert("Por favor selecciona una fila.");
    }
});
	jQuery("#a3Art").click(function() {
    var id = jQuery("#jqGridArticulos").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridArticulos").jqGrid('getRowData', id);
        ret.accion = "a";
     	var retJson = JSON.stringify(ret);
     	$('#nombreArticuloTFU').val(ret.nombre);
     	$('#descripcionArticuloTFU').val(ret.descripcion);
     	$('#precioArticuloTFU').val(ret.precio);

     	$('#stockArticuloTFU').val(ret.stock);

     	var categoria = document.createElement("option");
     	categoria.text = ret.categoria;
     	categoria.value = ret.categoria;
     	document.getElementById("categoriaSLU").add(categoria,null);

		$('#actualizarArticuloBT').attr("idarticulo",ret.idArticulo);

     	abrirFormularioUpdateArticulo();
    } else {
        alert("Por favor selecciona una fila.");
    }
});

}

function mostrarClientes(){
$('#capaGridArticulos').hide();
$('#capaGridCategorias').hide();
$('#capaGridPedidos').hide();
$('#a1Cat').hide();
$('#a2Cat').hide();
$('#a3Cat').hide();
$('#a1Art').hide();
$('#a2Art').hide();
$('#a3Art').hide();

    $('#hCli').show();
    $('#a1Cli').show();
    $('#a2Cli').show();
    $('#a3Cli').show();
    $('#a4Cli').show();
	$('#capaGridClientes').show();
	jQuery("#jqGridClientes").jqGrid({
    url: 'jqgrid/cliente.php',
    datatype: "json",
    height: "auto",
    colNames: ['idCliente','nombre','apellido','dni','direccion','telefono','correo','empleado'],
    colModel: [
    {
        name: 'idCliente',
        index: 'idCliente',
        width: 100
    },
    {
        name: 'nombre',
        index: 'nombre',
        width: 100
    },
    {
    	name: 'apellido',
    	index: 'apellido',
    	width: 100
    },
    {
    	name: 'dni',
    	index: 'dni',
    	width: 100
    },
    {
    	name: 'direccion',
    	index: 'direccion',
    	width: 100
    },
    {
    	name: 'telefono',
    	index: 'telefono',
    	width: 100
    },
    {
    	name: 'correo',
    	index: 'correo',
    	width: 100
    },
    {
    	name: 'empleado',
    	index: 'empleado',
    	width: 100
    }

    ],
    rowNum: 20,
    rowList: [10, 20, 30],
    pager: '#paginadorClientes',
    sortname: 'idCliente',
    viewrecords: true,
    sortorder: "desc",
    caption: "Clientes"
});
$('#a1Cli').on("click",abrirFormularioInsertCliente);
jQuery("#a2Cli").click(function() {
    var id = jQuery("#jqGridClientes").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridClientes").jqGrid('getRowData', id);
        ret.accion = "d";
     	var retJson = JSON.stringify(ret);
     	deleteCliente(retJson);
    } else {
        alert("Por favor selecciona una fila.");
    }
});
	jQuery("#a3Cli").click(function() {
    var id = jQuery("#jqGridClientes").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridClientes").jqGrid('getRowData', id);
        ret.accion = "a";
     	var retJson = JSON.stringify(ret);

     	$('#nombreClienteTFU').val(ret.nombre);
     	$('#apellidoClienteTFU').val(ret.apellido);
     	$('#dniClienteTFU').val(ret.dni);
     	$('#direccionClienteTFU').val(ret.direccion);
     	$('#telefonoClienteTFU').val(ret.telefono);
     	$('#correoClienteTFU').val(ret.correo);

     	if (ret.empleado == "true") {
     		document.getElementById("empleadoSLU").selectedIndex = 0;
     	}else{
     		document.getElementById("empleadoSLU").selectedIndex = 1;
     	}
     	abrirFormularioUpdateCliente();
		$('#actualizarClienteBT').attr('idCliente',ret.idCliente);

    } else {
        alert("Por favor selecciona una fila.");
    }
});

jQuery("#a4Cli").click(function() {
    var id = jQuery("#jqGridClientes").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridClientes").jqGrid('getRowData', id);
        ret.accion = "c";
     	var retJson = JSON.stringify(ret);
     	$('#actualizarPassClienteBT').attr('idCliente',ret.idCliente);
     	abrirFormularioCambiarContrasenyaCliente();

    } else {
        alert("Por favor selecciona una fila.");
    }
});

}

function mostrarPedidos(){
    $('#capaGridArticulos').hide();
    $('#capaGridCategorias').hide();
    $('#capaGridClientes').hide();

    $('#capaGridPedidos').show();
    $('#hPed').show();
    jQuery("#jqGridPedidos").jqGrid({
   	url:'jqgrid/pedido.php',
	datatype: "json",
   	colNames:['idPedido','fecha', 'total', 'dni'],
   	colModel:[
   		{name:'idPedido',index:'idPedido', width:55},
   		{name:'fecha',index:'fecha', width:90},
   		{name:'total',index:'total', width:100},
   		{name:'dni',index:'dni', width:80, align:"right"}
   	],
   	rowNum:10,
   	rowList:[10,20,30],
   	pager: '#paginadorPedidos',
   	sortname: 'idPedidios',
    viewrecords: true,
    sortorder: "desc",
	multiselect: false,
	caption: "Pedidos",
	onSelectRow: function(ids) {
		if(ids == null) {
			ids=0;
			if(jQuery("#jqGridPedidos_d").jqGrid('getGridParam','records') >0 )
			{
				jQuery("#jqGridPedidos_d").jqGrid('setGridParam',{url:"jqgrid/lineapedido.php?q=1&id="+ids,page:1});
				jQuery("#jqGridPedidos_d").jqGrid('setCaption',"Linea Pedido nº: "+ids)
				.trigger('reloadGrid');
			}
		} else {
			jQuery("#jqGridPedidos_d").jqGrid('setGridParam',{url:"jqgrid/lineapedido.php?q=1&id="+ids,page:1});
			jQuery("#jqGridPedidos_d").jqGrid('setCaption',"Linea Pedido nº: "+ids)
			.trigger('reloadGrid');
		}
	}
});
jQuery("#jqGridPedidos").jqGrid('navGrid','#paginadorPedidos_d',{add:false,edit:false,del:false});
jQuery("#jqGridPedidos_d").jqGrid({
	height: 100,
   	url:'jqgrid/lineapedido.php?q=1&id=0',
	datatype: "json",
   	colNames:['idPedido','idArticulo', 'nombre', 'unidades','Precio total'],
   	colModel:[
   		{name:'idPedido',index:'idPedido', width:55},
   		{name:'idArticulo',index:'idArticulo', width:180},
   		{name:'nombre',index:'nombre', width:80, align:"right"},
   		{name:'unidad',index:'unidad', width:80, align:"right"},
   		{name:'precioTotal',index:'precioTotal', width:80,align:"right", sortable:false, search:false}
   	],
   	rowNum:5,
   	rowList:[5,10,20],
   	pager: '#paginadorPedidos_d',
   	sortname: 'idPedido',
    viewrecords: true,
    sortorder: "asc",
	multiselect: true,
	caption:"Linea Pedido"
}).navGrid('#paginadorPedidos_d',{add:false,edit:false,del:false});
jQuery("#a1Ped").click( function() {
	var s;
	s = jQuery("#jqGridPedidos_d").jqGrid('getGridParam','selarrrow');
	alert(s);
});
}
$(document).ready(function(){
	$('#a1Cat').hide();
	$('#a2Cat').hide();
	$('#a3Cat').hide();
	$('#a1Art').hide();
	$('#a2Art').hide();
	$('#a3Art').hide();
	$('#a1Cli').hide();
	$('#a2Cli').hide();
	$('#a3Cli').hide();
	$('#a4Cli').hide();
    $('#hCli').hide();
    $('#hArt').hide();
    $('#hPed').hide();
    $('#hCat').hide();
	$('#logoutBT').on('click',logout);
	$('#articulosBT').on('click',mostrarArticulos);
	$('#categoriasBT').on('click',mostrarCategorias);
	$('#clientesBT').on('click',mostrarClientes);
    $('#pedidosBT').on('click',mostrarPedidos);

	saludar();
  });
<commit_msg>style: mejorado aspecto jqgrids<commit_after>function logout(){
	 $.ajax({
       url: './server/logout.php',
       type: 'GET',
       success: function(data){
        location.href = "/tienda";
},
       error: function(data){
          console.log("Ha fallado la petición HTTP. "+data.responseText);
       }
     });
}

function saludar(){
	 $.ajax({
       url: './server/session.php',
       type: 'GET',
       success: function(data){
       	var sesionObjeto = JSON.parse(data);
        $('#fraseSesion').html('¡Hola, '+sesionObjeto.nombre + '! Bienvenido al panel de administración.');
        $('#cabeceraLogout').prepend('Has iniciado sesión como '+sesionObjeto.email + ".")
},
       error: function(data){
          console.log("Ha fallado la petición HTTP. "+data.responseText);
       }
     });
}

function mostrarCategorias(){
$('#capaGridArticulos').hide();
$('#capaGridClientes').hide();
$('#capaGridPedidos').hide();
$('#capaGridCategorias').show();
$('#a1Cat').show();
$('#a2Cat').show();
$('#a3Cat').show();
$('#hCat').show();
jQuery("#jqGridCategorias").jqGrid({
    url: 'jqgrid/categoria.php',
    datatype: "json",
    height: "auto",
    colNames: ['idCategoria','nombre'],
    colModel: [{
        name: 'idCategoria',
        index: 'idCategoria',
        width: 200
    }, {
        name: 'nombre',
        index: 'nombre',
        width: 400
    }],
    rowNum: 10,
    rowList: [10, 20, 30],
    pager: '#paginadorCategorias',
    sortname: 'idCategoria',
    viewrecords: true,
    sortorder: "desc",
    caption: "Categorias"
});

$('#a1Cat').css("display","initial");
$('#a2Cat').css("display","initial");

$('#a1Cat').on('click',abrirFormularioInsertCategoria);

jQuery("#a2Cat").click(function() {
    var id = jQuery("#jqGridCategorias").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridCategorias").jqGrid('getRowData', id);
        ret.accion = "d";
     	var retJson = JSON.stringify(ret);
     	deleteCategoria(retJson);
    } else {
        alert("Por favor selecciona una fila.");
    }
});

jQuery("#a3Cat").click(function() {
    var id = jQuery("#jqGridCategorias").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridCategorias").jqGrid('getRowData', id);
        ret.accion = "d";
     	var retJson = JSON.stringify(ret);
     	$('#nombreCategoriaTFU').val(ret.nombre);
     	$('#actualizarCategoriaBT').html("Actualizar");
		$('#actualizarCategoriaBT').attr("idcategoria",ret.idCategoria);

     	abrirFormularioUpdateCategoria();
    } else {
        alert("Por favor selecciona una fila.");
    }
});

}

function mostrarArticulos(){

$('#a1Cat').hide();
$('#a2Cat').hide();
$('#capaGridCategorias').hide();
$('#capaGridPedidos').hide();
$('#capaGridClientes').hide();
$('#hArt').show();
$('#capaGridArticulos').show();
$('#a1Art').show();
$('#a2Art').show();
$('#a3Art').show();
	jQuery("#jqGridArticulos").jqGrid({
    url: 'jqgrid/articulo.php',
    datatype: "json",
    height: "auto",
    colNames: ['idArticulo','nombre','descripcion','precio','imagen','stock','categoria'],
    colModel: [
    {
        name: 'idArticulo',
        index: 'idArticulo',
        width: 100
    },
    {
        name: 'nombre',
        index: 'nombre',
        width: 200
    },
    {
    	name: 'descripcion',
    	index: 'descripcion',
    	width: 400
    },
    {
    	name: 'precio',
    	index: 'precio',
    	width: 100
    },
    {
    	name: 'imagen',
    	index: 'imagen',
    	width: 100
    },
    {
    	name: 'stock',
    	index: 'stock',
    	width: 100
    },
    {
    	name: 'categoria',
    	index: 'categoria',
    	width: 100
    }],
    rowNum: 30,
    rowList: [10, 20, 30],
    pager: '#paginadorArticulos',
    sortname: 'idArticulo',
    viewrecords: true,
    sortorder: "asc",
    caption: "Articulos",
    loadonce: true
});
	$('#a1Art').on('click',abrirFormularioInsertArticulo);
	jQuery("#a2Art").click(function() {
    var id = jQuery("#jqGridArticulos").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridArticulos").jqGrid('getRowData', id);
        ret.accion = "d";
     	var retJson = JSON.stringify(ret);
     	deleteArticulo(retJson);
    } else {
        alert("Por favor selecciona una fila.");
    }
});
	jQuery("#a3Art").click(function() {
    var id = jQuery("#jqGridArticulos").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridArticulos").jqGrid('getRowData', id);
        ret.accion = "a";
     	var retJson = JSON.stringify(ret);
     	$('#nombreArticuloTFU').val(ret.nombre);
     	$('#descripcionArticuloTFU').val(ret.descripcion);
     	$('#precioArticuloTFU').val(ret.precio);

     	$('#stockArticuloTFU').val(ret.stock);

     	var categoria = document.createElement("option");
     	categoria.text = ret.categoria;
     	categoria.value = ret.categoria;
     	document.getElementById("categoriaSLU").add(categoria,null);

		$('#actualizarArticuloBT').attr("idarticulo",ret.idArticulo);

     	abrirFormularioUpdateArticulo();
    } else {
        alert("Por favor selecciona una fila.");
    }
});

}

function mostrarClientes(){
$('#capaGridArticulos').hide();
$('#capaGridCategorias').hide();
$('#capaGridPedidos').hide();
$('#a1Cat').hide();
$('#a2Cat').hide();
$('#a3Cat').hide();
$('#a1Art').hide();
$('#a2Art').hide();
$('#a3Art').hide();

    $('#hCli').show();
    $('#a1Cli').show();
    $('#a2Cli').show();
    $('#a3Cli').show();
    $('#a4Cli').show();
	$('#capaGridClientes').show();
	jQuery("#jqGridClientes").jqGrid({
    url: 'jqgrid/cliente.php',
    datatype: "json",
    height: "auto",
    colNames: ['idCliente','nombre','apellido','dni','direccion','telefono','correo','empleado'],
    colModel: [
    {
        name: 'idCliente',
        index: 'idCliente',
        width: 100
    },
    {
        name: 'nombre',
        index: 'nombre',
        width: 100
    },
    {
    	name: 'apellido',
    	index: 'apellido',
    	width: 100
    },
    {
    	name: 'dni',
    	index: 'dni',
    	width: 100
    },
    {
    	name: 'direccion',
    	index: 'direccion',
    	width: 100
    },
    {
    	name: 'telefono',
    	index: 'telefono',
    	width: 100
    },
    {
    	name: 'correo',
    	index: 'correo',
    	width: 100
    },
    {
    	name: 'empleado',
    	index: 'empleado',
    	width: 100
    }

    ],
    rowNum: 20,
    rowList: [10, 20, 30],
    pager: '#paginadorClientes',
    sortname: 'idCliente',
    viewrecords: true,
    sortorder: "desc",
    caption: "Clientes"
});
$('#a1Cli').on("click",abrirFormularioInsertCliente);
jQuery("#a2Cli").click(function() {
    var id = jQuery("#jqGridClientes").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridClientes").jqGrid('getRowData', id);
        ret.accion = "d";
     	var retJson = JSON.stringify(ret);
     	deleteCliente(retJson);
    } else {
        alert("Por favor selecciona una fila.");
    }
});
	jQuery("#a3Cli").click(function() {
    var id = jQuery("#jqGridClientes").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridClientes").jqGrid('getRowData', id);
        ret.accion = "a";
     	var retJson = JSON.stringify(ret);

     	$('#nombreClienteTFU').val(ret.nombre);
     	$('#apellidoClienteTFU').val(ret.apellido);
     	$('#dniClienteTFU').val(ret.dni);
     	$('#direccionClienteTFU').val(ret.direccion);
     	$('#telefonoClienteTFU').val(ret.telefono);
     	$('#correoClienteTFU').val(ret.correo);

     	if (ret.empleado == "true") {
     		document.getElementById("empleadoSLU").selectedIndex = 0;
     	}else{
     		document.getElementById("empleadoSLU").selectedIndex = 1;
     	}
     	abrirFormularioUpdateCliente();
		$('#actualizarClienteBT').attr('idCliente',ret.idCliente);

    } else {
        alert("Por favor selecciona una fila.");
    }
});

jQuery("#a4Cli").click(function() {
    var id = jQuery("#jqGridClientes").jqGrid('getGridParam', 'selrow');
    if (id) {
        var ret = jQuery("#jqGridClientes").jqGrid('getRowData', id);
        ret.accion = "c";
     	var retJson = JSON.stringify(ret);
     	$('#actualizarPassClienteBT').attr('idCliente',ret.idCliente);
     	abrirFormularioCambiarContrasenyaCliente();

    } else {
        alert("Por favor selecciona una fila.");
    }
});

}

function mostrarPedidos(){
    $('#capaGridArticulos').hide();
    $('#capaGridCategorias').hide();
    $('#capaGridClientes').hide();

    $('#capaGridPedidos').show();
    $('#hPed').show();
    jQuery("#jqGridPedidos").jqGrid({
   	url:'jqgrid/pedido.php',
	datatype: "json",
   	colNames:['idPedido','fecha', 'total', 'dni'],
   	colModel:[
   		{name:'idPedido',index:'idPedido', width:55},
   		{name:'fecha',index:'fecha', width:90},
   		{name:'total',index:'total', width:100},
   		{name:'dni',index:'dni', width:80, align:"right"}
   	],
   	rowNum:10,
   	rowList:[10,20,30],
   	pager: '#paginadorPedidos',
   	sortname: 'idPedidios',
    viewrecords: true,
    sortorder: "desc",
	multiselect: false,
	caption: "Pedidos",
	onSelectRow: function(ids) {
		if(ids == null) {
			ids=0;
			if(jQuery("#jqGridPedidos_d").jqGrid('getGridParam','records') >0 )
			{
				jQuery("#jqGridPedidos_d").jqGrid('setGridParam',{url:"jqgrid/lineapedido.php?q=1&id="+ids,page:1});
				jQuery("#jqGridPedidos_d").jqGrid('setCaption',"Linea Pedido nº: "+ids)
				.trigger('reloadGrid');
			}
		} else {
			jQuery("#jqGridPedidos_d").jqGrid('setGridParam',{url:"jqgrid/lineapedido.php?q=1&id="+ids,page:1});
			jQuery("#jqGridPedidos_d").jqGrid('setCaption',"Linea Pedido nº: "+ids)
			.trigger('reloadGrid');
		}
	}
});
jQuery("#jqGridPedidos").jqGrid('navGrid','#paginadorPedidos_d',{add:false,edit:false,del:false});
jQuery("#jqGridPedidos_d").jqGrid({
	height: 100,
   	url:'jqgrid/lineapedido.php?q=1&id=0',
	datatype: "json",
   	colNames:['idPedido','idArticulo', 'nombre', 'unidades','Precio total'],
   	colModel:[
   		{name:'idPedido',index:'idPedido', width:55},
   		{name:'idArticulo',index:'idArticulo', width:180},
   		{name:'nombre',index:'nombre', width:200, align:"right"},
   		{name:'unidad',index:'unidad', width:80, align:"right"},
   		{name:'precioTotal',index:'precioTotal', width:80,align:"right", sortable:false, search:false}
   	],
   	rowNum:5,
   	rowList:[5,10,20],
   	pager: '#paginadorPedidos_d',
   	sortname: 'idPedido',
    viewrecords: true,
    sortorder: "asc",
	multiselect: true,
	caption:"Linea Pedido"
}).navGrid('#paginadorPedidos_d',{add:false,edit:false,del:false});
jQuery("#a1Ped").click( function() {
	var s;
	s = jQuery("#jqGridPedidos_d").jqGrid('getGridParam','selarrrow');
	alert(s);
});
}
$(document).ready(function(){
	$('#a1Cat').hide();
	$('#a2Cat').hide();
	$('#a3Cat').hide();
	$('#a1Art').hide();
	$('#a2Art').hide();
	$('#a3Art').hide();
	$('#a1Cli').hide();
	$('#a2Cli').hide();
	$('#a3Cli').hide();
	$('#a4Cli').hide();
    $('#hCli').hide();
    $('#hArt').hide();
    $('#hPed').hide();
    $('#hCat').hide();
	$('#logoutBT').on('click',logout);
	$('#articulosBT').on('click',mostrarArticulos);
	$('#categoriasBT').on('click',mostrarCategorias);
	$('#clientesBT').on('click',mostrarClientes);
    $('#pedidosBT').on('click',mostrarPedidos);

	saludar();
  });
<|endoftext|>


<commit_before>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class bithumb extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'bithumb',
            'name': 'Bithumb',
            'countries': 'KR', // South Korea
            'rateLimit': 500,
            'has': {
                'CORS': true,
                'fetchTickers': true,
                'withdraw': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/30597177-ea800172-9d5e-11e7-804c-b9d4fa9b56b0.jpg',
                'api': {
                    'public': 'https://api.bithumb.com/public',
                    'private': 'https://api.bithumb.com',
                },
                'www': 'https://www.bithumb.com',
                'doc': 'https://www.bithumb.com/u1/US127',
            },
            'api': {
                'public': {
                    'get': [
                        'ticker/{currency}',
                        'ticker/all',
                        'orderbook/{currency}',
                        'orderbook/all',
                        'recent_transactions/{currency}',
                        'recent_transactions/all',
                    ],
                },
                'private': {
                    'post': [
                        'info/account',
                        'info/balance',
                        'info/wallet_address',
                        'info/ticker',
                        'info/orders',
                        'info/user_transactions',
                        'trade/place',
                        'info/order_detail',
                        'trade/cancel',
                        'trade/btc_withdrawal',
                        'trade/krw_deposit',
                        'trade/krw_withdrawal',
                        'trade/market_buy',
                        'trade/market_sell',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.15 / 100,
                    'taker': 0.15 / 100,
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetTickerAll ();
        let currencies = Object.keys (markets['data']);
        let result = [];
        for (let i = 0; i < currencies.length; i++) {
            let id = currencies[i];
            if (id !== 'date') {
                let market = markets['data'][id];
                let base = id;
                let quote = 'KRW';
                let symbol = id + '/' + quote;
                result.push ({
                    'id': id,
                    'symbol': symbol,
                    'base': base,
                    'quote': quote,
                    'info': market,
                    'lot': undefined,
                    'active': true,
                    'precision': {
                        'amount': undefined,
                        'price': undefined,
                    },
                    'limits': {
                        'amount': {
                            'min': undefined,
                            'max': undefined,
                        },
                        'price': {
                            'min': undefined,
                            'max': undefined,
                        },
                        'cost': {
                            'min': undefined,
                            'max': undefined,
                        },
                    },
                });
            }
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostInfoBalance (this.extend ({
            'currency': 'ALL',
        }, params));
        let result = { 'info': response };
        let balances = response['data'];
        let currencies = Object.keys (this.currencies);
        for (let i = 0; i < currencies.length; i++) {
            let currency = currencies[i];
            let account = this.account ();
            let lowercase = currency.toLowerCase ();
            account['total'] = this.safeFloat (balances, 'total_' + lowercase);
            account['used'] = this.safeFloat (balances, 'in_use_' + lowercase);
            account['free'] = this.safeFloat (balances, 'available_' + lowercase);
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let request = {
            'currency': market['base'],
        };
        if (typeof limit !== 'undefined')
            request['count'] = limit; // max = 50
        let response = await this.publicGetOrderbookCurrency (this.extend (request, params));
        let orderbook = response['data'];
        let timestamp = parseInt (orderbook['timestamp']);
        return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'quantity');
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = parseInt (ticker['date']);
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        let open = this.safeFloat (ticker, 'opening_price');
        let last = this.safeFloat (ticker, 'closing_price');
        let change = last - open;
        let vwap = this.safeFloat (ticker, 'average_price');
        let baseVolume = this.safeFloat (ticker, 'volume_1day');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeFloat (ticker, 'max_price'),
            'low': this.safeFloat (ticker, 'min_price'),
            'bid': this.safeFloat (ticker, 'buy_price'),
            'ask': this.safeFloat (ticker, 'sell_price'),
            'vwap': vwap,
            'open': open,
            'last': last,
            'change': change,
            'percentage': change / open * 100,
            'average': (open + last) / 2,
            'baseVolume': baseVolume,
            'quoteVolume': baseVolume * vwap,
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetTickerAll (params);
        let result = {};
        let timestamp = response['data']['date'];
        let tickers = this.omit (response['data'], 'date');
        let ids = Object.keys (tickers);
        for (let i = 0; i < ids.length; i++) {
            let id = ids[i];
            let symbol = id;
            let market = undefined;
            if (id in this.markets_by_id) {
                market = this.markets_by_id[id];
                symbol = market['symbol'];
            }
            let ticker = tickers[id];
            ticker['date'] = timestamp;
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTickerCurrency (this.extend ({
            'currency': market['base'],
        }, params));
        return this.parseTicker (response['data'], market);
    }

    parseTrade (trade, market) {
        // a workaround for their bug in date format, hours are not 0-padded
        let [ transaction_date, transaction_time ] = trade['transaction_date'].split (' ');
        if (transaction_time.length < 8)
            transaction_time = '0' + transaction_time;
        let timestamp = this.parse8601 (transaction_date + ' ' + transaction_time);
        timestamp -= 9 * 3600000; // they report UTC + 9 hours (server in Korean timezone)
        let side = (trade['type'] === 'ask') ? 'sell' : 'buy';
        return {
            'id': undefined,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'order': undefined,
            'type': undefined,
            'side': side,
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['units_traded']),
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetRecentTransactionsCurrency (this.extend ({
            'currency': market['base'],
            'count': 100, // max = 100
        }, params));
        return this.parseTrades (response['data'], market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let request = undefined;
        let method = 'privatePostTrade';
        if (type === 'limit') {
            request = {
                'order_currency': market['id'],
                'Payment_currency': market['quote'],
                'units': amount,
                'price': price,
                'type': (side === 'buy') ? 'bid' : 'ask',
            };
            method += 'Place';
        } else if (type === 'market') {
            request = {
                'currency': market['id'],
                'units': amount,
            };
            method += 'Market' + this.capitalize (side);
        }
        let response = await this[method] (this.extend (request, params));
        let id = undefined;
        if ('order_id' in response) {
            if (response['order_id'])
                id = response['order_id'].toString ();
        }
        return {
            'info': response,
            'id': id,
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        let side = ('side' in params);
        if (!side)
            throw new ExchangeError (this.id + ' cancelOrder requires a side parameter (sell or buy) and a currency parameter');
        side = (side === 'buy') ? 'purchase' : 'sales';
        let currency = ('currency' in params);
        if (!currency)
            throw new ExchangeError (this.id + ' cancelOrder requires a currency parameter');
        return await this.privatePostTradeCancel ({
            'order_id': id,
            'type': params['side'],
            'currency': params['currency'],
        });
    }

    async withdraw (currency, amount, address, tag = undefined, params = {}) {
        let request = {
            'units': amount,
            'address': address,
            'currency': currency,
        };
        if (currency === 'XRP' || currency === 'XMR') {
            let destination = ('destination' in params);
            if (!destination)
                throw new ExchangeError (this.id + ' ' + currency + ' withdraw requires an extra destination param');
        }
        let response = await this.privatePostTradeBtcWithdrawal (this.extend (request, params));
        return {
            'info': response,
            'id': undefined,
        };
    }

    nonce () {
        return this.milliseconds ();
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let endpoint = '/' + this.implodeParams (path, params);
        let url = this.urls['api'][api] + endpoint;
        let query = this.omit (params, this.extractParams (path));
        if (api === 'public') {
            if (Object.keys (query).length)
                url += '?' + this.urlencode (query);
        } else {
            this.checkRequiredCredentials ();
            body = this.urlencode (this.extend ({
                'endpoint': endpoint,
            }, query));
            let nonce = this.nonce ().toString ();
            let auth = endpoint + '\0' + body + '\0' + nonce;
            let signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512');
            let signature64 = this.decode (this.stringToBase64 (this.encode (signature)));
            headers = {
                'Accept': 'application/json',
                'Content-Type': 'application/x-www-form-urlencoded',
                'Api-Key': this.apiKey,
                'Api-Sign': signature64.toString (),
                'Api-Nonce': nonce,
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('status' in response) {
            if (response['status'] === '0000')
                return response;
            throw new ExchangeError (this.id + ' ' + this.json (response));
        }
        return response;
    }
};
<commit_msg>restored bithumb `close` in parseTicker #1997<commit_after>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class bithumb extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'bithumb',
            'name': 'Bithumb',
            'countries': 'KR', // South Korea
            'rateLimit': 500,
            'has': {
                'CORS': true,
                'fetchTickers': true,
                'withdraw': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/30597177-ea800172-9d5e-11e7-804c-b9d4fa9b56b0.jpg',
                'api': {
                    'public': 'https://api.bithumb.com/public',
                    'private': 'https://api.bithumb.com',
                },
                'www': 'https://www.bithumb.com',
                'doc': 'https://www.bithumb.com/u1/US127',
            },
            'api': {
                'public': {
                    'get': [
                        'ticker/{currency}',
                        'ticker/all',
                        'orderbook/{currency}',
                        'orderbook/all',
                        'recent_transactions/{currency}',
                        'recent_transactions/all',
                    ],
                },
                'private': {
                    'post': [
                        'info/account',
                        'info/balance',
                        'info/wallet_address',
                        'info/ticker',
                        'info/orders',
                        'info/user_transactions',
                        'trade/place',
                        'info/order_detail',
                        'trade/cancel',
                        'trade/btc_withdrawal',
                        'trade/krw_deposit',
                        'trade/krw_withdrawal',
                        'trade/market_buy',
                        'trade/market_sell',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.15 / 100,
                    'taker': 0.15 / 100,
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetTickerAll ();
        let currencies = Object.keys (markets['data']);
        let result = [];
        for (let i = 0; i < currencies.length; i++) {
            let id = currencies[i];
            if (id !== 'date') {
                let market = markets['data'][id];
                let base = id;
                let quote = 'KRW';
                let symbol = id + '/' + quote;
                result.push ({
                    'id': id,
                    'symbol': symbol,
                    'base': base,
                    'quote': quote,
                    'info': market,
                    'lot': undefined,
                    'active': true,
                    'precision': {
                        'amount': undefined,
                        'price': undefined,
                    },
                    'limits': {
                        'amount': {
                            'min': undefined,
                            'max': undefined,
                        },
                        'price': {
                            'min': undefined,
                            'max': undefined,
                        },
                        'cost': {
                            'min': undefined,
                            'max': undefined,
                        },
                    },
                });
            }
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostInfoBalance (this.extend ({
            'currency': 'ALL',
        }, params));
        let result = { 'info': response };
        let balances = response['data'];
        let currencies = Object.keys (this.currencies);
        for (let i = 0; i < currencies.length; i++) {
            let currency = currencies[i];
            let account = this.account ();
            let lowercase = currency.toLowerCase ();
            account['total'] = this.safeFloat (balances, 'total_' + lowercase);
            account['used'] = this.safeFloat (balances, 'in_use_' + lowercase);
            account['free'] = this.safeFloat (balances, 'available_' + lowercase);
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let request = {
            'currency': market['base'],
        };
        if (typeof limit !== 'undefined')
            request['count'] = limit; // max = 50
        let response = await this.publicGetOrderbookCurrency (this.extend (request, params));
        let orderbook = response['data'];
        let timestamp = parseInt (orderbook['timestamp']);
        return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'quantity');
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = parseInt (ticker['date']);
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        let open = this.safeFloat (ticker, 'opening_price');
        let last = this.safeFloat (ticker, 'closing_price');
        let change = last - open;
        let vwap = this.safeFloat (ticker, 'average_price');
        let baseVolume = this.safeFloat (ticker, 'volume_1day');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeFloat (ticker, 'max_price'),
            'low': this.safeFloat (ticker, 'min_price'),
            'bid': this.safeFloat (ticker, 'buy_price'),
            'ask': this.safeFloat (ticker, 'sell_price'),
            'vwap': vwap,
            'open': open,
            'close': undefined,
            'last': last,
            'change': change,
            'percentage': change / open * 100,
            'average': (open + last) / 2,
            'baseVolume': baseVolume,
            'quoteVolume': baseVolume * vwap,
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetTickerAll (params);
        let result = {};
        let timestamp = response['data']['date'];
        let tickers = this.omit (response['data'], 'date');
        let ids = Object.keys (tickers);
        for (let i = 0; i < ids.length; i++) {
            let id = ids[i];
            let symbol = id;
            let market = undefined;
            if (id in this.markets_by_id) {
                market = this.markets_by_id[id];
                symbol = market['symbol'];
            }
            let ticker = tickers[id];
            ticker['date'] = timestamp;
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTickerCurrency (this.extend ({
            'currency': market['base'],
        }, params));
        return this.parseTicker (response['data'], market);
    }

    parseTrade (trade, market) {
        // a workaround for their bug in date format, hours are not 0-padded
        let [ transaction_date, transaction_time ] = trade['transaction_date'].split (' ');
        if (transaction_time.length < 8)
            transaction_time = '0' + transaction_time;
        let timestamp = this.parse8601 (transaction_date + ' ' + transaction_time);
        timestamp -= 9 * 3600000; // they report UTC + 9 hours (server in Korean timezone)
        let side = (trade['type'] === 'ask') ? 'sell' : 'buy';
        return {
            'id': undefined,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'order': undefined,
            'type': undefined,
            'side': side,
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['units_traded']),
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetRecentTransactionsCurrency (this.extend ({
            'currency': market['base'],
            'count': 100, // max = 100
        }, params));
        return this.parseTrades (response['data'], market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let request = undefined;
        let method = 'privatePostTrade';
        if (type === 'limit') {
            request = {
                'order_currency': market['id'],
                'Payment_currency': market['quote'],
                'units': amount,
                'price': price,
                'type': (side === 'buy') ? 'bid' : 'ask',
            };
            method += 'Place';
        } else if (type === 'market') {
            request = {
                'currency': market['id'],
                'units': amount,
            };
            method += 'Market' + this.capitalize (side);
        }
        let response = await this[method] (this.extend (request, params));
        let id = undefined;
        if ('order_id' in response) {
            if (response['order_id'])
                id = response['order_id'].toString ();
        }
        return {
            'info': response,
            'id': id,
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        let side = ('side' in params);
        if (!side)
            throw new ExchangeError (this.id + ' cancelOrder requires a side parameter (sell or buy) and a currency parameter');
        side = (side === 'buy') ? 'purchase' : 'sales';
        let currency = ('currency' in params);
        if (!currency)
            throw new ExchangeError (this.id + ' cancelOrder requires a currency parameter');
        return await this.privatePostTradeCancel ({
            'order_id': id,
            'type': params['side'],
            'currency': params['currency'],
        });
    }

    async withdraw (currency, amount, address, tag = undefined, params = {}) {
        let request = {
            'units': amount,
            'address': address,
            'currency': currency,
        };
        if (currency === 'XRP' || currency === 'XMR') {
            let destination = ('destination' in params);
            if (!destination)
                throw new ExchangeError (this.id + ' ' + currency + ' withdraw requires an extra destination param');
        }
        let response = await this.privatePostTradeBtcWithdrawal (this.extend (request, params));
        return {
            'info': response,
            'id': undefined,
        };
    }

    nonce () {
        return this.milliseconds ();
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let endpoint = '/' + this.implodeParams (path, params);
        let url = this.urls['api'][api] + endpoint;
        let query = this.omit (params, this.extractParams (path));
        if (api === 'public') {
            if (Object.keys (query).length)
                url += '?' + this.urlencode (query);
        } else {
            this.checkRequiredCredentials ();
            body = this.urlencode (this.extend ({
                'endpoint': endpoint,
            }, query));
            let nonce = this.nonce ().toString ();
            let auth = endpoint + '\0' + body + '\0' + nonce;
            let signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512');
            let signature64 = this.decode (this.stringToBase64 (this.encode (signature)));
            headers = {
                'Accept': 'application/json',
                'Content-Type': 'application/x-www-form-urlencoded',
                'Api-Key': this.apiKey,
                'Api-Sign': signature64.toString (),
                'Api-Nonce': nonce,
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('status' in response) {
            if (response['status'] === '0000')
                return response;
            throw new ExchangeError (this.id + ' ' + this.json (response));
        }
        return response;
    }
};
<|endoftext|>