<commit_before>// TODO create a model from a local config object. not from a store config
var env = process.env;

var fs = require('fs');
var cache = require(env.Z_PATH_CACHE + 'cache');
var storeFactory = require(env.Z_PATH_STORES + 'factory');
var object = require(env.Z_PATH_UTILS + 'object');
var compModels = cache.comp('models');
var compStores = cache.comp('stores');

// read available store adapters to adapter cache
var adapatersPath = __dirname + '/adapters/';
var adapters = fs.readdirSync(adapatersPath);

// save adapter paths in adapter cache
for (var i = 0; i < adapters.length; ++i) {
    adapters[adapters[i]] = adapatersPath + adapters[i] + '/' + adapters[i] + '.js';
}

exports.service = factoryService;
exports.factory = factory;
exports.queries = messageHandler;

function factory (name, role, callback) {

    // check cache
    var model = compModels.pojo.get(name, role);

    if (model === 0) {
        return callback(new Error('Model ' + name + ' not found.'));
    }

    if (model) {
        return callback(null, model);
    }

    // get and set model config from composition store
    compModels.get(name, role, function (err, comp_model, modelChanged) {

        if (err || !comp_model) {
            return callback(err || new Error('Model ' + name + ' not found.'));
        }

        // get connected store
        storeFactory(comp_model.store, role, function (err, store) {

            if (err) {
                return callback(err);
            }

            // handle error and check if adapter exists
            if (err || !adapters[comp_model.adapter]) {
                return callback(err || new Error('Model adapter "' + comp_model.adapter + '" not available.'));
            }

            // require adapter
            if (typeof adapters[comp_model.adapter] === 'string') {
                adapters[comp_model.adapter] = require(adapters[comp_model.adapter]);
            }

            // save model in cache
            model = adapters[comp_model.adapter].model(store, comp_model);

            // extend model with query function
            model.query = createQuery;

            // save adapter name on model
            model._adapter = comp_model.adapter;

            // query container
            model.queries = {};

            // create queries from the config
            if (comp_model.queries) {
                for (var i = 0; i < comp_model.queries.length; ++i) {
                    model.query(comp_model.queries[i]);
                }
            }

            // save roles on model
            model._roles = comp_model.roles;

            // remove view on snippet change
            if (!modelChanged) {
                compStores.obs.once('change:' + comp_model.store, function () {
                    compModels.rm(name, role);
                });
            }

            // save model in cache
            compModels.pojo.set(name, model);

            callback(null, model);
        });
    });
}

// listen to model request
function factoryService (err, name, callback) {
    var self = this;

    // check message
    if (!name) {
        return callback('Bad message');
    }

    // send the model schema to the client
    factory(name, self.link.role, function (err, model) {

        if (err) {
            return callback(err);
        }

        var clientModel = {
            name: model.name,
            schema: model.schema
        };

        callback(null, clientModel);
    });
}

// handle requests
function createQuery (config) {
    var self = this;
    var handler;

    // get custom query handlers
    if (config.handler) {
        handler = object.path(config.handler/* TODO pass instance*/);

        if (typeof handler === 'function') {
            handler = config.handler.call(self, config.request);
        }
    } else {
        // create a query handler with the adapters query factory
        handler = adapters[self._adapter].query.call(self, config.request);
    }

    // don't add query to container if no hander is created
    if (!handler) {
        return;
    }

    // add query function to query container
    self.queries[config.name] = function (data, callback) {

        // copy request object
        var request = JSON.parse(JSON.stringify(config.request));

        // merge data into request
        if (config.add && data) {

            var value;

            // extend request object
            for (var key in config.add) {

                if ((value = object.path(config.add[key], data))) {
                    request[key] = value;
                }
            }
        }

        // nest flat paths
        request = object.deep(request);

        // execute query with request infos
        handler(request, callback);
    };
}

// handle model data request
function messageHandler (err, message, callback) {
    var self = this;

    // check message
    if (!message || !message.m || !message.q) {
        return callback(new Error('Bad message.'));
    }

    // get model from cache
    var model = compModels.pojo.get(message.m, self.link.role);

    if (!model) {
        return callback(new Error('Model "' + message.m + '" not found.'));
    }

    if (!model.queries[message.q]) {
        return callback(new Error('Model query "' + message.q + '" not found.'));
    }

    // do model query request
    model.queries[message.q](message.d, callback);
}
<commit_msg>added some comments<commit_after>// TODO create a model from a local config object. not from a store config
var env = process.env;

var fs = require('fs');
var cache = require(env.Z_PATH_CACHE + 'cache');
var storeFactory = require(env.Z_PATH_STORES + 'factory');
var object = require(env.Z_PATH_UTILS + 'object');
var compModels = cache.comp('models');
var compStores = cache.comp('stores');

// read available store adapters to adapter cache
var adapatersPath = __dirname + '/adapters/';
var adapters = fs.readdirSync(adapatersPath);

// save adapter paths in adapter cache
for (var i = 0; i < adapters.length; ++i) {
    adapters[adapters[i]] = adapatersPath + adapters[i] + '/' + adapters[i] + '.js';
}

exports.service = factoryService;
exports.factory = factory;
exports.queries = messageHandler;

function factory (name, role, callback) {

    // check cache
    var model = compModels.pojo.get(name, role);

    if (model === 0) {
        return callback(new Error('Model ' + name + ' not found.'));
    }

    if (model) {
        return callback(null, model);
    }

    // get and set model config from composition store
    compModels.get(name, role, function (err, comp_model, modelChanged) {

        if (err || !comp_model) {
            return callback(err || new Error('Model ' + name + ' not found.'));
        }

        // get connected store
        storeFactory(comp_model.store, role, function (err, store) {

            if (err) {
                return callback(err);
            }

            // handle error and check if adapter exists
            if (err || !adapters[comp_model.adapter]) {
                return callback(err || new Error('Model adapter "' + comp_model.adapter + '" not available.'));
            }

            // require adapter
            if (typeof adapters[comp_model.adapter] === 'string') {
                adapters[comp_model.adapter] = require(adapters[comp_model.adapter]);
            }

            // save model in cache
            model = adapters[comp_model.adapter].model(store, comp_model);

            // extend model with query function
            model.query = createQuery;

            // save adapter name on model
            model._adapter = comp_model.adapter;

            // query container
            model.queries = {};

            // create queries from the config
            if (comp_model.queries) {
                for (var i = 0; i < comp_model.queries.length; ++i) {
                    model.query(comp_model.queries[i]);
                }
            }

            // save roles on model
            model._roles = comp_model.roles;

            // remove model on store change
            // TODO what about the connected drivers? must the connection(s) be closed
            if (!modelChanged) {
                compStores.obs.once('change:' + comp_model.store, function () {
                    compModels.rm(name, role);
                });
            }

            // save model in cache
            compModels.pojo.set(name, model);

            callback(null, model);
        });
    });
}

// listen to model request
function factoryService (err, name, callback) {
    var self = this;

    // check message
    if (!name) {
        return callback('Bad message');
    }

    // send the model schema to the client
    factory(name, self.link.role, function (err, model) {

        if (err) {
            return callback(err);
        }

        var clientModel = {
            name: model.name,
            schema: model.schema
        };

        callback(null, clientModel);
    });
}

// handle requests
function createQuery (config) {
    var self = this;
    var handler;

    // get custom query handlers
    if (config.handler) {
        handler = object.path(config.handler/* TODO pass instance*/);

        if (typeof handler === 'function') {
            handler = config.handler.call(self, config.request);
        }
    } else {
        // create a query handler with the adapters query factory
        handler = adapters[self._adapter].query.call(self, config.request);
    }

    // don't add query to container if no hander is created
    if (!handler) {
        return;
    }

    // add query function to query container
    self.queries[config.name] = function (data, callback) {

        // copy request object
        var request = JSON.parse(JSON.stringify(config.request));

        // merge data into request
        if (config.add && data) {

            var value;

            // extend request object
            for (var key in config.add) {

                if ((value = object.path(config.add[key], data))) {
                    request[key] = value;
                }
            }
        }

        // nest flat paths
        request = object.deep(request);

        // execute query with request infos
        handler(request, callback);
    };
}

// handle model data request
function messageHandler (err, message, callback) {
    var self = this;

    // check message
    if (!message || !message.m || !message.q) {
        return callback(new Error('Bad message.'));
    }

    // get model from cache
    var model = compModels.pojo.get(message.m, self.link.role);

    if (!model) {
        return callback(new Error('Model "' + message.m + '" not found.'));
    }

    if (!model.queries[message.q]) {
        return callback(new Error('Model query "' + message.q + '" not found.'));
    }

    // do model query request
    model.queries[message.q](message.d, callback);
}
<|endoftext|>


<commit_before>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Shows a readout of the elapsed time, with play and pause buttons.  By default there are no units (which could be used
 * if all of a simulations time units are in 'seconds'), or you can specify a selection of units to choose from.
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Anton Ulyanov (Mlearner)
 */

import Bounds2 from '../../dot/js/Bounds2.js';
import Utils from '../../dot/js/Utils.js';
import InstanceRegistry from '../../phet-core/js/documentation/InstanceRegistry.js';
import inherit from '../../phet-core/js/inherit.js';
import merge from '../../phet-core/js/merge.js';
import StringUtils from '../../phetcommon/js/util/StringUtils.js';
import DragListener from '../../scenery/js/listeners/DragListener.js';
import Circle from '../../scenery/js/nodes/Circle.js';
import HBox from '../../scenery/js/nodes/HBox.js';
import Node from '../../scenery/js/nodes/Node.js';
import Path from '../../scenery/js/nodes/Path.js';
import VBox from '../../scenery/js/nodes/VBox.js';
import BooleanRectangularToggleButton from '../../sun/js/buttons/BooleanRectangularToggleButton.js';
import RectangularPushButton from '../../sun/js/buttons/RectangularPushButton.js';
import Tandem from '../../tandem/js/Tandem.js';
import DragBoundsProperty from './DragBoundsProperty.js';
import NumberDisplay from './NumberDisplay.js';
import PauseIconShape from './PauseIconShape.js';
import PlayIconShape from './PlayIconShape.js';
import sceneryPhet from './sceneryPhet.js';
import sceneryPhetStrings from './sceneryPhetStrings.js';
import ShadedRectangle from './ShadedRectangle.js';
import Stopwatch from './Stopwatch.js';
import UTurnArrowShape from './UTurnArrowShape.js';

/**
 * @param {Stopwatch} stopwatch
 * @param {Object} [options]
 */
function StopwatchNode( stopwatch, options ) {
  assert && assert( stopwatch instanceof Stopwatch, `invalid stopwatch: ${stopwatch}` );

  options = merge( {

    cursor: 'pointer',
    iconHeight: 10,
    numberDisplayRange: Stopwatch.ZERO_TO_ALMOST_SIXTY, // Just for sizing the display, sized for 59:59.99 (mm:ss) or 3599.99 (decimal)
    iconFill: 'black',
    iconLineWidth: 1,
    backgroundBaseColor: 'rgb( 80, 130, 230 )',
    buttonBaseColor: '#DFE0E1',
    xSpacing: 6, // horizontal space between the buttons
    ySpacing: 6, // vertical space between readout and buttons
    xMargin: 8,
    yMargin: 8,

    // options propagated to the NumberDisplay
    numberDisplayOptions: {
      numberFormatter: StopwatchNode.richNumberFormatter,
      useRichText: true,
      align: 'right',
      cornerRadius: 4,
      xMargin: 4,
      yMargin: 2,
      pickable: false // allow dragging by the number display
    },

    visibleBoundsProperty: null, // {Property.<Bounds2>|null} if provided, the node is draggable within the bounds

    // Tandem is required to make sure the buttons are instrumented
    tandem: Tandem.REQUIRED,

    // options propagated to the DragListener.
    dragListenerOptions: null
  }, options );
  assert && assert( !options.hasOwnProperty( 'maxValue' ), 'options.maxValue no longer supported' );

  assert && assert( options.xSpacing >= 0, 'Buttons cannot overlap' );
  assert && assert( options.ySpacing >= 0, 'Buttons cannot overlap the readout' );

  // Create the StopwatchReadoutNode.
  this.numberDisplay = new NumberDisplay( stopwatch.timeProperty, options.numberDisplayRange, options.numberDisplayOptions );

  // Buttons ----------------------------------------------------------------------------

  const resetPath = new Path( new UTurnArrowShape( options.iconHeight ), {
    fill: options.iconFill
  } );

  const playIconHeight = resetPath.height;
  const playIconWidth = 0.8 * playIconHeight;
  const playPath = new Path( new PlayIconShape( playIconWidth, playIconHeight ), {
    fill: options.iconFill
  } );

  const pausePath = new Path( new PauseIconShape( 0.75 * playIconWidth, playIconHeight ), {
    fill: options.iconFill
  } );

  const playPauseButton = new BooleanRectangularToggleButton( pausePath, playPath, stopwatch.isRunningProperty, {
    baseColor: options.buttonBaseColor,
    tandem: options.tandem.createTandem( 'playPauseButton' )
  } );

  const resetButton = new RectangularPushButton( {
    listener: function resetTimer() {
      stopwatch.isRunningProperty.set( false );
      stopwatch.timeProperty.set( 0 );
    },
    content: resetPath,
    baseColor: options.buttonBaseColor,
    tandem: options.tandem.createTandem( 'resetButton' )
  } );

  const contents = new VBox( {
    spacing: options.ySpacing,
    children: [
      this.numberDisplay,
      new HBox( {
        spacing: options.xSpacing,
        children: [ resetButton, playPauseButton ]
      } )
    ]
  } );

  // Background panel ----------------------------------------------------------------------------

  const backgroundNode = new ShadedRectangle( new Bounds2( 0, 0,
    contents.width + 2 * options.xMargin, contents.height + 2 * options.yMargin ), {
    baseColor: options.backgroundBaseColor
  } );
  contents.center = backgroundNode.center;

  assert && assert( !options.children, 'StopwatchNode sets children' );
  options.children = [ backgroundNode, contents ];

  Node.call( this, options );

  // @public (read-only) - Target for drag listeners
  this.dragTarget = backgroundNode;

  // Disable the reset button when time is zero, and enable the play/pause button when not at the max time
  const timeListener = time => {
    resetButton.enabled = time > 0;
    playPauseButton.enabled = time < stopwatch.timeProperty.range.max;
  };
  stopwatch.timeProperty.link( timeListener );

  // Put a red dot at the origin, for debugging layout.
  if ( phet.chipper.queryParameters.dev ) {
    this.addChild( new Circle( 3, { fill: 'red' } ) );
  }

  const stopwatchVisibleListener = visible => {
    this.visible = visible;
    if ( visible ) {
      this.moveToFront();
    }
    else {

      // interrupt user interactions when the stopwatch is made invisible
      this.interruptSubtreeInput();
    }
  };
  stopwatch.isVisibleProperty.link( stopwatchVisibleListener );

  // Move to the stopwatch's position
  const stopwatchPositionListener = position => this.setTranslation( position );
  stopwatch.positionProperty.link( stopwatchPositionListener );

  // @public (read-only) {DragListener|null} -- reassigned below, if draggable.  Can be used for forwarding press
  // events when dragging out of a toolbox.
  this.dragListener = null;

  let dragBoundsProperty = null;

  if ( options.visibleBoundsProperty ) {

    // drag bounds, adjusted to keep this entire Node inside visible bounds
    dragBoundsProperty = new DragBoundsProperty( this, options.visibleBoundsProperty );

    // interrupt user interactions when the visible bounds changes, such as a device orientation change or window resize
    options.visibleBoundsProperty.link( () => this.interruptSubtreeInput() );

    // If the stopwatch is outside the drag bounds, move it inside.
    dragBoundsProperty.link( dragBounds => {
      if ( !dragBounds.containsPoint( stopwatch.positionProperty ) ) {
        stopwatch.positionProperty.value = dragBounds.closestPointTo( stopwatch.positionProperty.value );
      }
    } );

    // dragging, added to background so that other UI components get input events on touch devices
    const dragListenerOptions = merge( {
      targetNode: this,
      positionProperty: stopwatch.positionProperty,
      dragBoundsProperty: dragBoundsProperty,
      tandem: options.tandem.createTandem( 'dragListener' )
    }, options.dragListenerOptions );

    // The StopwatchNode always calls moveToFront on drag start
    const startOption = dragListenerOptions.start;
    dragListenerOptions.start = () => {
      this.moveToFront();
      startOption && startOption();
    };

    this.dragListener = new DragListener( dragListenerOptions );
    this.dragTarget.addInputListener( this.dragListener );
  }

  this.addLinkedElement( stopwatch, {
    tandem: options.tandem.createTandem( 'stopwatch' )
  } );

  // @private
  this.disposeStopwatchNode = function() {
    stopwatch.isVisibleProperty.unlink( stopwatchVisibleListener );
    stopwatch.timeProperty.unlink( timeListener );
    stopwatch.positionProperty.unlink( stopwatchPositionListener );

    this.numberDisplay.dispose();
    resetButton.dispose();
    playPauseButton.dispose();

    if ( this.dragListener ) {
      this.dragTarget.removeInputListener( this.dragListener );
      this.dragListener.dispose();
    }

    dragBoundsProperty && dragBoundsProperty.dispose();
  };

  // support for binder documentation, stripped out in builds and only runs when ?binder is specified
  assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'scenery-phet', 'StopwatchNode', this );
}

sceneryPhet.register( 'StopwatchNode', StopwatchNode );

inherit( Node, StopwatchNode, {

  /**
   * @param {function<number,string>} numberFormatter
   * @public
   */
  setNumberFormatter( numberFormatter ) {
    this.numberDisplay.setNumberFormatter( numberFormatter );
  },

  // @public - redraw the text when something other than the numberProperty changes (such as units, formatter, etc).
  redrawNumberDisplay() {
    this.numberDisplay.recomputeText();
  },

  /**
   * Release resources when no longer be used.
   * @public
   * @override
   */
  dispose: function() {
    this.disposeStopwatchNode();
    Node.prototype.dispose.call( this );
  }
} );

// the full-sized minutes and seconds string
const toMinutesAndSeconds = time => {

  // Round to the nearest centi-part (if time is in seconds, this would be centiseconds), (compatible with timeToSmallString).
  // see https://github.com/phetsims/masses-and-springs/issues/156
  time = Utils.roundSymmetric( time * 100 ) / 100;

  // When showing units, don't show the "00:" prefix, see https://github.com/phetsims/scenery-phet/issues/378
  const timeInSeconds = time;

  // If no units are provided, then we assume the time is in seconds, and should be shown in mm:ss.cs
  let minutes = Math.floor( timeInSeconds / 60 );
  let seconds = Math.floor( timeInSeconds ) % 60;

  if ( seconds < 10 ) {
    seconds = '0' + seconds;
  }
  if ( minutes < 10 ) {
    minutes = '0' + minutes;
  }
  return minutes + ':' + seconds;
};

// the smaller hundredths-of-a-second string
const toCentiseconds = time => {

  // Round to the nearest centisecond (compatible with timeToSmallString).
  // see https://github.com/phetsims/masses-and-springs/issues/156
  time = Utils.roundSymmetric( time * 100 ) / 100;

  // Rounding after mod, in case there is floating-point error
  let centitime = Utils.roundSymmetric( time % 1 * 100 );
  if ( centitime < 10 ) {
    centitime = '0' + centitime;
  }
  return '.' + centitime;
};

// @public - for NumberDisplay, shows 12:34.56
StopwatchNode.numberFormatter = x => {
  const minutesAndSeconds = toMinutesAndSeconds( x );
  const centiseconds = toCentiseconds( x );
  return minutesAndSeconds + centiseconds;
};

// @public - We used to use Lucida Console, Arial, but Arial has smaller number width for "11" and hence was causing
// jitter. Neither Trebuchet MS and Lucida Grande is a monospace font, but the digits all appear to be monospace.
// Use Trebuchet first, since it has broader cross-platform support.
// Another advantage of using a non-monospace font (that has monospace digits) is that the : and . symbols aren't as
// wide as the numerals.  @ariel-phet and @samreid tested this combination of families on Mac/Chrome and Windows/Chrome
// and it seemed to work nicely, with no jitter.
StopwatchNode.NUMBER_FONT_FAMILY = 'Trebuchet MS,Lucida Grande,monospace';

// @public - for NumberDisplay, shows 12:34.56, but the ".56" is smaller
StopwatchNode.richNumberFormatter = x => {
  const minutesAndSeconds = toMinutesAndSeconds( x );
  const centiseconds = toCentiseconds( x );

  return `${minutesAndSeconds}${centiseconds}`;
};

// @public - for NumberDisplay, more customizable
StopwatchNode.getRichNumberFormatter = options => {
  options = merge( {
    showAsDecimal: false,
    bigNumberFont: 20,
    smallNumberFont: 14,
    unitsFont: 14,
    units: '',

    // Units cannot be baked into the i18n string because they can change independently
    valueUnitsPattern: sceneryPhetStrings.stopwatchValueUnitsPattern
  }, options );

  return x => {
    const minutesAndSeconds = options.showAsDecimal ? Math.floor( x ) : toMinutesAndSeconds( x );
    const centiseconds = toCentiseconds( x );

    return StringUtils.fillIn( options.valueUnitsPattern, {
      value: `${minutesAndSeconds}${centiseconds}`,
      units: `<span style="font-size: ${options.unitsFont}px; font-family:${StopwatchNode.NUMBER_FONT_FAMILY};">${options.units}</span>`
    } );
  };
};

export default StopwatchNode;<commit_msg>whitespace, see https://github.com/phetsims/collision-lab/issues/140<commit_after>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Shows a readout of the elapsed time, with play and pause buttons.  By default there are no units (which could be used
 * if all of a simulations time units are in 'seconds'), or you can specify a selection of units to choose from.
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Anton Ulyanov (Mlearner)
 */

import Bounds2 from '../../dot/js/Bounds2.js';
import Utils from '../../dot/js/Utils.js';
import InstanceRegistry from '../../phet-core/js/documentation/InstanceRegistry.js';
import inherit from '../../phet-core/js/inherit.js';
import merge from '../../phet-core/js/merge.js';
import StringUtils from '../../phetcommon/js/util/StringUtils.js';
import DragListener from '../../scenery/js/listeners/DragListener.js';
import Circle from '../../scenery/js/nodes/Circle.js';
import HBox from '../../scenery/js/nodes/HBox.js';
import Node from '../../scenery/js/nodes/Node.js';
import Path from '../../scenery/js/nodes/Path.js';
import VBox from '../../scenery/js/nodes/VBox.js';
import BooleanRectangularToggleButton from '../../sun/js/buttons/BooleanRectangularToggleButton.js';
import RectangularPushButton from '../../sun/js/buttons/RectangularPushButton.js';
import Tandem from '../../tandem/js/Tandem.js';
import DragBoundsProperty from './DragBoundsProperty.js';
import NumberDisplay from './NumberDisplay.js';
import PauseIconShape from './PauseIconShape.js';
import PlayIconShape from './PlayIconShape.js';
import sceneryPhet from './sceneryPhet.js';
import sceneryPhetStrings from './sceneryPhetStrings.js';
import ShadedRectangle from './ShadedRectangle.js';
import Stopwatch from './Stopwatch.js';
import UTurnArrowShape from './UTurnArrowShape.js';

/**
 * @param {Stopwatch} stopwatch
 * @param {Object} [options]
 */
function StopwatchNode( stopwatch, options ) {
  assert && assert( stopwatch instanceof Stopwatch, `invalid stopwatch: ${stopwatch}` );

  options = merge( {

    cursor: 'pointer',
    iconHeight: 10,
    numberDisplayRange: Stopwatch.ZERO_TO_ALMOST_SIXTY, // Just for sizing the display, sized for 59:59.99 (mm:ss) or 3599.99 (decimal)
    iconFill: 'black',
    iconLineWidth: 1,
    backgroundBaseColor: 'rgb( 80, 130, 230 )',
    buttonBaseColor: '#DFE0E1',
    xSpacing: 6, // horizontal space between the buttons
    ySpacing: 6, // vertical space between readout and buttons
    xMargin: 8,
    yMargin: 8,

    // options propagated to the NumberDisplay
    numberDisplayOptions: {
      numberFormatter: StopwatchNode.richNumberFormatter,
      useRichText: true,
      align: 'right',
      cornerRadius: 4,
      xMargin: 4,
      yMargin: 2,
      pickable: false // allow dragging by the number display
    },

    visibleBoundsProperty: null, // {Property.<Bounds2>|null} if provided, the node is draggable within the bounds

    // Tandem is required to make sure the buttons are instrumented
    tandem: Tandem.REQUIRED,

    // options propagated to the DragListener.
    dragListenerOptions: null
  }, options );
  assert && assert( !options.hasOwnProperty( 'maxValue' ), 'options.maxValue no longer supported' );

  assert && assert( options.xSpacing >= 0, 'Buttons cannot overlap' );
  assert && assert( options.ySpacing >= 0, 'Buttons cannot overlap the readout' );

  // Create the StopwatchReadoutNode.
  this.numberDisplay = new NumberDisplay( stopwatch.timeProperty, options.numberDisplayRange, options.numberDisplayOptions );

  // Buttons ----------------------------------------------------------------------------

  const resetPath = new Path( new UTurnArrowShape( options.iconHeight ), {
    fill: options.iconFill
  } );

  const playIconHeight = resetPath.height;
  const playIconWidth = 0.8 * playIconHeight;
  const playPath = new Path( new PlayIconShape( playIconWidth, playIconHeight ), {
    fill: options.iconFill
  } );

  const pausePath = new Path( new PauseIconShape( 0.75 * playIconWidth, playIconHeight ), {
    fill: options.iconFill
  } );

  const playPauseButton = new BooleanRectangularToggleButton( pausePath, playPath, stopwatch.isRunningProperty, {
    baseColor: options.buttonBaseColor,
    tandem: options.tandem.createTandem( 'playPauseButton' )
  } );

  const resetButton = new RectangularPushButton( {
    listener: function resetTimer() {
      stopwatch.isRunningProperty.set( false );
      stopwatch.timeProperty.set( 0 );
    },
    content: resetPath,
    baseColor: options.buttonBaseColor,
    tandem: options.tandem.createTandem( 'resetButton' )
  } );

  const contents = new VBox( {
    spacing: options.ySpacing,
    children: [
      this.numberDisplay,
      new HBox( {
        spacing: options.xSpacing,
        children: [ resetButton, playPauseButton ]
      } )
    ]
  } );

  // Background panel ----------------------------------------------------------------------------

  const backgroundNode = new ShadedRectangle( new Bounds2( 0, 0,
    contents.width + 2 * options.xMargin, contents.height + 2 * options.yMargin ), {
    baseColor: options.backgroundBaseColor
  } );
  contents.center = backgroundNode.center;

  assert && assert( !options.children, 'StopwatchNode sets children' );
  options.children = [ backgroundNode, contents ];

  Node.call( this, options );

  // @public (read-only) - Target for drag listeners
  this.dragTarget = backgroundNode;

  // Disable the reset button when time is zero, and enable the play/pause button when not at the max time
  const timeListener = time => {
    resetButton.enabled = time > 0;
    playPauseButton.enabled = time < stopwatch.timeProperty.range.max;
  };
  stopwatch.timeProperty.link( timeListener );

  // Put a red dot at the origin, for debugging layout.
  if ( phet.chipper.queryParameters.dev ) {
    this.addChild( new Circle( 3, { fill: 'red' } ) );
  }

  const stopwatchVisibleListener = visible => {
    this.visible = visible;
    if ( visible ) {
      this.moveToFront();
    }
    else {

      // interrupt user interactions when the stopwatch is made invisible
      this.interruptSubtreeInput();
    }
  };
  stopwatch.isVisibleProperty.link( stopwatchVisibleListener );

  // Move to the stopwatch's position
  const stopwatchPositionListener = position => this.setTranslation( position );
  stopwatch.positionProperty.link( stopwatchPositionListener );

  // @public (read-only) {DragListener|null} -- reassigned below, if draggable.  Can be used for forwarding press
  // events when dragging out of a toolbox.
  this.dragListener = null;

  let dragBoundsProperty = null;

  if ( options.visibleBoundsProperty ) {

    // drag bounds, adjusted to keep this entire Node inside visible bounds
    dragBoundsProperty = new DragBoundsProperty( this, options.visibleBoundsProperty );

    // interrupt user interactions when the visible bounds changes, such as a device orientation change or window resize
    options.visibleBoundsProperty.link( () => this.interruptSubtreeInput() );

    // If the stopwatch is outside the drag bounds, move it inside.
    dragBoundsProperty.link( dragBounds => {
      if ( !dragBounds.containsPoint( stopwatch.positionProperty ) ) {
        stopwatch.positionProperty.value = dragBounds.closestPointTo( stopwatch.positionProperty.value );
      }
    } );

    // dragging, added to background so that other UI components get input events on touch devices
    const dragListenerOptions = merge( {
      targetNode: this,
      positionProperty: stopwatch.positionProperty,
      dragBoundsProperty: dragBoundsProperty,
      tandem: options.tandem.createTandem( 'dragListener' )
    }, options.dragListenerOptions );

    // The StopwatchNode always calls moveToFront on drag start
    const startOption = dragListenerOptions.start;
    dragListenerOptions.start = () => {
      this.moveToFront();
      startOption && startOption();
    };

    this.dragListener = new DragListener( dragListenerOptions );
    this.dragTarget.addInputListener( this.dragListener );
  }

  this.addLinkedElement( stopwatch, {
    tandem: options.tandem.createTandem( 'stopwatch' )
  } );

  // @private
  this.disposeStopwatchNode = function() {
    stopwatch.isVisibleProperty.unlink( stopwatchVisibleListener );
    stopwatch.timeProperty.unlink( timeListener );
    stopwatch.positionProperty.unlink( stopwatchPositionListener );

    this.numberDisplay.dispose();
    resetButton.dispose();
    playPauseButton.dispose();

    if ( this.dragListener ) {
      this.dragTarget.removeInputListener( this.dragListener );
      this.dragListener.dispose();
    }

    dragBoundsProperty && dragBoundsProperty.dispose();
  };

  // support for binder documentation, stripped out in builds and only runs when ?binder is specified
  assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'scenery-phet', 'StopwatchNode', this );
}

sceneryPhet.register( 'StopwatchNode', StopwatchNode );

inherit( Node, StopwatchNode, {

  /**
   * @param {function<number,string>} numberFormatter
   * @public
   */
  setNumberFormatter( numberFormatter ) {
    this.numberDisplay.setNumberFormatter( numberFormatter );
  },

  // @public - redraw the text when something other than the numberProperty changes (such as units, formatter, etc).
  redrawNumberDisplay() {
    this.numberDisplay.recomputeText();
  },

  /**
   * Release resources when no longer be used.
   * @public
   * @override
   */
  dispose: function() {
    this.disposeStopwatchNode();
    Node.prototype.dispose.call( this );
  }
} );

// the full-sized minutes and seconds string
const toMinutesAndSeconds = time => {

  // Round to the nearest centi-part (if time is in seconds, this would be centiseconds), (compatible with timeToSmallString).
  // see https://github.com/phetsims/masses-and-springs/issues/156
  time = Utils.roundSymmetric( time * 100 ) / 100;

  // When showing units, don't show the "00:" prefix, see https://github.com/phetsims/scenery-phet/issues/378
  const timeInSeconds = time;

  // If no units are provided, then we assume the time is in seconds, and should be shown in mm:ss.cs
  let minutes = Math.floor( timeInSeconds / 60 );
  let seconds = Math.floor( timeInSeconds ) % 60;

  if ( seconds < 10 ) {
    seconds = '0' + seconds;
  }
  if ( minutes < 10 ) {
    minutes = '0' + minutes;
  }
  return minutes + ':' + seconds;
};

// the smaller hundredths-of-a-second string
const toCentiseconds = time => {

  // Round to the nearest centisecond (compatible with timeToSmallString).
  // see https://github.com/phetsims/masses-and-springs/issues/156
  time = Utils.roundSymmetric( time * 100 ) / 100;

  // Rounding after mod, in case there is floating-point error
  let centitime = Utils.roundSymmetric( time % 1 * 100 );
  if ( centitime < 10 ) {
    centitime = '0' + centitime;
  }
  return '.' + centitime;
};

// @public - for NumberDisplay, shows 12:34.56
StopwatchNode.numberFormatter = x => {
  const minutesAndSeconds = toMinutesAndSeconds( x );
  const centiseconds = toCentiseconds( x );
  return minutesAndSeconds + centiseconds;
};

// @public - We used to use Lucida Console, Arial, but Arial has smaller number width for "11" and hence was causing
// jitter. Neither Trebuchet MS and Lucida Grande is a monospace font, but the digits all appear to be monospace.
// Use Trebuchet first, since it has broader cross-platform support.
// Another advantage of using a non-monospace font (that has monospace digits) is that the : and . symbols aren't as
// wide as the numerals.  @ariel-phet and @samreid tested this combination of families on Mac/Chrome and Windows/Chrome
// and it seemed to work nicely, with no jitter.
StopwatchNode.NUMBER_FONT_FAMILY = 'Trebuchet MS,Lucida Grande,monospace';

// @public - for NumberDisplay, shows 12:34.56, but the ".56" is smaller
StopwatchNode.richNumberFormatter = x => {
  const minutesAndSeconds = toMinutesAndSeconds( x );
  const centiseconds = toCentiseconds( x );

  return `${minutesAndSeconds}${centiseconds}`;
};

// @public - for NumberDisplay, more customizable
StopwatchNode.getRichNumberFormatter = options => {
  options = merge( {
    showAsDecimal: false,
    bigNumberFont: 20,
    smallNumberFont: 14,
    unitsFont: 14,
    units: '',

    // Units cannot be baked into the i18n string because they can change independently
    valueUnitsPattern: sceneryPhetStrings.stopwatchValueUnitsPattern
  }, options );

  return x => {
    const minutesAndSeconds = options.showAsDecimal ? Math.floor( x ) : toMinutesAndSeconds( x );
    const centiseconds = toCentiseconds( x );

    return StringUtils.fillIn( options.valueUnitsPattern, {
      value: `${minutesAndSeconds}${centiseconds}`,
      units: `<span style="font-size: ${options.unitsFont}px; font-family:${StopwatchNode.NUMBER_FONT_FAMILY};">${options.units}</span>`
    } );
  };
};

export default StopwatchNode;<|endoftext|>


<commit_before>var async = require('async');
var _ = require('underscore');
var hjs = require('hjs');

module.exports = function(app){
	app.param('event', function(req, res, next, id){

		var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
		if(!checkForHexRegExp.test(id)){
			return next();
		}
		return app.model.Event.findOne({ _id: id }).exec(function(err, event){
			if(err) return next(err);
			if(event){
				res.bootstrap('event', event);
			}
			return next();
		});

	});

	return njax_events = {
		event_tpl:{},
		event_compiled_tpl:{},
		query:function(query, callback){
			if(!query){
				return callback(new Error("Not a valid query"));
			}else if(_.isObject(query)){
				if(query._njax_type){
					var url_query = [];
					if(query.url){
						url_query.push({ entity_url: query.url });
					}
					if(query.api_url){
						url_query.push({ entity_url: query.api_url });
					}
					query = {/*
						entity_type:query._njax_type,*/
						$or:url_query
					}
				}

			}
			query.mutedDate = null;

			return app.model.Event.find(query).sort({ 'creDate': -1 }).exec(function(err, events){
				if(err) return callback(err);

				return  callback(null, events);

			});

		},
		queryNewsfeed:function(data){
			if(!data){
				data = {};
			}
			//TODO: Paginate
			var query = data.query;
			if(!query){
				 query = {};
			}
			var EVENTS_PER_PAGE = data.events_per_page || 25;
			if(!data.query_extra){
				var query_extra = {
					limit:EVENTS_PER_PAGE,
					sort:{ 'creDate': -1 }
				}
			}
			if(data.page){
				query_extra.skip = data.page * EVENTS_PER_PAGE
			}
			if(!query.$and){
				query.$and = [];
			}
			query.mutedDate = null;

			var visible_event_query = [];
			for(var event_namespace in njax_events.event_tpl){
				visible_event_query.push({ event_namespace: event_namespace });
			}
			query.$and.push({ $or: visible_event_query });
			return app.model.Event.find(query, null, query_extra);
		},
		registerEventTpl:function(event, template){
			if(_.isArray(event)){
				for(var i in event){
					njax_events.registerEventTpl(event[i], template)
				}
				return;
			}
			njax_events.event_tpl[event] = template;

		},

		render:function(event, req, res){
			var html = '';
			if(_.isArray(event)){
				for(var i in event){
					html += njax_events.render(event[i], req, res);
				}
			}else{
				//Insert logic for event
				var partial = njax_events.event_tpl[event.event_namespace];

				if(!partial){//Its cool, just ignore it
					return '';
				}

				if(!njax_events.event_compiled_tpl[partial]){
					var partial_loc = app.njax.setup_partials(partial);

					njax_events.event_compiled_tpl[partial] = hjs.fcompile(partial_loc + '.hjs', { cache:true });
				}
				//TODO:Render it
				var _data = _.clone(event.data);
				_data._event = event.toObject();
				_data._can_hide = false;
				if(req.is_admin){
					_data._can_hide = true;
				}
				if(req.user && _data.user && (req.user._id == _data.user._id)){
					_data._can_hide = true;
				}
				html += njax_events.event_compiled_tpl[partial].render(_data, res.locals.partials);
			}
			return html;
		},
		config:function(config, val){
			if(_.isObject(config)){
				for(var i in config){
					njax_events.config(i, config);
				}
				return;
			}else if(_.isString(config) && val){
				switch(config){
					case('event_pre_save'):
						if(!_.isFunction(val)){
							throw new Error("Invalid njax.events.even_pre_save value. Must be function");
						}
						njax_events.event_pre_save = val;
						return;
					break;
				}
			}
			throw new Error("Invalid NJax-Events module Config Property: " + config);

		},
		event_pre_save:function(event, callback){
			return callback(null, event);
		}
	}
}<commit_msg>Code flying from the fingertips<commit_after>var async = require('async');
var _ = require('underscore');
var hjs = require('hjs');

module.exports = function(app){
	app.param('event', function(req, res, next, id){

		var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
		if(!checkForHexRegExp.test(id)){
			return next();
		}
		return app.model.Event.findOne({ _id: id }).exec(function(err, event){
			if(err) return next(err);
			if(event){
				res.bootstrap('event', event);
			}
			return next();
		});

	});

	return njax_events = {
		event_tpl:{},
		event_compiled_tpl:{},
		query:function(query, callback){
			if(!query){
				return callback(new Error("Not a valid query"));
			}else if(_.isObject(query)){
				if(query._njax_type){
					var url_query = [];
					if(query.url){
						url_query.push({ entity_url: query.url });
					}
					if(query.api_url){
						url_query.push({ entity_url: query.api_url });
					}
					query = {/*
						entity_type:query._njax_type,*/
						$or:url_query
					}
				}

			}
			query.mutedDate = null;

			return app.model.Event.find(query).sort({ 'creDate': -1 }).exec(function(err, events){
				if(err) return callback(err);

				return  callback(null, events);

			});

		},
		queryNewsfeed:function(data){
			if(!data){
				data = {};
			}
			//TODO: Paginate
			var query = data.query;
			if(!query){
				 query = {};
			}
			var EVENTS_PER_PAGE = data.events_per_page || 25;
			var query_extra = data.query_extra;
			if(!query_extra){
				query_extra = {
					limit:EVENTS_PER_PAGE,
					sort:{ 'creDate': -1 }
				}
			}
			if(data.page){
				query_extra.skip = data.page * EVENTS_PER_PAGE
			}
			if(!query.$and){
				query.$and = [];
			}
			query.mutedDate = null;

			var visible_event_query = [];
			for(var event_namespace in njax_events.event_tpl){
				visible_event_query.push({ event_namespace: event_namespace });
			}
			query.$and.push({ $or: visible_event_query });
			return app.model.Event.find(query, null, query_extra);
		},
		registerEventTpl:function(event, template){
			if(_.isArray(event)){
				for(var i in event){
					njax_events.registerEventTpl(event[i], template)
				}
				return;
			}
			njax_events.event_tpl[event] = template;

		},

		render:function(event, req, res){
			var html = '';
			if(_.isArray(event)){
				for(var i in event){
					html += njax_events.render(event[i], req, res);
				}
			}else{
				//Insert logic for event
				var partial = njax_events.event_tpl[event.event_namespace];

				if(!partial){//Its cool, just ignore it
					return '';
				}

				if(!njax_events.event_compiled_tpl[partial]){
					var partial_loc = app.njax.setup_partials(partial);

					njax_events.event_compiled_tpl[partial] = hjs.fcompile(partial_loc + '.hjs', { cache:true });
				}
				//TODO:Render it
				var _data = _.clone(event.data);
				_data._event = event.toObject();
				_data._can_hide = false;
				if(req.is_admin){
					_data._can_hide = true;
				}
				if(req.user && _data.user && (req.user._id == _data.user._id)){
					_data._can_hide = true;
				}
				html += njax_events.event_compiled_tpl[partial].render(_data, res.locals.partials);
			}
			return html;
		},
		config:function(config, val){
			if(_.isObject(config)){
				for(var i in config){
					njax_events.config(i, config);
				}
				return;
			}else if(_.isString(config) && val){
				switch(config){
					case('event_pre_save'):
						if(!_.isFunction(val)){
							throw new Error("Invalid njax.events.even_pre_save value. Must be function");
						}
						njax_events.event_pre_save = val;
						return;
					break;
				}
			}
			throw new Error("Invalid NJax-Events module Config Property: " + config);

		},
		event_pre_save:function(event, callback){
			return callback(null, event);
		}
	}
}<|endoftext|>


<commit_before>/*  Author: Andre Bakker, VexUI project
 * 	This class requires the MIDI.js in the project to work correctly.
 *
 */

Player = function(){ //(handler){
	this.events = [];
	// this.handler = handler;
	this.currentTime = 0;
	this.currentEventIndex = 0;
	this.ready = false;
	this.loadInstrument("acoustic_grand_piano");
	this.playing = false;
};

Player.prototype.loadInstrument = function(instrumentName){ //, onReady){
	var player = this;
	//Initialize the player
	MIDI.loadPlugin({
		soundfontUrl: "./soundfont/",
		instrument: instrumentName,
		callback: function(){
			player.ready = true;
			// if(onReady)
			// 	onReady();
		}
	});
}

Player.prototype.onPlayFinished = function(callback){
	this.callback = callback;
}

/**
 * Add functionality to add events manually, instead of using loadFile
 * @param event -> must have these attributes:
 * channel -> integer
 * subtype -> 'noteOn' | 'noteOff' | 'chordOn' | 'chordOff'
 * noteNumber -> integer
 * velocity -> integer (only required when subtype == 'noteOn' | 'chordOn')
 * queuedTime -> float (when the event will be triggered)
 */
Player.prototype.addEvent = function(event){
	this.events.push(event);
};

Player.prototype.addEvents = function(eventList){
	this.events = this.events.concat(eventList);
};

Player.prototype.play = function(self){
	console.log('player play()');
	if(self === undefined)
		self = this;
	self.playing = true;
	if(self.currentEventIndex >= self.events.length){
		self.playing = false;
		return self.callback();
	}

	var event = self.events[self.currentEventIndex];

	if(self.currentTime <= event.queuedTime){
		//Fire the event
		self.fireEvent(event);

		//Increment the current event and add current time
		if(self.currentEventIndex + 1 >= self.events.length){
			self.playing = false;
			return self.callback();
		}
		var timeUntilNextEvent = self.events[self.currentEventIndex + 1].queuedTime -
								self.events[self.currentEventIndex].queuedTime;

		self.currentEventIndex++;
		self.currentTime += timeUntilNextEvent;

		self.scheduledId = setTimeout(self.play, timeUntilNextEvent * 1000, self);
	}

};

Player.prototype.stop = function(){
	console.log('player stop');
	if(this.scheduledId){
		clearTimeout(this.scheduledId);
		this.clear();
		this.playing = false;
		while(this.events.length){
			var event = this.events.pop();
			if(event.subtype == "noteOff" || event.subtype == "chordOff")
				this.fireEvent(event);
		}
	}
};

Player.prototype.fireEvent = function(event){
	switch(event.subtype){
		case 'noteOn':
			MIDI.noteOn(event.channel, event.noteNumber, event.velocity, 0);
      $('svg #vf-'+event.note.id).colourNote("red");
			// event.note.setHighlight(true);
			// self.handler.redraw();
			break;
		case 'noteOff':
			MIDI.noteOff(event.channel, event.noteNumber, 0);
      $('svg #vf-'+event.note.id).colourNote("black");
			// event.note.setHighlight(false);
			// self.handler.redraw();
			break;
		case 'chordOn':
			MIDI.chordOn(event.channel, event.noteNumber, event.velocity, 0);
			// event.note.setHighlight(true);
			// self.handler.redraw();
			break;
		case 'chordOff':
			MIDI.chordOff(event.channel, event.noteNumber, 0);
			// event.note.setHighlight(false);
			// self.handler.redraw();
			break;
	}
};

Player.prototype.clear = function(){
	this.scheduledId = null;
	this.currentTime = 0;
	this.currentEventIndex = 0;
};

editor.player = new Player();

editor.play = function(){
	console.log('editor play()');
	var playButton, stopButton;

	playButton = document.getElementById("button-play");
	stopButton = document.getElementById("button-stop");

	//enable stop, disable play
	stopButton.disabled = false;
	playButton.disabled = true;

	//TODO RPM should be set outside...
	var rpm = 120;
	var playInfo = {
			delay: 0,
			rpm: rpm,
			defaultTime : (rpm / 60) // to seconds
			};
	//var script = "MIDI.setVolume(0, 127);";
	var playEvents = [];
	for(var i = getSelectedMeasureIndex(); i < gl_VfStaves.length; i++){
		var stave = gl_VfStaves[i];
		//set clef to playinfo
		playInfo.clef = stave.clef;

		//Call initial barline play events
		// var barNote = new Vex.Flow.BarNote();
		// barNote.setType(stave.modifiers[0].barline);
		// playEvents = playEvents.concat(barNote.getPlayEvents(playInfo, playEvents));

		for(var j = getSelectedNoteIndex(); j < gl_VfStaveNotes[i].length; j++){
			var staveNote = gl_VfStaveNotes[i][j];
			playEvents = playEvents.concat(staveNote.getPlayEvents(playInfo));//, playEvents));
		}

		//Call final barline play events
		// barNote.setType(stave.modifiers[1].barline);
		// playEvents = playEvents.concat(barNote.getPlayEvents(playInfo, playEvents));
	}

	editor.player.addEvents(playEvents);
	editor.player.onPlayFinished(function(){
		//Reenable play and disable stop
		playButton.disabled = false;
		stopButton.disabled = true;
	});
	editor.player.play();
};

editor.stop = function(){
	console.log('editor stop');
	editor.player.stop();

	playButton = document.getElementById("button-play");
	stopButton = document.getElementById("button-stop");

	//enable stop, disable play
	playButton.disabled = false;
	stopButton.disabled = true;

};<commit_msg>fixed playing from selected non-first note, indentation<commit_after>/*  Author: Andre Bakker, VexUI project
 *  This class requires the MIDI.js in the project to work correctly.
 *
 */

Player = function(){ //(handler){
  this.events = [];
  // this.handler = handler;
  this.currentTime = 0;
  this.currentEventIndex = 0;
  this.ready = false;
  this.loadInstrument("acoustic_grand_piano");
  this.playing = false;
};

Player.prototype.loadInstrument = function(instrumentName){ //, onReady){
  var player = this;
  //Initialize the player
  MIDI.loadPlugin({
    soundfontUrl: "./soundfont/",
    instrument: instrumentName,
    callback: function(){
      player.ready = true;
      // if(onReady)
      //  onReady();
    }
  });
}

Player.prototype.onPlayFinished = function(callback){
  this.callback = callback;
}

/**
 * Add functionality to add events manually, instead of using loadFile
 * @param event -> must have these attributes:
 * channel -> integer
 * subtype -> 'noteOn' | 'noteOff' | 'chordOn' | 'chordOff'
 * noteNumber -> integer
 * velocity -> integer (only required when subtype == 'noteOn' | 'chordOn')
 * queuedTime -> float (when the event will be triggered)
 */
Player.prototype.addEvent = function(event){
  this.events.push(event);
};

Player.prototype.addEvents = function(eventList){
  this.events = this.events.concat(eventList);
};

Player.prototype.play = function(self){
  console.log('player play()');
  if(self === undefined)
    self = this;
  self.playing = true;
  if(self.currentEventIndex >= self.events.length){
    self.playing = false;
    return self.callback();
  }

  var event = self.events[self.currentEventIndex];

  if(self.currentTime <= event.queuedTime){
    //Fire the event
    self.fireEvent(event);

    //Increment the current event and add current time
    if(self.currentEventIndex + 1 >= self.events.length){
      self.playing = false;
      return self.callback();
    }
    var timeUntilNextEvent = self.events[self.currentEventIndex + 1].queuedTime -
                self.events[self.currentEventIndex].queuedTime;

    self.currentEventIndex++;
    self.currentTime += timeUntilNextEvent;

    self.scheduledId = setTimeout(self.play, timeUntilNextEvent * 1000, self);
  }

};

Player.prototype.stop = function(){
  console.log('player stop');
  if(this.scheduledId){
    clearTimeout(this.scheduledId);
    this.clear();
    this.playing = false;
    while(this.events.length){
      var event = this.events.pop();
      if(event.subtype == "noteOff" || event.subtype == "chordOff")
        this.fireEvent(event);
    }
  }
};

Player.prototype.fireEvent = function(event){
  switch(event.subtype){
    case 'noteOn':
      MIDI.noteOn(event.channel, event.noteNumber, event.velocity, 0);
      $('svg #vf-'+event.note.id).colourNote("red");
      // event.note.setHighlight(true);
      // self.handler.redraw();
      break;
    case 'noteOff':
      MIDI.noteOff(event.channel, event.noteNumber, 0);
      $('svg #vf-'+event.note.id).colourNote("black");
      // event.note.setHighlight(false);
      // self.handler.redraw();
      break;
    case 'chordOn':
      MIDI.chordOn(event.channel, event.noteNumber, event.velocity, 0);
      // event.note.setHighlight(true);
      // self.handler.redraw();
      break;
    case 'chordOff':
      MIDI.chordOff(event.channel, event.noteNumber, 0);
      // event.note.setHighlight(false);
      // self.handler.redraw();
      break;
  }
};

Player.prototype.clear = function(){
  this.scheduledId = null;
  this.currentTime = 0;
  this.currentEventIndex = 0;
};

editor.player = new Player();

editor.play = function(){
  console.log('editor play()');
  var playButton, stopButton;

  playButton = document.getElementById("button-play");
  stopButton = document.getElementById("button-stop");

  //enable stop, disable play
  stopButton.disabled = false;
  playButton.disabled = true;

  //TODO RPM should be set outside...
  var rpm = 120;
  var playInfo = {
      delay: 0,
      rpm: rpm,
      defaultTime : (rpm / 60) // to seconds
      };
  //var script = "MIDI.setVolume(0, 127);";
  var playEvents = [];

  var selMeasureIn = getSelectedMeasureIndex();
  var selNoteIn = getSelectedNoteIndex();

  for(var i = selMeasureIn; i < gl_VfStaves.length; i++){
    var stave = gl_VfStaves[i];
    //set clef to playinfo
    playInfo.clef = stave.clef;

    //Call initial barline play events
    // var barNote = new Vex.Flow.BarNote();
    // barNote.setType(stave.modifiers[0].barline);
    // playEvents = playEvents.concat(barNote.getPlayEvents(playInfo, playEvents));

    var j = (i == selMeasureIn) ? selNoteIn : 0;

    for(; j < gl_VfStaveNotes[i].length; j++){

      var staveNote = gl_VfStaveNotes[i][j];
      playEvents = playEvents.concat(staveNote.getPlayEvents(playInfo));//, playEvents));
    }

    //Call final barline play events
    // barNote.setType(stave.modifiers[1].barline);
    // playEvents = playEvents.concat(barNote.getPlayEvents(playInfo, playEvents));
  }

  editor.player.addEvents(playEvents);
  editor.player.onPlayFinished(function(){
    //Reenable play and disable stop
    playButton.disabled = false;
    stopButton.disabled = true;
  });
  editor.player.play();
};

editor.stop = function(){
  console.log('editor stop');
  editor.player.stop();

  playButton = document.getElementById("button-play");
  stopButton = document.getElementById("button-stop");

  //enable stop, disable play
  playButton.disabled = false;
  stopButton.disabled = true;

};<|endoftext|>


<commit_before>const debug = require('debug')('moveTempFile')
const fs = require('fs-extra')

/**
 * Moves the temporary file to the final destination.
 */
exports.moveTempFile = (ctx, task) => {
  const tempFile = ctx.tempFile
  const outputFilename = ctx.outputFilename
  debug(`copying ${tempFile} to ${outputFilename}`)
  return new Promise((resolve, reject), () => {
    fs.move(tempFile, outputFilename, { overwrite: true }, (err) => {
      if (err) { return reject(err) }
      task.title = `Done. Saved to ${outputFilename}`
      resolve()
    })
  })
}
<commit_msg>fix(move-temp-file): Fix typo in move-temp-file module<commit_after>const debug = require('debug')('moveTempFile')
const fs = require('fs-extra')

/**
 * Moves the temporary file to the final destination.
 */
exports.moveTempFile = (ctx, task) => {
  const tempFile = ctx.tempFile
  const outputFilename = ctx.outputFilename
  debug(`copying ${tempFile} to ${outputFilename}`)
  return new Promise((resolve, reject) => {
    fs.move(tempFile, outputFilename, { overwrite: true }, (err) => {
      if (err) { return reject(err) }
      task.title = `Done. Saved to ${outputFilename}`
      resolve()
    })
  })
}
<|endoftext|>


<commit_before>/*!
 * Print button for Buttons and DataTables.
 * 2015 SpryMedia Ltd - datatables.net/license
 */

(function($, DataTable) {
"use strict";

var _link = document.createElement( 'a' );

/**
 * Convert a `link` tag's URL from a relative to an absolute address so it will
 * work correctly in the popup window which has no base URL.
 *
 * @param  {node}     el Element to convert
 */
var _relToAbs = function( el ) {
	var url;
	var clone = $(el).clone()[0];
	var linkHost;

	if ( clone.nodeName.toLowerCase() === 'link' ) {
		_link.href = clone.href;
		linkHost = _link.host;

		// IE doesn't have a trailing slash on the host
		if ( linkHost.indexOf('/') === -1 ) {
			linkHost += '/';
		}

		clone.href = _link.protocol+"//"+linkHost+_link.pathname+_link.search;
	}

	return clone.outerHTML;
};

DataTable.ext.buttons.print = {
	className: 'buttons-print',

	text: function ( dt ) {
		return dt.i18n( 'buttons.print', 'Print' );
	},

	action: function ( e, dt, button, config ) {
		var data = dt.buttons.exportData( config.exportOptions );
		var addRow = function ( d, tag ) {
			var str = '
';

			for ( var i=0, ien=d.length ; i<ien ; i++ ) {
				str += '<'+tag+'>'+d[i]+'</'+tag+'>';
			}

			return str + '
';
		};

		// Construct a table for printing
		var html = '
';

		if ( config.header ) {
			html += '
'+ addRow( data.header, 'th' ) +'
';
		}

		html += '
';
		for ( var i=0, ien=data.body.length ; i<ien ; i++ ) {
			html += addRow( data.body[i], 'td' );
		}
		html += '
';

		if ( config.footer ) {
			html += '
'+ addRow( data.footer, 'th' ) +'
';
		}

		// Open a new window for the printable table
		var win = window.open( '', '' );
		var title = config.title.replace( '*', $('title').text() );

		win.document.close();

		// Inject the title and also a copy of the style and link tags from this
		// document so the table can retain its base styling. Note that we have
		// to use string manipulation as IE won't allow elements to be created
		// in the host document and then appended to the new window.
		var head = '<title>'+title+'</title>';
		$('style, link').each( function () {
			head += _relToAbs( this );
		} );

		$(win.document.head).html( head );

		// Inject the table and other surrounding information
		$(win.document.body).html(
			'
'+title+'
'+
			'
'+config.message+'
'+
			html
		);

		if ( config.customize ) {
			config.customize( win );
		}

		setTimeout( function () {
			if ( config.autoPrint ) {
				win.print(); // blocking - so close will not
				win.close(); // execute until this is done
			}
		}, 250 );
	},

	title: '*',

	message: '',

	exportOptions: {},

	header: true,

	footer: false,

	autoPrint: true,

	customize: null
};

})(jQuery, jQuery.fn.dataTable);
<commit_msg>Fix: Prevent double slash in Chrome<commit_after>/*!
 * Print button for Buttons and DataTables.
 * 2015 SpryMedia Ltd - datatables.net/license
 */

(function($, DataTable) {
"use strict";

var _link = document.createElement( 'a' );

/**
 * Convert a `link` tag's URL from a relative to an absolute address so it will
 * work correctly in the popup window which has no base URL.
 *
 * @param  {node}     el Element to convert
 */
var _relToAbs = function( el ) {
	var url;
	var clone = $(el).clone()[0];
	var linkHost;

	if ( clone.nodeName.toLowerCase() === 'link' ) {
		_link.href = clone.href;
		linkHost = _link.host;

		// IE doesn't have a trailing slash on the host
		// Chrome has it on the pathname
		if ( linkHost.indexOf('/') === -1 && _link.pathname.indexOf('/') !== 0) {
			linkHost += '/';
		}

		clone.href = _link.protocol+"//"+linkHost+_link.pathname+_link.search;
	}

	return clone.outerHTML;
};

DataTable.ext.buttons.print = {
	className: 'buttons-print',

	text: function ( dt ) {
		return dt.i18n( 'buttons.print', 'Print' );
	},

	action: function ( e, dt, button, config ) {
		var data = dt.buttons.exportData( config.exportOptions );
		var addRow = function ( d, tag ) {
			var str = '
';

			for ( var i=0, ien=d.length ; i<ien ; i++ ) {
				str += '<'+tag+'>'+d[i]+'</'+tag+'>';
			}

			return str + '
';
		};

		// Construct a table for printing
		var html = '
';

		if ( config.header ) {
			html += '
'+ addRow( data.header, 'th' ) +'
';
		}

		html += '
';
		for ( var i=0, ien=data.body.length ; i<ien ; i++ ) {
			html += addRow( data.body[i], 'td' );
		}
		html += '
';

		if ( config.footer ) {
			html += '
'+ addRow( data.footer, 'th' ) +'
';
		}

		// Open a new window for the printable table
		var win = window.open( '', '' );
		var title = config.title.replace( '*', $('title').text() );

		win.document.close();

		// Inject the title and also a copy of the style and link tags from this
		// document so the table can retain its base styling. Note that we have
		// to use string manipulation as IE won't allow elements to be created
		// in the host document and then appended to the new window.
		var head = '<title>'+title+'</title>';
		$('style, link').each( function () {
			head += _relToAbs( this );
		} );

		$(win.document.head).html( head );

		// Inject the table and other surrounding information
		$(win.document.body).html(
			'
'+title+'
'+
			'
'+config.message+'
'+
			html
		);

		if ( config.customize ) {
			config.customize( win );
		}

		setTimeout( function () {
			if ( config.autoPrint ) {
				win.print(); // blocking - so close will not
				win.close(); // execute until this is done
			}
		}, 250 );
	},

	title: '*',

	message: '',

	exportOptions: {},

	header: true,

	footer: false,

	autoPrint: true,

	customize: null
};

})(jQuery, jQuery.fn.dataTable);
<|endoftext|>


<commit_before>(function() {

  this.Player = function() {

    var defaults = {
        videoId: null,
        playerClass: 'custom-player',
        width: 480,
        height: 270,
        autoPlay: null,
        controls: null,
        relatedVideos: 0,
        showInfo: null,
        wrap: null,
        wrapClass: 'custom-player-wrapper',
        playBtn: null,
        playBtnClass: 'play-btn',
        playBtnText: 'Play',
        pauseBtn: null,
        pauseBtnClass: 'pause-btn',
        pauseBtnText: 'Pause',
        mute: null,
        placement: null,
        videoContainer: null
    };

    // Add our defaults above into a global array of options
    if ( arguments[0] && typeof arguments[0] === 'object' ) {
      this.options = extendDefaults( defaults, arguments[0] );
    }

    if( document.readyState === 'complete' || document.readyState === 'interactive' ) {
      this.init();
    } else {
      // Run Player.init()
      document.addEventListener( 'DOMContentLoaded', ( this.init ).bind( this ), false);
    }

  };

  Player.prototype.init = function() {

    var mode;
    if ( this.options.element ) {
      mode = 'exists';
      this.video = this.videoExists();
    /*} else if ( this.options.wrap === 1 ) {
      mode = 'wrap';
      //this.addClasses(this.wrapper, ['hello', 'world']);
      this.video = this.videoGet(); */
    } else {
      mode = 'generate';
      this.video = this.videoGenerate();
    }

    this.appendYTDiv();

    if ( this.options.playBtn || this.options.pauseBtn ) {
      this.video = this.customControls( mode );
    }

    // Place Video on the page
    this.appendVideo();
    this.YTGenerate();
    this.assignListeners();

  };

  Player.prototype.appendYTDiv = function() {

    if ( this.options.videoContainer ) {
      this.youtubeDiv = this.video.querySelector('.' + this.options.videoContainer );
    } else {
      this.youtubeDiv = document.createElement( 'div' );
      this.video.appendChild( this.youtubeDiv );
    }

  };

  Player.prototype.YTGenerate = function() {

    var this_ = this,
        options = this_.options;

    var callback = ( function() {
      this_.player = new YT.Player( this.youtubeDiv, {
        videoId: options.videoId,
        height: options.height,
        width: options.width,
        playerVars: {
          autoplay: options.autoPlay,
          controls: options.controls,
          rel: options.relatedVideos,
          showinfo: options.showInfo,
        },
        events: {
          onReady: ( this_._onPlayerReady ).bind( this ),
          onStateChange: ( this_._onStateChange ).bind( this )
        }
      });

    }).bind( this );

    // Test if the youtube api is loaded
    if ( 'YT' in window ) {
      if ( window.YT.loaded === 0 ) {
        if ( window.onYouTubePlayerAPIReady ) {
          var oldonYouTubePlayerAPIReady = window.onYouTubePlayerAPIReady;
        }
        window.onYouTubePlayerAPIReady = function() {
          if ( window.onYouTubePlayerAPIReady ) {
            oldonYouTubePlayerAPIReady();
          }
          callback();
        };
      } else {
        callback();
      }
    } else {
      window.YT = {
        loaded: 0
      };

      window.onYouTubePlayerAPIReady = function() {
        callback();
      };

      var tag = document.createElement( 'script' );
      var firstScriptTag = document.getElementsByTagName( 'script' )[0];

      tag.src = 'https://www.youtube.com/iframe_api';
      firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
    }

  };

  // Controls
  Player.prototype.customControls = function( mode ) {
    // Play button
    if ( !this.options.playBtn.nodeName ) {
      this.playBtn = this.playBtnGenerate();
    } else {
      this.playBtn = this.playBtnExists();
    }

    // Pause button
    if ( !this.options.pauseBtn.nodeName ) {
      this.pauseBtn = this.pauseBtnGenerate();
    } else {
      this.pauseBtn = this.pauseBtnExists();
    }

    // Custom Controls
    if ( this.options.playBtn || this.options.pauseBtn ) {
      var control = document.createElement( 'div' );
      control.className = 'custom-controls';

      if ( this.playBtn ) {
        control.appendChild( this.playBtn );
      }
      if ( this.pauseBtn ) {
        control.appendChild( this.pauseBtn );
      }
      // Add controls to the generated video
      this.video.appendChild( control );
    }

    return this.video;

  };

  // Play Button
  Player.prototype.playBtnExists = function() {

    return this.options.playBtnClass;

  };
  Player.prototype.playBtnGenerate = function() {

    var el = document.createElement( 'button' );
    el.className = this.options.playBtnClass;
    el.innerHTML = this.options.playBtnText;

    return el;

  };

  // Pause Button
  Player.prototype.pauseBtnExists = function() {

    return this.options.pauseBtnClass;

  };
  Player.prototype.pauseBtnGenerate = function() {

    var el = document.createElement( 'button' );
    el.className = this.options.pauseBtnClass;
    el.innerHTML = this.options.pauseBtnText;

    return el;

  };

  // Generate the class
  Player.prototype.videoGenerate = function() {

    var el = document.createElement( 'div' );
    el.id = this.options.videoId;
    el.className = this.options.playerClass;

    return el;

  };

  Player.prototype.videoExists = function() {

    return this.options.element;

  };

  // Player.prototype.videoGet = function() {
  //   return document.querySelector( this.options.wrapClass );
  // };

  Player.prototype.appendVideo = function() {

    document.body.appendChild( this.video );

  };

  // Events

  // Listeners
  Player.prototype.assignListeners = function() {

    this.video.addEventListener('click', function( event ) {

      if ( event.target.nodeName === 'BUTTON' ) {
        var matches = matchesPolyfill();
        if ( event.target[ matches ]( '.' + this.options.playBtnClass ) ) {
          this.player.playVideo();
        } else  if ( event.target[ matches ]( '.' + this.options.pauseBtnClass ) ) {
          this.player.pauseVideo();
        }
      }
    }.bind( this ));

  };

  // Player ready
  Player.prototype._onPlayerReady = function( event ) {

    if ( this.options.mute === 1 ) {
      event.target.mute();
    }

  };
  // Player state change
  Player.prototype._onStateChange = function( event ) {};

  // Private Methods

  // Build the necessary markup
  // and inject it into the document if needed
  Player.prototype.generateEl = function( el, id, className ) {

    var docFrag, element;

    docFrag = document.createDocumentFragment();
    element = document.createElement( el );

    if ( id ) {
      element.id = id;
    }
    if ( className ) {
      element.className = className;
    }

    docFrag.appendChild( element );

    if ( this.options.placement !== null ) {
      var selector = document.querySelector( this.options.placement );
      if ( !selector ) { throw new selectorError( selector ); }
      selector.appendChild( docFrag );
    } else {
      document.body.appendChild( docFrag );
    }

    function selectorError( value ) {

      this.message = 'Make sure your selector is on the page';
      this.toString = function() {
        return this.value + ' ' + this.message;
      };
    }

    return element;

  };

  // Utility method to extend defaults with user options
  function extendDefaults( source, properties ) {

    var property;
    for ( property in properties ) {
      if ( properties.hasOwnProperty( property ) ) {
        source[ property ] = properties[ property ];
      }
    }
    return source;

  }

  function matchesPolyfill( el ) {

    var matches = document.body.matches || document.body.webkitMatchesSelector || document.body.msMatchesSelector;
    return matches.name;

  }

  // Look for all elements that has a data-video-id
  // and populate an array
  var getStandalone = function() {
    var playerData = document.querySelectorAll( '[data-video-id]' );
    var standaloneVideos = [];

    // Cut up the array and spit out the available videos
    [].slice.call( playerData ).forEach(function( vid ) {
      var options = JSON.parse( JSON.stringify( vid.dataset ) );
      vid.id = options.videoId;
      options.element = vid;
      standaloneVideos.push( new Player( options ) );
    });

    return standaloneVideos;
  };

  if( document.readyState === 'complete' || document.readyState === 'interactive' ) {
    getStandalone();
  } else {
    document.addEventListener( 'DOMContentLoaded', getStandalone, false);
  }

})();
<commit_msg>Created readyState function as it's being used in a few places<commit_after>(function() {

  this.Player = function() {

    var defaults = {
        videoId: null,
        playerClass: 'custom-player',
        width: 480,
        height: 270,
        autoPlay: null,
        controls: null,
        relatedVideos: 0,
        showInfo: null,
        wrap: null,
        wrapClass: 'custom-player-wrapper',
        playBtn: null,
        playBtnClass: 'play-btn',
        playBtnText: 'Play',
        pauseBtn: null,
        pauseBtnClass: 'pause-btn',
        pauseBtnText: 'Pause',
        mute: null,
        placement: null,
        videoContainer: null
    };

    // Add our defaults above into a global array of options
    if ( arguments[0] && typeof arguments[0] === 'object' ) {
      this.options = extendDefaults( defaults, arguments[0] );
    }

    if( readyState() ) {
      this.init();
    } else {
      // Run Player.init()
      document.addEventListener( 'DOMContentLoaded', ( this.init ).bind( this ), false);
    }

  };

  Player.prototype.init = function() {

    var mode;
    if ( this.options.element ) {
      mode = 'exists';
      this.video = this.videoExists();
    /*} else if ( this.options.wrap === 1 ) {
      mode = 'wrap';
      //this.addClasses(this.wrapper, ['hello', 'world']);
      this.video = this.videoGet(); */
    } else {
      mode = 'generate';
      this.video = this.videoGenerate();
    }

    this.appendYTDiv();

    if ( this.options.playBtn || this.options.pauseBtn ) {
      this.video = this.customControls( mode );
    }

    // Place Video on the page
    this.appendVideo();
    this.YTGenerate();
    this.assignListeners();

  };

  Player.prototype.appendYTDiv = function() {

    if ( this.options.videoContainer ) {
      this.youtubeDiv = this.video.querySelector('.' + this.options.videoContainer );
    } else {
      this.youtubeDiv = document.createElement( 'div' );
      this.video.appendChild( this.youtubeDiv );
    }

  };

  Player.prototype.YTGenerate = function() {

    var this_ = this,
        options = this_.options;

    var callback = ( function() {
      this_.player = new YT.Player( this.youtubeDiv, {
        videoId: options.videoId,
        height: options.height,
        width: options.width,
        playerVars: {
          autoplay: options.autoPlay,
          controls: options.controls,
          rel: options.relatedVideos,
          showinfo: options.showInfo,
        },
        events: {
          onReady: ( this_._onPlayerReady ).bind( this ),
          onStateChange: ( this_._onStateChange ).bind( this )
        }
      });

    }).bind( this );

    // Test if the youtube api is loaded
    if ( 'YT' in window ) {
      if ( window.YT.loaded === 0 ) {
        if ( window.onYouTubePlayerAPIReady ) {
          var oldonYouTubePlayerAPIReady = window.onYouTubePlayerAPIReady;
        }
        window.onYouTubePlayerAPIReady = function() {
          if ( window.onYouTubePlayerAPIReady ) {
            oldonYouTubePlayerAPIReady();
          }
          callback();
        };
      } else {
        callback();
      }
    } else {
      window.YT = {
        loaded: 0
      };

      window.onYouTubePlayerAPIReady = function() {
        callback();
      };

      var tag = document.createElement( 'script' );
      var firstScriptTag = document.getElementsByTagName( 'script' )[0];

      tag.src = 'https://www.youtube.com/iframe_api';
      firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
    }

  };

  // Controls
  Player.prototype.customControls = function( mode ) {
    // Play button
    if ( !this.options.playBtn.nodeName ) {
      this.playBtn = this.playBtnGenerate();
    } else {
      this.playBtn = this.playBtnExists();
    }

    // Pause button
    if ( !this.options.pauseBtn.nodeName ) {
      this.pauseBtn = this.pauseBtnGenerate();
    } else {
      this.pauseBtn = this.pauseBtnExists();
    }

    // Custom Controls
    if ( this.options.playBtn || this.options.pauseBtn ) {
      var control = document.createElement( 'div' );
      control.className = 'custom-controls';

      if ( this.playBtn ) {
        control.appendChild( this.playBtn );
      }
      if ( this.pauseBtn ) {
        control.appendChild( this.pauseBtn );
      }
      // Add controls to the generated video
      this.video.appendChild( control );
    }

    return this.video;

  };

  // Play Button
  Player.prototype.playBtnExists = function() {

    return this.options.playBtnClass;

  };
  Player.prototype.playBtnGenerate = function() {

    var el = document.createElement( 'button' );
    el.className = this.options.playBtnClass;
    el.innerHTML = this.options.playBtnText;

    return el;

  };

  // Pause Button
  Player.prototype.pauseBtnExists = function() {

    return this.options.pauseBtnClass;

  };
  Player.prototype.pauseBtnGenerate = function() {

    var el = document.createElement( 'button' );
    el.className = this.options.pauseBtnClass;
    el.innerHTML = this.options.pauseBtnText;

    return el;

  };

  // Generate the class
  Player.prototype.videoGenerate = function() {

    var el = document.createElement( 'div' );
    el.id = this.options.videoId;
    el.className = this.options.playerClass;

    return el;

  };

  Player.prototype.videoExists = function() {

    return this.options.element;

  };

  // Player.prototype.videoGet = function() {
  //   return document.querySelector( this.options.wrapClass );
  // };

  Player.prototype.appendVideo = function() {

    document.body.appendChild( this.video );

  };

  // Events

  // Listeners
  Player.prototype.assignListeners = function() {

    this.video.addEventListener('click', function( event ) {

      if ( event.target.nodeName === 'BUTTON' ) {
        var matches = matchesPolyfill();
        if ( event.target[ matches ]( '.' + this.options.playBtnClass ) ) {
          this.player.playVideo();
        } else  if ( event.target[ matches ]( '.' + this.options.pauseBtnClass ) ) {
          this.player.pauseVideo();
        }
      }
    }.bind( this ));

  };

  // Player ready
  Player.prototype._onPlayerReady = function( event ) {

    if ( this.options.mute === 1 ) {
      event.target.mute();
    }

  };
  // Player state change
  Player.prototype._onStateChange = function( event ) {};

  // Private Methods

  // Build the necessary markup
  // and inject it into the document if needed
  Player.prototype.generateEl = function( el, id, className ) {

    var docFrag, element;

    docFrag = document.createDocumentFragment();
    element = document.createElement( el );

    if ( id ) {
      element.id = id;
    }
    if ( className ) {
      element.className = className;
    }

    docFrag.appendChild( element );

    if ( this.options.placement !== null ) {
      var selector = document.querySelector( this.options.placement );
      if ( !selector ) { throw new selectorError( selector ); }
      selector.appendChild( docFrag );
    } else {
      document.body.appendChild( docFrag );
    }

    function selectorError( value ) {

      this.message = 'Make sure your selector is on the page';
      this.toString = function() {
        return this.value + ' ' + this.message;
      };
    }

    return element;

  };

  // Utility method to extend defaults with user options
  function extendDefaults( source, properties ) {

    var property;
    for ( property in properties ) {
      if ( properties.hasOwnProperty( property ) ) {
        source[ property ] = properties[ property ];
      }
    }
    return source;

  }

  function matchesPolyfill( el ) {

    var matches = document.body.matches || document.body.webkitMatchesSelector || document.body.msMatchesSelector;
    return matches.name;

  }

  // Look for all elements that has a data-video-id
  // and populate an array
  var getStandalone = function() {
    var playerData = document.querySelectorAll( '[data-video-id]' );
    var standaloneVideos = [];

    // Cut up the array and spit out the available videos
    [].slice.call( playerData ).forEach(function( vid ) {
      var options = JSON.parse( JSON.stringify( vid.dataset ) );
      vid.id = options.videoId;
      options.element = vid;
      standaloneVideos.push( new Player( options ) );
    });

    return standaloneVideos;
  };

  function readyState() {
    return document.readyState === 'complete' || document.readyState === 'interactive';
  }

  if( readyState() ) {
    getStandalone();
  } else {
    document.addEventListener( 'DOMContentLoaded', getStandalone, false);
  }

})();
<|endoftext|>


<commit_before>function setupPlugin(Hammer, $) {
    // provide polyfill for Date.now()
    // browser support: http://kangax.github.io/es5-compat-table/#Date.now
    if(!Date.now) {
        Date.now = function now() {
            return new Date().getTime();
        };
    }

    /**
     * the methods on/off are called by the instance, but with the jquery plugin
     * we use the jquery event methods instead.
     * @this    {Hammer.Instance}
     * @return  {jQuery}
     */
    Hammer.utils.each(['on', 'off'], function(method) {
        Hammer.utils[method] = function(element, type, handler) {
            $(element)[method](type, function($ev) {
                // append the jquery fixed properties/methods
                var data = $.extend({}, $ev.originalEvent, $ev);
                if(data.button === undefined) {
                    data.button = $ev.which - 1;
                }
                // fix the wrong value for event.button in ie8 and older.
                else if(!!window.attachEvent && !window.addEventListener) {
                    data.button = (data.button === 1 ? 0 : (data.button === 4 ? 1 : data.button));
                }

                handler.call(this, data);
            });
        };
    });

    /**
     * trigger events
     * this is called by the gestures to trigger an event like 'tap'
     * @this    {Hammer.Instance}
     * @param   {String}    gesture
     * @param   {Object}    eventData
     * @return  {jQuery}
     */
    Hammer.Instance.prototype.trigger = function(gesture, eventData) {
        var el = $(this.element);
        if(el.has(eventData.target).length) {
            el = $(eventData.target);
        }

        return el.trigger({
            type: gesture,
            gesture: eventData
        });
    };

    /**
     * jQuery plugin
     * create instance of Hammer and watch for gestures,
     * and when called again you can change the options
     * @param   {Object}    [options={}]
     * @return  {jQuery}
     */
    $.fn.hammer = function(options) {
        return this.each(function() {
            var el = $(this);
            var inst = el.data('hammer');

            // start new hammer instance
            if(!inst) {
                el.data('hammer', new Hammer(this, options || {}));
                // change the options
            } else if(inst && options) {
                Hammer.utils.extend(inst.options, options);
            }
        });
    };
}
<commit_msg>forgot a semicolon.<commit_after>function setupPlugin(Hammer, $) {
    // provide polyfill for Date.now()
    // browser support: http://kangax.github.io/es5-compat-table/#Date.now
    if(!Date.now) {
        Date.now = function now() {
            return new Date().getTime();
        };
    }

    /**
     * the methods on/off are called by the instance, but with the jquery plugin
     * we use the jquery event methods instead.
     * @this    {Hammer.Instance}
     * @return  {jQuery}
     */
    Hammer.utils.each(['on', 'off'], function(method) {
        Hammer.utils[method] = function(element, type, handler) {
            $(element)[method](type, function($ev) {
                // append the jquery fixed properties/methods
                var data = $.extend({}, $ev.originalEvent, $ev);
                if(data.button === undefined) {
                    data.button = $ev.which - 1;
                }
                // fixes the wrong value that ie8 and older return for event.button
                else if(!!window.attachEvent && !window.addEventListener) {
                    data.button = (data.button === 1 ? 0 : (data.button === 4 ? 1 : data.button));
                }

                handler.call(this, data);
            });
        };
    });

    /**
     * trigger events
     * this is called by the gestures to trigger an event like 'tap'
     * @this    {Hammer.Instance}
     * @param   {String}    gesture
     * @param   {Object}    eventData
     * @return  {jQuery}
     */
    Hammer.Instance.prototype.trigger = function(gesture, eventData) {
        var el = $(this.element);
        if(el.has(eventData.target).length) {
            el = $(eventData.target);
        }

        return el.trigger({
            type: gesture,
            gesture: eventData
        });
    };

    /**
     * jQuery plugin
     * create instance of Hammer and watch for gestures,
     * and when called again you can change the options
     * @param   {Object}    [options={}]
     * @return  {jQuery}
     */
    $.fn.hammer = function(options) {
        return this.each(function() {
            var el = $(this);
            var inst = el.data('hammer');

            // start new hammer instance
            if(!inst) {
                el.data('hammer', new Hammer(this, options || {}));
                // change the options
            } else if(inst && options) {
                Hammer.utils.extend(inst.options, options);
            }
        });
    };
}
<|endoftext|>


<commit_before>define(['react', 'q', './mixins', './table-heading', './table-body', './pager'],
    function (React, Q, mixins, TableHeading, TableBody, Pager) {

  'use strict';

  var ContentTable = React.createClass({

    displayName: 'ContentTable',

    getInitialState: function () {
      return {
        offset: 0,
        size: 25,
        items: [],
        allItems: [],
        query: {select: []}
      };
    },

    mixins: [mixins.BuildsQuery, mixins.SetStateProperty, mixins.ComputableState],

    computeState: function (props) {

      var that = this
        , state = this.state
        , type = props.list.type;

      var modelP = props.service.fetchModel();
      var summaryFieldsP = props.service.fetchSummaryFields();

      state.offset = 0;
      state.items = state.allItems.filter(this.rowMatchesFilter(props.filterTerm));
      this.setState(state);

      Q.spread([modelP, summaryFieldsP], this.buildQuery.bind(this, props));

    },

    render: function () {
      return React.DOM.div(
          null,
          Pager({
            offset: this.state.offset,
            size: this.state.size,
            length: this.state.items.length,
            back: this._goBack,
            next: this._goNext
          }),
          React.DOM.table(
            {className: 'table table-striped'},
            TableHeading({
              service: this.props.service,
              view: this.state.query.select,
            }),
            TableBody({
              offset: this.state.offset,
              size: this.state.size,
              rows:       this.state.items,
              filterTerm: this.props.filterTerm,
              allSelected: this.state.allSelected,
              onCount:    this.setStateProperty.bind(this, 'total')
            })));
    },

    _goBack: function () {
      this.setStateProperty('offset', Math.max(0, this.state.offset - this.state.size));
    },

    _goNext: function () {
      var next = this.state.offset + this.state.size;
      var total = this.state.items.length;
      if (next < total) {
        this.setStateProperty('offset', next);
      }
    }

  });

  return ContentTable;
});

<commit_msg>Provide the view to the table body.<commit_after>define(['react', 'q', './mixins', './table-heading', './table-body', './pager'],
    function (React, Q, mixins, TableHeading, TableBody, Pager) {

  'use strict';

  var ContentTable = React.createClass({

    displayName: 'ContentTable',

    getInitialState: function () {
      return {
        offset: 0,
        size: 25,
        items: [],
        allItems: [],
        query: {select: []}
      };
    },

    mixins: [mixins.BuildsQuery, mixins.SetStateProperty, mixins.ComputableState],

    computeState: function (props) {

      var that = this
        , state = this.state
        , type = props.list.type;

      var modelP = props.service.fetchModel();
      var summaryFieldsP = props.service.fetchSummaryFields();

      state.offset = 0;
      state.items = state.allItems.filter(this.rowMatchesFilter(props.filterTerm));
      this.setState(state);

      Q.spread([modelP, summaryFieldsP], this.buildQuery.bind(this, props));

    },

    render: function () {
      return React.DOM.div(
          null,
          Pager({
            offset: this.state.offset,
            size: this.state.size,
            length: this.state.items.length,
            back: this._goBack,
            next: this._goNext
          }),
          React.DOM.table(
            {className: 'table table-striped'},
            TableHeading({
              service: this.props.service,
              view: this.state.query.select,
            }),
            TableBody({
              offset: this.state.offset,
              size: this.state.size,
              rows:       this.state.items,
              view: this.state.query.select,
              filterTerm: this.props.filterTerm,
              allSelected: this.state.allSelected,
              onCount:    this.setStateProperty.bind(this, 'total')
            })));
    },

    _goBack: function () {
      this.setStateProperty('offset', Math.max(0, this.state.offset - this.state.size));
    },

    _goNext: function () {
      var next = this.state.offset + this.state.size;
      var total = this.state.items.length;
      if (next < total) {
        this.setStateProperty('offset', next);
      }
    }

  });

  return ContentTable;
});

<|endoftext|>