<commit_before>/* @flow */

import React, {Component} from 'react'
import commonStyles from '../styles/common'
import {globalStyles, globalColors} from '../styles/style-guide'
import {Icon, Text} from '../common-adapters/index'
import {CircularProgress} from 'material-ui'
import {normal as proofNormal, checking as proofChecking, revoked as proofRevoked, error as proofError, warning as proofWarning} from '../constants/tracker'
import {metaNew, metaUpgraded, metaUnreachable, metaPending, metaDeleted, metaNone} from '../constants/tracker'
import electron from 'electron'

import type {Props as IconProps} from '../common-adapters/icon'

const shell = electron.shell || electron.remote.shell

import type {Proof, ProofsProps} from './proofs.render'

export class ProofsRender extends Component {
  props: ProofsProps;

  openLink (url: string): void {
    shell.openExternal(url)
  }

  onClickProof (proof: Proof): void {
    if (proof.state !== proofChecking) {
      proof.humanUrl && this.openLink(proof.humanUrl)
    }
  }

  onClickProfile (proof: Proof): void {
    console.log('Opening profile link:', proof)
    if (proof.state !== proofChecking) {
      proof.profileUrl && this.openLink(proof.profileUrl)
    }
  }

  onClickUsername () {
    shell.openExternal(`https://keybase.io/${this.props.username}`)
  }

  iconNameForProof (proof: Proof): IconProps.type {
    return {
      'twitter': 'fa-twitter',
      'github': 'fa-github',
      'reddit': 'fa-reddit',
      'pgp': 'fa-key',
      'coinbase': 'fa-btc',
      'hackernews': 'fa-hacker-news',
      'rooter': 'fa-shopping-basket',
      'web': 'fa-globe'
    }[proof.type]
  }

  metaColor (proof: Proof): string {
    let color = globalColors.blue
    switch (proof.meta) {
      case metaNew: color = globalColors.blue; break
      case metaUpgraded: color = globalColors.blue; break
      case metaUnreachable: color = globalColors.red; break
      case metaPending: color = globalColors.black40; break
      case metaDeleted: color = globalColors.red; break
    }
    return color
  }

  _isTracked (proof: Proof): boolean {
    return this.props.currentlyFollowing && (!proof.meta || proof.meta === metaNone)
  }

  proofColor (proof: Proof): string {
    let color = globalColors.blue
    switch (proof.state) {
      case proofNormal: {
        color = this._isTracked(proof) ? globalColors.green2 : globalColors.blue
        break
      }
      case proofChecking:
        color = globalColors.black20
        break
      case proofRevoked:
      case proofWarning:
      case proofError:
        color = globalColors.red
        break
    }

    if (proof.state === proofChecking) color = globalColors.black20

    return color
  }

  proofStatusIcon (proof: Proof): ?IconProps.type {
    switch (proof.state) {
      case proofNormal:
        return this._isTracked(proof) ? 'fa-custom-icon-proof-good-followed' : 'fa-custom-icon-proof-good-new'

      case proofWarning:
      case proofError:
      case proofRevoked:
        return 'fa-custom-icon-proof-broken'
      default:
        return null
    }
  }

  renderProofRow (styles: Object, proof: Proof) {
    const metaColor = this.metaColor(proof)
    const proofNameColor = this.proofColor(proof)
    const proofStatusIcon = this.proofStatusIcon(proof)
    const onClickProfile = () => { this.onClickProfile(proof) }
    // TODO: State is deprecated, will refactor after nuking v1
    let isChecking = (proof.state === proofChecking)

    const targetSpinnerSize = 20 // This is how big we actually want the spinner in px
    const spinnerSize = ((targetSpinnerSize / 50) / 1.4) // material ui doesn't make this easy for you. We calculate the scale, then divide by 1.4 because they multiply by that

    return (

        <Icon style={styles.service} type={this.iconNameForProof(proof)} title={proof.type} onClick={onClickProfile} />

                <Text inline style={{...globalStyles.selectable, color: proofNameColor}} type='Body'>{proof.name}</Text>

              <wbr/>
              <Text inline type='Body' style={styles.proofType}>@{proof.type}</Text>

          {proof.meta && <Text type='Header' style={{...styles.meta, backgroundColor: metaColor}}>{proof.meta}</Text>}

        {isChecking &&
          <CircularProgress style={styles.loader} mode='indeterminate' color={globalColors.black20} size={spinnerSize} />
        }
        {!isChecking && proofStatusIcon &&
          <Icon type={proofStatusIcon} style={styles.statusIcon} onClick={() => this.onClickProof(proof)} />
        }

    )
  }

  render () {
    return (

        {this.props.proofs.map(p => this.renderProofRow(styles, p))}

    )
  }
}

const styles = {
  container: {
    ...globalStyles.flexBoxColumn,
    backgroundColor: globalColors.white
  },
  row: {
    ...globalStyles.flexBoxRow,
    paddingTop: 8,
    paddingLeft: 30,
    paddingRight: 30,
    alignItems: 'flex-start',
    justifyContent: 'flex-start'
  },
  service: {
    ...globalStyles.clickable,
    height: 14,
    width: 14,
    color: globalColors.black75,
    hoverColor: globalColors.black75,
    marginRight: 9,
    marginTop: 4
  },
  statusIcon: {
    ...globalStyles.clickable,
    fontSize: 20,
    marginLeft: 10
  },
  proofNameSection: {
    ...globalStyles.flexBoxRow,
    alignItems: 'flex-start',
    flex: 1
  },
  proofNameLabelContainer: {
    ...globalStyles.flexBoxColumn,
    flex: 1
  },
  proofNameContainer: {
    wordWrap: 'break-word',
    flex: 1
  },
  proofName: {
    ...commonStyles.clickable,
    flex: 1
  },
  proofType: {
    color: globalColors.black10
  },
  meta: {
    color: globalColors.white,
    borderRadius: 1,
    fontSize: 10,
    height: 13,
    lineHeight: '13px',
    paddingLeft: 4,
    paddingRight: 4,
    alignSelf: 'flex-start',
    textTransform: 'uppercase'
  },
  serviceStatus: {
    ...globalStyles.clickable,
    marginTop: 1
  },
  loader: {
    // Using negative margins cause we can't override height for CircularProgress
    marginTop: -14,
    marginBottom: -16,
    marginLeft: -16,
    marginRight: -16
  }
}
<commit_msg>Less padding on proof tag<commit_after>/* @flow */

import React, {Component} from 'react'
import commonStyles from '../styles/common'
import {globalStyles, globalColors} from '../styles/style-guide'
import {Icon, Text} from '../common-adapters/index'
import {CircularProgress} from 'material-ui'
import {normal as proofNormal, checking as proofChecking, revoked as proofRevoked, error as proofError, warning as proofWarning} from '../constants/tracker'
import {metaNew, metaUpgraded, metaUnreachable, metaPending, metaDeleted, metaNone} from '../constants/tracker'
import electron from 'electron'

import type {Props as IconProps} from '../common-adapters/icon'

const shell = electron.shell || electron.remote.shell

import type {Proof, ProofsProps} from './proofs.render'

export class ProofsRender extends Component {
  props: ProofsProps;

  openLink (url: string): void {
    shell.openExternal(url)
  }

  onClickProof (proof: Proof): void {
    if (proof.state !== proofChecking) {
      proof.humanUrl && this.openLink(proof.humanUrl)
    }
  }

  onClickProfile (proof: Proof): void {
    console.log('Opening profile link:', proof)
    if (proof.state !== proofChecking) {
      proof.profileUrl && this.openLink(proof.profileUrl)
    }
  }

  onClickUsername () {
    shell.openExternal(`https://keybase.io/${this.props.username}`)
  }

  iconNameForProof (proof: Proof): IconProps.type {
    return {
      'twitter': 'fa-twitter',
      'github': 'fa-github',
      'reddit': 'fa-reddit',
      'pgp': 'fa-key',
      'coinbase': 'fa-btc',
      'hackernews': 'fa-hacker-news',
      'rooter': 'fa-shopping-basket',
      'web': 'fa-globe'
    }[proof.type]
  }

  metaColor (proof: Proof): string {
    let color = globalColors.blue
    switch (proof.meta) {
      case metaNew: color = globalColors.blue; break
      case metaUpgraded: color = globalColors.blue; break
      case metaUnreachable: color = globalColors.red; break
      case metaPending: color = globalColors.black40; break
      case metaDeleted: color = globalColors.red; break
    }
    return color
  }

  _isTracked (proof: Proof): boolean {
    return this.props.currentlyFollowing && (!proof.meta || proof.meta === metaNone)
  }

  proofColor (proof: Proof): string {
    let color = globalColors.blue
    switch (proof.state) {
      case proofNormal: {
        color = this._isTracked(proof) ? globalColors.green2 : globalColors.blue
        break
      }
      case proofChecking:
        color = globalColors.black20
        break
      case proofRevoked:
      case proofWarning:
      case proofError:
        color = globalColors.red
        break
    }

    if (proof.state === proofChecking) color = globalColors.black20

    return color
  }

  proofStatusIcon (proof: Proof): ?IconProps.type {
    switch (proof.state) {
      case proofNormal:
        return this._isTracked(proof) ? 'fa-custom-icon-proof-good-followed' : 'fa-custom-icon-proof-good-new'

      case proofWarning:
      case proofError:
      case proofRevoked:
        return 'fa-custom-icon-proof-broken'
      default:
        return null
    }
  }

  renderProofRow (styles: Object, proof: Proof) {
    const metaColor = this.metaColor(proof)
    const proofNameColor = this.proofColor(proof)
    const proofStatusIcon = this.proofStatusIcon(proof)
    const onClickProfile = () => { this.onClickProfile(proof) }
    // TODO: State is deprecated, will refactor after nuking v1
    let isChecking = (proof.state === proofChecking)

    const targetSpinnerSize = 20 // This is how big we actually want the spinner in px
    const spinnerSize = ((targetSpinnerSize / 50) / 1.4) // material ui doesn't make this easy for you. We calculate the scale, then divide by 1.4 because they multiply by that

    return (

        <Icon style={styles.service} type={this.iconNameForProof(proof)} title={proof.type} onClick={onClickProfile} />

                <Text inline style={{...globalStyles.selectable, color: proofNameColor}} type='Body'>{proof.name}</Text>

              <wbr/>
              <Text inline type='Body' style={styles.proofType}>@{proof.type}</Text>

          {proof.meta && <Text type='Header' style={{...styles.meta, backgroundColor: metaColor}}>{proof.meta}</Text>}

        {isChecking &&
          <CircularProgress style={styles.loader} mode='indeterminate' color={globalColors.black20} size={spinnerSize} />
        }
        {!isChecking && proofStatusIcon &&
          <Icon type={proofStatusIcon} style={styles.statusIcon} onClick={() => this.onClickProof(proof)} />
        }

    )
  }

  render () {
    return (

        {this.props.proofs.map(p => this.renderProofRow(styles, p))}

    )
  }
}

const styles = {
  container: {
    ...globalStyles.flexBoxColumn,
    backgroundColor: globalColors.white
  },
  row: {
    ...globalStyles.flexBoxRow,
    paddingTop: 8,
    paddingLeft: 30,
    paddingRight: 30,
    alignItems: 'flex-start',
    justifyContent: 'flex-start'
  },
  service: {
    ...globalStyles.clickable,
    height: 14,
    width: 14,
    color: globalColors.black75,
    hoverColor: globalColors.black75,
    marginRight: 9,
    marginTop: 4
  },
  statusIcon: {
    ...globalStyles.clickable,
    fontSize: 20,
    marginLeft: 10
  },
  proofNameSection: {
    ...globalStyles.flexBoxRow,
    alignItems: 'flex-start',
    flex: 1
  },
  proofNameLabelContainer: {
    ...globalStyles.flexBoxColumn,
    flex: 1
  },
  proofNameContainer: {
    wordWrap: 'break-word',
    flex: 1
  },
  proofName: {
    ...commonStyles.clickable,
    flex: 1
  },
  proofType: {
    color: globalColors.black10
  },
  meta: {
    color: globalColors.white,
    borderRadius: 1,
    fontSize: 10,
    height: 11,
    lineHeight: '11px',
    paddingLeft: 2,
    paddingRight: 2,
    alignSelf: 'flex-start',
    textTransform: 'uppercase'
  },
  serviceStatus: {
    ...globalStyles.clickable,
    marginTop: 1
  },
  loader: {
    // Using negative margins cause we can't override height for CircularProgress
    marginTop: -14,
    marginBottom: -16,
    marginLeft: -16,
    marginRight: -16
  }
}
<|endoftext|>


<commit_before>/**
@module @ember/object
*/

import { symbol, OWNER } from 'ember-utils';
import { on, descriptor, peekMeta } from 'ember-metal';
import CoreObject from './core_object';
import Observable from '../mixins/observable';
import { assert } from 'ember-debug';
import { DEBUG } from 'ember-env-flags';

let OVERRIDE_CONTAINER_KEY = symbol('OVERRIDE_CONTAINER_KEY');
let OVERRIDE_OWNER = symbol('OVERRIDE_OWNER');

/**
  `Ember.Object` is the main base class for all Ember objects. It is a subclass
  of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
  see the documentation for each of these.

  @class EmberObject
  @extends CoreObject
  @uses Observable
  @public
*/
const EmberObject = CoreObject.extend(Observable, {
  _debugContainerKey: descriptor({
    enumerable: false,
    get() {
      if (this[OVERRIDE_CONTAINER_KEY]) {
        return this[OVERRIDE_CONTAINER_KEY];
      }

      let meta = peekMeta(this);
      let { factory } = meta;

      return factory && factory.fullName;
    }
  }),

  [OWNER]: descriptor({
    enumerable: false,
    get() {
      if (this[OVERRIDE_OWNER]) {
        return this[OVERRIDE_OWNER];
      }

      let meta = peekMeta(this);
      let { factory } = meta;

      return factory && factory.owner;
    },

    // we need a setter here largely to support
    // folks calling `owner.ownerInjection()` API
    set(value) {
      this[OVERRIDE_OWNER] = value;
    }
  })
});

EmberObject.toString = () => 'Ember.Object';

export let FrameworkObject = EmberObject;

if (DEBUG) {
  let INIT_WAS_CALLED = symbol('INIT_WAS_CALLED');
  let ASSERT_INIT_WAS_CALLED = symbol('ASSERT_INIT_WAS_CALLED');

  FrameworkObject = EmberObject.extend({
    init() {
      this._super(...arguments);
      this[INIT_WAS_CALLED] = true;
    },

    [ASSERT_INIT_WAS_CALLED]: on('init', function() {
      assert(
        `You must call \`this._super(...arguments);\` when overriding \`init\` on a framework object. Please update ${this} to call \`this._super(...arguments);\` from \`init\`.`,
        this[INIT_WAS_CALLED]
      );
    })
  });
}

export default EmberObject;
<commit_msg>remove unneeded `OVERRIDE_CONTAINER_KEY`<commit_after>/**
@module @ember/object
*/

import { symbol, OWNER } from 'ember-utils';
import { on, descriptor, peekMeta } from 'ember-metal';
import CoreObject from './core_object';
import Observable from '../mixins/observable';
import { assert } from 'ember-debug';
import { DEBUG } from 'ember-env-flags';

let OVERRIDE_OWNER = symbol('OVERRIDE_OWNER');

/**
  `Ember.Object` is the main base class for all Ember objects. It is a subclass
  of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
  see the documentation for each of these.

  @class EmberObject
  @extends CoreObject
  @uses Observable
  @public
*/
const EmberObject = CoreObject.extend(Observable, {
  _debugContainerKey: descriptor({
    enumerable: false,
    get() {
      let meta = peekMeta(this);
      let { factory } = meta;

      return factory && factory.fullName;
    }
  }),

  [OWNER]: descriptor({
    enumerable: false,
    get() {
      if (this[OVERRIDE_OWNER]) {
        return this[OVERRIDE_OWNER];
      }

      let meta = peekMeta(this);
      let { factory } = meta;

      return factory && factory.owner;
    },

    // we need a setter here largely to support
    // folks calling `owner.ownerInjection()` API
    set(value) {
      this[OVERRIDE_OWNER] = value;
    }
  })
});

EmberObject.toString = () => 'Ember.Object';

export let FrameworkObject = EmberObject;

if (DEBUG) {
  let INIT_WAS_CALLED = symbol('INIT_WAS_CALLED');
  let ASSERT_INIT_WAS_CALLED = symbol('ASSERT_INIT_WAS_CALLED');

  FrameworkObject = EmberObject.extend({
    init() {
      this._super(...arguments);
      this[INIT_WAS_CALLED] = true;
    },

    [ASSERT_INIT_WAS_CALLED]: on('init', function() {
      assert(
        `You must call \`this._super(...arguments);\` when overriding \`init\` on a framework object. Please update ${this} to call \`this._super(...arguments);\` from \`init\`.`,
        this[INIT_WAS_CALLED]
      );
    })
  });
}

export default EmberObject;
<|endoftext|>


<commit_before>var xmldom = require('xmldom');
var _ = require('lodash');

module.exports = function (grunt) {
    'use strict';

    var PLACEHOLDER_WITH_DEFAULT_G = /\${([0-9]+):(\w*)}|\$([1-9]+)/g,
        PLACEHOLDER_END_G          = /\$0/g;

    grunt.registerTask('postProcessSnippets', 'Text based post processing of transformed sublime-snippets', function() {
        var done = this.async(),
            files = grunt.file.expand(grunt.config('postProcessSnippets.options.files')),
            domParser       = new xmldom.DOMParser(),
            xmlSerializer   = new xmldom.XMLSerializer();

        var findIndex = function(customVars, index) {
         return _.find(customVars, function (customVar) {
            return customVar.index === index;
          });
        };

        grunt.log.writeln('running postProcess', files);

        files.forEach(function(file) {
            var template = grunt.file.read(file),
                doc,
                customVars = [],
                tmplStr,
                tmplEl;

            grunt.verbose.write('processing ', template);

            doc = domParser.parseFromString(template, 'text/xml');
            tmplEl = [].slice.call(doc.getElementsByTagName('template'));

            if (tmplEl) {
                tmplEl.forEach(function(tmpl) {
                    tmplStr = tmpl.getAttribute('value');
                  grunt.verbose.write('template prior modification', tmplStr);
                    tmplStr = tmplStr.replace(PLACEHOLDER_WITH_DEFAULT_G, function(match, p1, p2, p3) {
                      if (!findIndex(customVars, p1 | p3)) {
                        customVars.push({
                          index: p1 | p3,
                          expression: "\"" + p2 + "\"",
                          name: 'MYVAR' + (p1 | p3)
                        });
                      }
                      return '$MYVAR' + (p1 | p3) +'$';
                    });
                  grunt.verbose.write('template after modification' ,tmplStr);

                    tmplStr = tmplStr.replace(PLACEHOLDER_END_G, '$END$');
                    tmpl.setAttribute('value', tmplStr);

                    customVars.sort(function(a, b) {
                        return a.index - b.index;
                    });

                    customVars.forEach(function(customVar){
                        var variableEl = doc.createElement('variable');
                        variableEl.setAttribute('name', customVar.name);
                        variableEl.setAttribute('expression', customVar.expression || '');
                        variableEl.setAttribute('defaultValue', '');
                        variableEl.setAttribute('alwaysStopAt', 'true');
                        tmpl.appendChild(variableEl);
                    });
                });
            }

            doc = xmlSerializer.serializeToString(doc);

            //Replace actual newline and tabs for their utf8 code in template value.
            doc = doc.replace(/<template name="[^\"]*" value=\"[^\"]*\"/gm, function(match){
              match = match.replace(/\n/g, "
");
              match = match.replace(/\t/g, "	");
              return match;
            });

            grunt.file.write(file, doc);

            grunt.verbose.write(xmlSerializer.serializeToString(doc));
        });

        done();
    });
};<commit_msg>Update postprocess.js<commit_after>var xmldom = require('xmldom');
var _ = require('lodash');

module.exports = function (grunt) {
    'use strict';

    var PLACEHOLDER_WITH_DEFAULT_G = /\${([0-9]+):(\w*)}|\$([1-9]+)/g,
        PLACEHOLDER_END_G          = /\$0/g;

    grunt.registerTask('postProcessSnippets', 'Text based post processing of transformed sublime-snippets', function() {
        var done = this.async(),
            files = grunt.file.expand(grunt.config('postProcessSnippets.options.files')),
            domParser       = new xmldom.DOMParser(),
            xmlSerializer   = new xmldom.XMLSerializer();

        var findIndex = function(customVars, index) {
         return _.find(customVars, function (customVar) {
            return customVar.index === index;
          });
        };

        grunt.log.writeln('running postProcess', files);

        files.forEach(function(file) {
            var template = grunt.file.read(file),
                doc,
                customVars = [],
                tmplStr,
                tmplEl;

            grunt.verbose.write('processing ', template);

            doc = domParser.parseFromString(template, 'text/xml');
            tmplEl = [].slice.call(doc.getElementsByTagName('template'));

            if (tmplEl) {
                tmplEl.forEach(function(tmpl) {
                    tmplStr = tmpl.getAttribute('value');
                  grunt.verbose.write('template prior modification', tmplStr);
                    tmplStr = tmplStr.replace(PLACEHOLDER_WITH_DEFAULT_G, function(match, p1, p2, p3) {
                      if (!findIndex(customVars, p1 || p3)) {
                        customVars.push({
                          index: p1 || p3,
                          expression: "\"" + p2 + "\"",
                          name: 'MYVAR' + (p1 || p3)
                        });
                      }
                      return '$MYVAR' + (p1 || p3) +'$';
                    });
                  grunt.verbose.write('template after modification' ,tmplStr);

                    tmplStr = tmplStr.replace(PLACEHOLDER_END_G, '$END$');
                    tmpl.setAttribute('value', tmplStr);

                    customVars.sort(function(a, b) {
                        return a.index - b.index;
                    });

                    customVars.forEach(function(customVar){
                        var variableEl = doc.createElement('variable');
                        variableEl.setAttribute('name', customVar.name);
                        variableEl.setAttribute('expression', customVar.expression || '');
                        variableEl.setAttribute('defaultValue', '');
                        variableEl.setAttribute('alwaysStopAt', 'true');
                        tmpl.appendChild(variableEl);
                    });
                });
            }

            doc = xmlSerializer.serializeToString(doc);

            //Replace actual newline and tabs for their utf8 code in template value.
            doc = doc.replace(/<template name="[^\"]*" value=\"[^\"]*\"/gm, function(match){
              match = match.replace(/\n/g, "
");
              match = match.replace(/\t/g, "	");
              return match;
            });

            grunt.file.write(file, doc);

            grunt.verbose.write(xmlSerializer.serializeToString(doc));
        });

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


<commit_before>require('normalize.css');
require('styles/App.css');

import React from 'react';
import IframeComponent from './IframeComponent';
import ControlsComponent from './ControlsComponent';
import StopWatchComponent from './StopWatchComponent';
import SettingsComponent from './SettingsComponent';

class AppComponent extends React.Component {

  render() {
    return (

        <IframeComponent url={this.props.slides.collection[this.props.slides.pointer].url}/>
        <ControlsComponent slides={this.props.slides} {...this.props.actions}/>
        <StopWatchComponent slides={this.props.slides}/>
        <SettingsComponent slides={this.props.slides}/>

    );
  }
}

AppComponent.defaultProps = {
};

export default AppComponent;
<commit_msg>Only embeds the player if a screen rotation list is set<commit_after>require('normalize.css');
require('styles/App.css');

import React from 'react';
import IframeComponent from './IframeComponent';
import ControlsComponent from './ControlsComponent';
import StopWatchComponent from './StopWatchComponent';
import SettingsComponent from './SettingsComponent';

class AppComponent extends React.Component {

  renderWithFilledCollection() {
    if (this.props.slides.collection.length > 0) {
      return (

          <IframeComponent url={this.props.slides.collection[this.props.slides.pointer].url}/>
          <ControlsComponent slides={this.props.slides} {...this.props.actions}/>
          <StopWatchComponent slides={this.props.slides}/>

      )
    }
  }

  render() {
    return (

        {this.renderWithFilledCollection()}
        <SettingsComponent slides={this.props.slides}/>

    );
  }
}

AppComponent.defaultProps = {
};

export default AppComponent;
<|endoftext|>


<commit_before>define(['knockout', 'text!./explore-cohort.html', 'd3', 'appConfig', 'lodash', 'crossfilter/crossfilter', 'd3_tip', 'knockout.dataTables.binding', 'components/faceted-datatable-cf-profile', 'css!./styles/exploreCohort.css'],
	function (ko, view, d3, config, lodash, crossfilter) {

		function exploreCohort(params) {
			window.d3 = d3;
			window._ = _;
			var self = this;
			window.exploreCohort = self;
			self.sources = ko.observableArray([]);
			self.defaultFetchMax = 100;
			self.sourceKey = ko.observable(util.getState('sourceKey'));
			self.sourceKey.subscribe(function(sourceKey) {
				util.setState('sourceKey', sourceKey);
			});
			/*
			self.cohortDefSource = ko.computed(function() {
				return {
									cohortDef: params.model.currentCohortDefinition(),
									sourceKey: self.sourceKey(),
				};
			});
			self.cohortDefSource.subscribe(function(o) {
				self.loadConceptSets(o);
			});
			self.loadConceptSets = function(o) {
				var conceptSets = ko.toJS(o.cohortDef.expression().ConceptSets());
				var cs = {};
				window.cs = cs;
				conceptSets.forEach(function(conceptSet) {
					var success = function(results) {
						console.log(results);
						cs[conceptSet.name] = results;
					};
					pageModel.resolveConceptSetExpressionSimple(
						ko.toJSON(conceptSet.expression), success)
				});
			};
			self.loadConceptSets(self.cohortDefSource());
			*/

			params.services.sources
				.filter(source => source.hasCDM)
				.map(source => _.clone(source))
				.forEach(source => {
					source.breakdown = ko.observable({});
					source.cf = ko.observable({});
					source.facets = ko.observableArray([]);
					source.filtersChanged = ko.observable();
					source.filteredRecs = ko.observable([]);
					source.someMembers = ko.observableArray([]);
					source.membersChosen = ko.observable('');
					source.fetchMax = ko.observable(self.defaultFetchMax);
					self.sources.push(source);
					$.ajax({
						url: params.services.url + source.sourceKey + '/cohortresults/' + params.model.currentCohortDefinition().id() + '/breakdown',
						method: 'GET',
						contentType: 'application/json',
						success: function (breakdown) {
							let cf = crossfilter(breakdown);
							source.breakdown(breakdown);
							source.cf(cf);
							source.facets.push(..._.map(dimConfig, function (dc, dimKey) {
								let dim = _.clone(dc);
								dim.key = dimKey;
								dim.Members = [];
								dim.cfdim = cf.dimension(dim.func);
								dim.filter = ko.observable(null);
								dim.filter.subscribe(filter => {
									dim.cfdim.filter(filter);
									source.filtersChanged(filter);
								});
								dim.group = dim.cfdim.group();
								dim.group.reduceSum(d => d.people);
								dim.groupAll = dim.cfdim.groupAll();
								dim.groupAll.reduceSum(d => d.people);
								dim.countFunc = group => group.value;
								return dim;
							}));
							source.groupAll = source.cf().groupAll();
							source.groupAll.reduceSum(d => d.people);
							source.filtersChanged.subscribe(() => {
								source.filteredRecs(source.groupAll.value());
							});
							source.filteredRecs.subscribe(function () {
								source.someMembers.removeAll();
								source.membersChosen(source.groupAll.value() + ' people in cohort matching:');
								let genders = source.facets()[0].Members.filter(d => d.Selected);
								let gender = genders.length ?
									genders.map(d => `'${d.Name}'`).join(',') : "''";
								let ages = source.facets()[1].Members.filter(d => d.Selected);
								let age = ages.length ?
									ages.map(d => `'${d.Name}'`).join(',') : "''";
								let conditionss = source.facets()[2].Members.filter(d => d.Selected);
								let conditions = conditionss.length ?
									conditionss.map(d => d.Name).join(',') : "''";
								let drugss = source.facets()[3].Members.filter(d => d.Selected);
								let drugs = drugss.length ?
									drugss.map(d => d.Name).join(',') : "''";
								let url = params.services.url + source.sourceKey + '/cohortresults/' + params.model.currentCohortDefinition().id() + '/breakdown/' + gender + '/' + age + '/' + conditions + '/' + drugs + '/' + 100; // max number of cohort people to retrieve
								$.ajax({
									url: url,
									method: 'GET',
									contentType: 'application/json',
									success: function (people) {
										people.forEach(function (person) {
											//person.url = '#/profiles/' + source.sourceKey + '/' + params.model.currentCohortDefinition().id() + '/' + person.personId;
											person.url = '#/profiles';
										});
										source.someMembers.removeAll();
										source.someMembers.push(...people);
										source.fetchMax(Math.min(self.defaultFetchMax, people.length));
									}
								});
							});
							source.filteredRecs(source.breakdown())
						}
					});
				});

			self.viewProfile = function(person) {
				//console.log(person);
				util.setState('currentCohortDefinitionId', params.model.currentCohortDefinition().id());
				util.setState('personId', person.personId);
				location.hash = '#/profiles?' + location.hash.replace(/^.*\?/,'');
				//return true;
			};
			self.selectedDesc = function (facet) {
				let selected = facet.Members.filter(d => d.Selected);
				if (selected.length) {
					if (['gender', 'age'].indexOf(facet.key) > -1) {
						if (selected.length > 1) {
							return `${facet.caption}:
										${selected.slice(0,selected.length-1).map(d=>d.Name).join(', ')}
										or ${selected[selected.length-1].Name}`;
						} else {
							return `${facet.caption}: ${selected[0].Name}`;
						}
					} else {
						if (selected.length > 1) {
							return `People with
										${selected.slice(0,selected.length-1).map(d=>d.Name).join(', ')}
										or ${selected[selected.length-1].Name} ${facet.key}`;
						} else {
							return `People with ${selected[0].Name} ${facet.key}`;
						}
					}
				} else {
					if (['gender', 'age'].indexOf(facet.key) > -1) {
						return `${facet.caption}: all`;
					} else {
						return `People with any number of ${facet.key}`;
					}
				}
			};
			var dimConfig = {
				'gender': {
					caption: 'Gender',
					func: d => d.gender,
					suffix: '',
					//filter: ko.observable(null),
					//Members: [],
				},
				'age': {
					caption: 'Age range',
					func: d => d.age,
					suffix: '',
					//filter: ko.observable(null),
					//Members: [],
				},
				'conditions': {
					caption: 'Condition density',
					func: d => d.conditions,
					suffix: ' conditions',
					//filter: ko.observable(null),
					//Members: [],
				},
				'drugs': {
					caption: 'Drug density',
					func: d => d.drugs,
					suffix: ' drugs',
					//filter: ko.observable(null),
					//Members: [],
				},
			};
			self.columns = [
				{
					title: 'Gender',
					data: 'gender'
				},
				{
					title: 'Age range',
					data: 'age'
				},
				{
					title: 'Condition density',
					data: 'conditions'
				},
				{
					title: 'Drug density',
					data: 'drugs'
				},
			];
		}

		var component = {
			viewModel: exploreCohort,
			template: view
		};
		ko.components.register('explore-cohort', component);
		return component;

		function standardDeviation(values) {
			var avg = average(values);

			var squareDiffs = values.map(function (value) {
				var diff = value - avg;
				var sqrDiff = diff * diff;
				return sqrDiff;
			});

			var avgSquareDiff = average(squareDiffs);

			var stdDev = Math.sqrt(avgSquareDiff);
			return stdDev;
		}

		function average(data) {
			var sum = data.reduce(function (sum, value) {
				return sum + value;
			}, 0);

			var avg = sum / data.length;
			return avg;
		}
	});
<commit_msg>stopped datafetching for explore unless explore tab is open<commit_after>define(['knockout', 'text!./explore-cohort.html', 'd3', 'appConfig', 'lodash', 'crossfilter/crossfilter', 'd3_tip', 'knockout.dataTables.binding', 'components/faceted-datatable-cf-profile', 'css!./styles/exploreCohort.css'],
	function (ko, view, d3, config, lodash, crossfilter) {

		function exploreCohort(params) {
			var self = this;
			self.sources = ko.observableArray([]);
			self.sourceKey = ko.observable(util.getState('sourceKey'));
			if (!params.showing())
				return;
			window._ = _;
			window.exploreCohort = self;
			self.defaultFetchMax = 100;
			self.sourceKey.subscribe(function(sourceKey) {
				util.setState('sourceKey', sourceKey);
			});
			/*
			self.cohortDefSource = ko.computed(function() {
				return {
									cohortDef: params.model.currentCohortDefinition(),
									sourceKey: self.sourceKey(),
				};
			});
			self.cohortDefSource.subscribe(function(o) {
				self.loadConceptSets(o);
			});
			self.loadConceptSets = function(o) {
				var conceptSets = ko.toJS(o.cohortDef.expression().ConceptSets());
				var cs = {};
				window.cs = cs;
				conceptSets.forEach(function(conceptSet) {
					var success = function(results) {
						console.log(results);
						cs[conceptSet.name] = results;
					};
					pageModel.resolveConceptSetExpressionSimple(
						ko.toJSON(conceptSet.expression), success)
				});
			};
			self.loadConceptSets(self.cohortDefSource());
			*/

			params.services.sources
				.filter(source => source.hasCDM)
				.map(source => _.clone(source))
				.forEach(source => {
					source.breakdown = ko.observable({});
					source.cf = ko.observable({});
					source.facets = ko.observableArray([]);
					source.filtersChanged = ko.observable();
					source.filteredRecs = ko.observable([]);
					source.someMembers = ko.observableArray([]);
					source.membersChosen = ko.observable('');
					source.fetchMax = ko.observable(self.defaultFetchMax);
					self.sources.push(source);
					$.ajax({
						url: params.services.url + source.sourceKey + '/cohortresults/' + params.model.currentCohortDefinition().id() + '/breakdown',
						method: 'GET',
						contentType: 'application/json',
						success: function (breakdown) {
							let cf = crossfilter(breakdown);
							source.breakdown(breakdown);
							source.cf(cf);
							source.facets.push(..._.map(dimConfig, function (dc, dimKey) {
								let dim = _.clone(dc);
								dim.key = dimKey;
								dim.Members = [];
								dim.cfdim = cf.dimension(dim.func);
								dim.filter = ko.observable(null);
								dim.filter.subscribe(filter => {
									dim.cfdim.filter(filter);
									source.filtersChanged(filter);
								});
								dim.group = dim.cfdim.group();
								dim.group.reduceSum(d => d.people);
								dim.groupAll = dim.cfdim.groupAll();
								dim.groupAll.reduceSum(d => d.people);
								dim.countFunc = group => group.value;
								return dim;
							}));
							source.groupAll = source.cf().groupAll();
							source.groupAll.reduceSum(d => d.people);
							source.filtersChanged.subscribe(() => {
								source.filteredRecs(source.groupAll.value());
							});
							source.filteredRecs.subscribe(function () {
								source.someMembers.removeAll();
								source.membersChosen(source.groupAll.value() + ' people in cohort matching:');
								let genders = source.facets()[0].Members.filter(d => d.Selected);
								let gender = genders.length ?
									genders.map(d => `'${d.Name}'`).join(',') : "''";
								let ages = source.facets()[1].Members.filter(d => d.Selected);
								let age = ages.length ?
									ages.map(d => `'${d.Name}'`).join(',') : "''";
								let conditionss = source.facets()[2].Members.filter(d => d.Selected);
								let conditions = conditionss.length ?
									conditionss.map(d => d.Name).join(',') : "''";
								let drugss = source.facets()[3].Members.filter(d => d.Selected);
								let drugs = drugss.length ?
									drugss.map(d => d.Name).join(',') : "''";
								let url = params.services.url + source.sourceKey + '/cohortresults/' + params.model.currentCohortDefinition().id() + '/breakdown/' + gender + '/' + age + '/' + conditions + '/' + drugs + '/' + 100; // max number of cohort people to retrieve
								$.ajax({
									url: url,
									method: 'GET',
									contentType: 'application/json',
									success: function (people) {
										people.forEach(function (person) {
											//person.url = '#/profiles/' + source.sourceKey + '/' + params.model.currentCohortDefinition().id() + '/' + person.personId;
											person.url = '#/profiles';
										});
										source.someMembers.removeAll();
										source.someMembers.push(...people);
										source.fetchMax(Math.min(self.defaultFetchMax, people.length));
									}
								});
							});
							source.filteredRecs(source.breakdown())
						}
					});
				});

			self.viewProfile = function(person) {
				//console.log(person);
				util.setState('currentCohortDefinitionId', params.model.currentCohortDefinition().id());
				util.setState('personId', person.personId);
				location.hash = '#/profiles?' + location.hash.replace(/^.*\?/,'');
				//return true;
			};
			self.selectedDesc = function (facet) {
				let selected = facet.Members.filter(d => d.Selected);
				if (selected.length) {
					if (['gender', 'age'].indexOf(facet.key) > -1) {
						if (selected.length > 1) {
							return `${facet.caption}:
										${selected.slice(0,selected.length-1).map(d=>d.Name).join(', ')}
										or ${selected[selected.length-1].Name}`;
						} else {
							return `${facet.caption}: ${selected[0].Name}`;
						}
					} else {
						if (selected.length > 1) {
							return `People with
										${selected.slice(0,selected.length-1).map(d=>d.Name).join(', ')}
										or ${selected[selected.length-1].Name} ${facet.key}`;
						} else {
							return `People with ${selected[0].Name} ${facet.key}`;
						}
					}
				} else {
					if (['gender', 'age'].indexOf(facet.key) > -1) {
						return `${facet.caption}: all`;
					} else {
						return `People with any number of ${facet.key}`;
					}
				}
			};
			var dimConfig = {
				'gender': {
					caption: 'Gender',
					func: d => d.gender,
					suffix: '',
					//filter: ko.observable(null),
					//Members: [],
				},
				'age': {
					caption: 'Age range',
					func: d => d.age,
					suffix: '',
					//filter: ko.observable(null),
					//Members: [],
				},
				'conditions': {
					caption: 'Condition density',
					func: d => d.conditions,
					suffix: ' conditions',
					//filter: ko.observable(null),
					//Members: [],
				},
				'drugs': {
					caption: 'Drug density',
					func: d => d.drugs,
					suffix: ' drugs',
					//filter: ko.observable(null),
					//Members: [],
				},
			};
			self.columns = [
				{
					title: 'Gender',
					data: 'gender'
				},
				{
					title: 'Age range',
					data: 'age'
				},
				{
					title: 'Condition density',
					data: 'conditions'
				},
				{
					title: 'Drug density',
					data: 'drugs'
				},
			];
		}

		var component = {
			viewModel: exploreCohort,
			template: view
		};
		ko.components.register('explore-cohort', component);
		return component;

		function standardDeviation(values) {
			var avg = average(values);

			var squareDiffs = values.map(function (value) {
				var diff = value - avg;
				var sqrDiff = diff * diff;
				return sqrDiff;
			});

			var avgSquareDiff = average(squareDiffs);

			var stdDev = Math.sqrt(avgSquareDiff);
			return stdDev;
		}

		function average(data) {
			var sum = data.reduce(function (sum, value) {
				return sum + value;
			}, 0);

			var avg = sum / data.length;
			return avg;
		}
	});
<|endoftext|>


<commit_before>/*global _:true, App:true, Backbone:true */
/*jshint forin:false, plusplus:false, sub:true */
'use strict';

define([
    'zepto',
    'underscore',
    'backbone',
    'app',
    'collections/episodes',
    'collections/podcasts',
    'models/episode',
    'models/podcast',
    'text!templates/player.ejs',
    'jsmad'
], function($, _, Backbone, App, Episodes, Podcasts, Episode, Podcast, PlayerTemplate, JSMad) {
    // Save audio position every five seconds.
    var SAVE_POSITION_TIMER = 5000;

    var PlayerView = Backbone.View.extend({
        className: 'player',
        el: '#player',
        $el: $('#player'),
        model: Episode,
        template: _.template(PlayerTemplate),

        events: {
            'click #play-pause': 'playPause'
        },

        initialize: function() {
            var self = this;

            _(this).bindAll('getEpisode', 'playPause', 'savePlaybackPosition',
                            'setPlaybackPosition',
                            '_savePlaybackPositionTimer',
                            '_startSoftwarePlayer');

            // It's possible to have an empty player, so we check to see if
            // there's an episode loaded; if not, don't bother with much.
            if (this.model.id) {
                this.options.canPlayType = window.GLOBALS.HAS['audioSupport{type}'.format({type: this.model.get('type').toUpperCase()})];
                this.getEpisode();

                this.model.on('destroyed', function() {
                    self.remove();
                });
            }

            // If there's a software player running, we need to stop its
            // playback and remove it from the DOM.
            if (window._player) {
                window._player.setPlaying(false);
                delete window._player;
            }

            // Variables used to store timeouts that run to save position
            // and change titles that appear.
            this.playerTimeout = null;
            this.positionTimeout = null;

            this.render();
        },

        render: function() {
            var self = this;

            // Stop titles from transitioning.
            clearTimeout(this.playerTimeout);

            // Re-rendering the play/pause buttons means redelegating events.
            this.undelegateEvents();

            var html = this.template({
                blobURL: this.options.blobURL,
                episode: this.model,
                softwareDecode: !this.options.canPlayType
            });

            // Check for audio codec support and attempt a software decoding
            // fallback if native support isn't available. This is especially
            // useful for testing on Firefox for desktop which, as of Feb 2013,
            // lacks native MP3 support.
            //
            // TODO: Try to play any file, then search for a software decoder
            // backup if playback doesn't work (more futureproof).
            if (this.options.blobURL && !this.options.canPlayType) {
                JSMad.Player.fromURL(this.options.blobURL, this._startSoftwarePlayer);
            } else {
                window._player = null;
            }

            this.$el.html(html);

            // Set the start time of this podcast (in case it's been played
            // before).
            if (this.options.blobURL) {
                // HACK: Attach model to audio player, because this.model
                // gets fucked halfway through for some reason.
                if ($('audio')[0]) {
                    $('audio')[0]._model = this.model;
                }

                this.setPlaybackPosition(this.playPause);
            }

            // Scale size of text to fit in the player element.
            function resize(element) {
                if (!element || !element.parentNode) {
                    return;
                }

                var episodeWidth = element.offsetWidth;
                var maxWidth = element.parentNode.offsetWidth;
                // TODO: 70 should be the width of the play/pause button.
                var scaleFactor = Math.min(1, (maxWidth - 70) / episodeWidth);
                element.style.transform = 'scale(' + scaleFactor + ')';
                element.style.MozTransform = 'scale(' + scaleFactor + ')';
                element.style.WebkitTransform = 'scale(' + scaleFactor + ')';
            }

            var episodeTitle = this.$el.find('h3')[0];
            var podcastTitle = this.$el.find('h2')[0];
            resize(episodeTitle);
            resize(podcastTitle);

            // TODO: Wire this up to CSS transitions and such.
            function transitionTitles() {
                self.playerTimeout = setTimeout(function() {
                    $(episodeTitle).toggleClass('hide');
                    $(podcastTitle).toggleClass('hide');
                    resize(episodeTitle);
                    resize(podcastTitle);
                    transitionTitles();
                }, 4000);
            }

            if (episodeTitle && podcastTitle) {
                transitionTitles();
            }
        },

        // Extract an Object URI for this episode and insert it into the audio
        // player's src attribute by re-rendering the template.
        getEpisode: function() {
            var self = this;

            this.model.blob(function(podcast) {
                if (podcast && podcast.file) {
                    try {
                        self.options.blobURL = window.URL.createObjectURL(podcast.file);
                    } catch (e) {
                        console.log('ERROR', e);
                    }
                } else {
                    self.downloadEpisode();
                }

                self.render();
            });
        },

        // Play or pause the current track and update the DOM to reflect the
        // player's state.
        playPause: function(event) {
            var audioPlayer = $('#audio')[0];
            if (window._player) {
                window._player.setPlaying(!window._player.playing);
            } else if (audioPlayer) {
                if (audioPlayer.paused) {
                    audioPlayer.play();
                    this._savePlaybackPositionTimer();
                } else {
                    audioPlayer.pause();
                    clearTimeout(this.positionTimeout);
                    this.savePlaybackPosition();
                }
            } else {
                // Audio elements aren't available/ready yet. Abort and try
                // again in a bit!
                setTimeout(this.playPause, 200);
                return;
            }

            $('#play-pause').toggleClass('paused');
        },

        savePlaybackPosition: function() {
            var audioPlayer = $('#audio')[0];
            if (window._player) {
                // TODO: Implement position saving for software decoder.
                return;
                // console.log(window._player);
                // console.log(window._player.absoluteFrameIndex,
                //             window._player.dev.getPlaybackTime(),
                //             window._player.stream.contentLength);
                // this.model.set({
                //     playbackPosition: window._player.absoluteFrameIndex
                // });
                // this.model.save();
            } else if (audioPlayer) {
                audioPlayer._model.set({playbackPosition: audioPlayer.currentTime});
                audioPlayer._model.save();
            }
        },

        setPlaybackPosition: function(callback) {
            var audioPlayer = $('#audio')[0];
            var playbackPosition = this.model.get('playbackPosition');
            var self = this;

            if (window._player) {
                // TOOD: Implement this in software decoding mode.
                callback();
                return;
            } else if (audioPlayer) {
                $(audioPlayer).on('canplay', function() {
                    audioPlayer.currentTime = playbackPosition;
                    $(audioPlayer).off('canplay');

                    callback();
                });
            }
        },

        _savePlaybackPositionTimer: function() {
            var self = this;

            this.positionTimeout = setTimeout(function() {
                self.savePlaybackPosition();
                self._savePlaybackPositionTimer();
            }, SAVE_POSITION_TIMER);
        },

        // Create a software player to decode audio using pure JS (used only
        // if necessary when no hardware support for a codec exists).
        _startSoftwarePlayer: function(player) {
            player.onPlay = function() {};
            player.onPause = function() {};
            player.createDevice();

            // If this model has a previously set playback position, load it.
            if (this.model.get('playbackPosition')) {
                // TODO: Implement this.
                console.log('Implement playbackPosition for software decoder');
            }

            player.setPlaying(true);
            $('#play-pause').removeClass('paused');

            window._player = player;
        }
    });

    return PlayerView;
});
<commit_msg>Cleanup player render code<commit_after>/*global _:true, App:true, Backbone:true */
/*jshint forin:false, plusplus:false, sub:true */
'use strict';

define([
    'zepto',
    'underscore',
    'backbone',
    'app',
    'collections/episodes',
    'collections/podcasts',
    'models/episode',
    'models/podcast',
    'text!templates/player.ejs',
    'jsmad'
], function($, _, Backbone, App, Episodes, Podcasts, Episode, Podcast, PlayerTemplate, JSMad) {
    // Save audio position every five seconds.
    var SAVE_POSITION_TIMER = 5000;

    var PlayerView = Backbone.View.extend({
        className: 'player',
        el: '#player',
        $el: $('#player'),
        model: Episode,
        template: _.template(PlayerTemplate),

        events: {
            'click #play-pause': 'playPause'
        },

        initialize: function() {
            var self = this;

            _(this).bindAll('getEpisode', 'playPause', 'savePlaybackPosition',
                            'setPlaybackPosition', '_adjustPlayerSizeAndText',
                            '_savePlaybackPositionTimer',
                            '_startSoftwarePlayer');

            // It's possible to have an empty player, so we check to see if
            // there's an episode loaded; if not, don't bother with much.
            if (this.model.id) {
                this.options.canPlayType = window.GLOBALS.HAS['audioSupport{type}'.format({type: this.model.get('type').toUpperCase()})];
                this.getEpisode();

                this.model.on('destroyed', function() {
                    self.remove();
                });
            }

            // If there's a software player running, we need to stop its
            // playback and remove it from the DOM.
            if (window._player) {
                window._player.setPlaying(false);
                delete window._player;
            }

            // Variables used to store timeouts that run to save position
            // and change titles that appear.
            this.playerTimeout = null;
            this.positionTimeout = null;

            this.render();
        },

        render: function() {
            var self = this;

            // Stop titles from transitioning.
            clearTimeout(this.playerTimeout);

            // Re-rendering the play/pause buttons means redelegating events.
            this.undelegateEvents();

            var html = this.template({
                blobURL: this.options.blobURL,
                episode: this.model,
                softwareDecode: !this.options.canPlayType
            });

            // Check for audio codec support and attempt a software decoding
            // fallback if native support isn't available. This is especially
            // useful for testing on Firefox for desktop which, as of Feb 2013,
            // lacks native MP3 support.
            //
            // TODO: Try to play any file, then search for a software decoder
            // backup if playback doesn't work (more futureproof).
            if (this.options.blobURL && !this.options.canPlayType) {
                JSMad.Player.fromURL(this.options.blobURL, this._startSoftwarePlayer);
            } else {
                window._player = null;
            }

            this.$el.html(html);

            // Set the start time of this podcast (in case it's been played
            // before).
            if (this.options.blobURL) {
                // HACK: Attach model to audio player, because this.model
                // gets fucked halfway through for some reason.
                if ($('audio')[0]) {
                    $('audio')[0]._model = this.model;
                }

                this.setPlaybackPosition(this.playPause);
            }

            // TODO: Better name for this? lulz
            this._adjustPlayerSizeAndText();
        },

        // Extract an Object URI for this episode and insert it into the audio
        // player's src attribute by re-rendering the template.
        getEpisode: function() {
            var self = this;

            this.model.blob(function(podcast) {
                if (podcast && podcast.file) {
                    try {
                        self.options.blobURL = window.URL.createObjectURL(podcast.file);
                    } catch (e) {
                        console.log('ERROR', e);
                    }
                } else {
                    self.downloadEpisode();
                }

                self.render();
            });
        },

        // Play or pause the current track and update the DOM to reflect the
        // player's state.
        playPause: function(event) {
            var audioPlayer = $('#audio')[0];
            if (window._player) {
                window._player.setPlaying(!window._player.playing);
            } else if (audioPlayer) {
                if (audioPlayer.paused) {
                    audioPlayer.play();
                    this._savePlaybackPositionTimer();
                } else {
                    audioPlayer.pause();
                    clearTimeout(this.positionTimeout);
                    this.savePlaybackPosition();
                }
            } else {
                // Audio elements aren't available/ready yet. Abort and try
                // again in a bit!
                setTimeout(this.playPause, 200);
                return;
            }

            $('#play-pause').toggleClass('paused');
        },

        savePlaybackPosition: function() {
            var audioPlayer = $('#audio')[0];
            if (window._player) {
                // TODO: Implement position saving for software decoder.
                return;
                // console.log(window._player);
                // console.log(window._player.absoluteFrameIndex,
                //             window._player.dev.getPlaybackTime(),
                //             window._player.stream.contentLength);
                // this.model.set({
                //     playbackPosition: window._player.absoluteFrameIndex
                // });
                // this.model.save();
            } else if (audioPlayer) {
                audioPlayer._model.set({playbackPosition: audioPlayer.currentTime});
                audioPlayer._model.save();
            }
        },

        setPlaybackPosition: function(callback) {
            var audioPlayer = $('#audio')[0];
            var playbackPosition = this.model.get('playbackPosition');
            var self = this;

            if (window._player) {
                // TOOD: Implement this in software decoding mode.
                callback();
                return;
            } else if (audioPlayer) {
                $(audioPlayer).on('canplay', function() {
                    audioPlayer.currentTime = playbackPosition;
                    $(audioPlayer).off('canplay');

                    callback();
                });
            }
        },

        _adjustPlayerSizeAndText: function() {
            var self = this;

            // Scale size of text to fit in the player element.
            // TODO: Make this a global function. It's hella useful.
            function resize(element) {
                if (!element || !element.parentNode) {
                    return;
                }

                var episodeWidth = element.offsetWidth;
                var maxWidth = element.parentNode.offsetWidth;
                // TODO: 70 should be the width of the play/pause button.
                var scaleFactor = Math.min(1, (maxWidth - 70) / episodeWidth);
                element.style.transform = 'scale(' + scaleFactor + ')';
                element.style.MozTransform = 'scale(' + scaleFactor + ')';
                element.style.WebkitTransform = 'scale(' + scaleFactor + ')';
            }

            var episodeTitle = this.$el.find('h3')[0];
            var podcastTitle = this.$el.find('h2')[0];
            resize(episodeTitle);
            resize(podcastTitle);

            // TODO: Wire this up to CSS transitions and such.
            function transitionTitles() {
                self.playerTimeout = setTimeout(function() {
                    $(episodeTitle).toggleClass('hide');
                    $(podcastTitle).toggleClass('hide');
                    resize(episodeTitle);
                    resize(podcastTitle);
                    transitionTitles();
                }, 4000);
            }

            if (episodeTitle && podcastTitle) {
                transitionTitles();
            }
        },

        _savePlaybackPositionTimer: function() {
            var self = this;

            this.positionTimeout = setTimeout(function() {
                self.savePlaybackPosition();
                self._savePlaybackPositionTimer();
            }, SAVE_POSITION_TIMER);
        },

        // Create a software player to decode audio using pure JS (used only
        // if necessary when no hardware support for a codec exists).
        _startSoftwarePlayer: function(player) {
            player.onPlay = function() {};
            player.onPause = function() {};
            player.createDevice();

            // If this model has a previously set playback position, load it.
            if (this.model.get('playbackPosition')) {
                // TODO: Implement this.
                console.log('Implement playbackPosition for software decoder');
            }

            player.setPlaying(true);
            $('#play-pause').removeClass('paused');

            window._player = player;
        }
    });

    return PlayerView;
});
<|endoftext|>


<commit_before>/*
	jQuery Extended Mag(nify)
	Author: Caleb O'Leary
	Site: http://caleboleary.com
	Current Version: https://github.com/caleboleary/jQuery-Extended-Mag
	MIT License
*/
!function(t){"use strict";t.fn.extend({extm:function(e){function i(e,i){var s,o=i.width(),n=i.height(),a=i.offset();e.zoomElement?s=e.zoomElement:(s=t("

"),"right"===e.position?(s.appendTo(t("body")),s.css("position","absolute"),s.css("top",a.top),s.css("left",a.left+o)):"overlay"===e.position?(s.appendTo(t("body")),s.css("position","absolute"),s.css("top",a.top),s.css("left",a.left)):s.insertAfter(i));var r,h;e.squareOverlay&&(r=t("

"),t("body").append(r),h=t("

"),r.append(h)),e.lazy||s.css("visibility","hidden");var l=t('');e.imageSrc?l.attr("src",e.imageSrc):l.attr("src",i.attr("src")),l.appendTo(s),function(t,e,i,s,o,n,a,r,h){i.load(function(){t.on("mouseenter",function(){a=t.offset(),"right"===h.position?(e.css("top",a.top),e.css("left",a.left+n)):"overlay"===h.position&&(e.css("top",a.top),e.css("left",a.left)),h.squareOverlay&&(s.css("top",a.top),s.css("left",a.left)),e.css("visibility","visible"),h.squareOverlay&&r.css("visibility","visible")}),t.on("mouseleave",function(){e.css("visibility","hidden"),h.squareOverlay&&r.css("visibility","hidden")});var l=i.width(),c=i.height(),p=l/n,d=c/o,v=0-(l-e.width()),f=0-(c-e.height());if(h.squareOverlay){var u=n/l*n,m=o/c*o;r.css("height",m),r.css("width",u)}t.on("mousemove",function(e){a=t.offset();var s=o/2-(e.pageY-a.top)*d;s=Math.max(s,f),s=Math.min(s,0);var l=n/2-(e.pageX-a.left)*p;if(l=Math.max(l,v),l=Math.min(l,0),i.css("top",s),i.css("left",l),h.squareOverlay){var c=e.pageY-a.top-r.height()/2;c=Math.max(c,0),c=Math.min(c,o-r.height());var u=e.pageX-a.left-r.width()/2;u=Math.max(u,0),u=Math.min(u,n-r.width()),r.css("top",c),r.css("left",u)}})})}(i,s,l,r,n,o,a,h,e),i.on("extmdestroy",function(){s.remove(),e.squareOverlay&&r.remove()}),i.on("updateImage",function(t,e){l.attr("src",e)})}var s={zoomElement:!1,imageSrc:t(this).attr("src"),squareOverlay:!1,position:!1,lazy:!1},o=t.extend({},s,e||{});o.lazy?t(this).width()>10&&t(this).height()>10?t(this).one("mouseenter",function(){i(o,t(this))}):t(this).one("load",function(){t(this).one("mouseenter",function(){i(o,t(this))})}):t(this).width()>10&&t(this).height()>10?i(o,t(this)):t(this).one("load",function(){i(o,t(this))})},extmDestroy:function(){t(this).trigger("extmdestroy")},extmImageUpdate:function(e){t(this).trigger("updateImage",e)}})}(jQuery);
<commit_msg>adding option for custom zoom level<commit_after>/*
	jQuery Extended Mag(nify)
	Author: Caleb O'Leary
	Site: http://caleboleary.com
	Current Version: https://github.com/caleboleary/jQuery-Extended-Mag
	MIT License
*/
!function(t){"use strict";t.fn.extend({extm:function(e){function i(e,i){var s,o=i.width(),n=i.height(),a=i.offset();e.zoomElement?s=e.zoomElement:(s=t("

"),"right"===e.position?(s.appendTo(t("body")),s.css("position","absolute"),s.css("top",a.top),s.css("left",a.left+o)):"overlay"===e.position?(s.appendTo(t("body")),s.css("position","absolute"),s.css("top",a.top),s.css("left",a.left)):s.insertAfter(i));var r,l;e.squareOverlay&&(r=t("

"),t("body").append(r),l=t("

"),r.append(l)),e.lazy||s.css("visibility","hidden");var h=t('');e.imageSrc?h.attr("src",e.imageSrc):h.attr("src",i.attr("src")),h.appendTo(s),function(t,e,i,s,o,n,a,r,l){i.load(function(){t.on("mouseenter",function(){a=t.offset(),"right"===l.position?(e.css("top",a.top),e.css("left",a.left+n)):"overlay"===l.position&&(e.css("top",a.top),e.css("left",a.left)),l.squareOverlay&&(s.css("top",a.top),s.css("left",a.left)),e.css("visibility","visible"),l.squareOverlay&&r.css("visibility","visible")}),t.on("mouseleave",function(){e.css("visibility","hidden"),l.squareOverlay&&r.css("visibility","hidden")});var h=i.width(),c=i.height(),p=h/n,d=c/o,v=0-(h-e.width()),f=0-(c-e.height());if(l.squareOverlay){var m=n/h*n,u=o/c*o;r.css("height",u),r.css("width",m)}t.on("mousemove",function(e){a=t.offset();var s=o/2-(e.pageY-a.top)*d;s=Math.max(s,f),s=Math.min(s,0);var h=n/2-(e.pageX-a.left)*p;if(h=Math.max(h,v),h=Math.min(h,0),i.css("top",s),i.css("left",h),l.squareOverlay){var c=e.pageY-a.top-r.height()/2;c=Math.max(c,0),c=Math.min(c,o-r.height());var m=e.pageX-a.left-r.width()/2;m=Math.max(m,0),m=Math.min(m,n-r.width()),r.css("top",c),r.css("left",m)}})})}(i,s,h,r,n,o,a,l,e),i.on("extmdestroy",function(){s.remove(),e.squareOverlay&&r.remove()}),i.on("updateImage",function(t,e){h.attr("src",e)})}var s={zoomElement:!1,imageSrc:t(this).attr("src"),squareOverlay:!1,position:!1,lazy:!1,zoomLevel:1},o=t.extend({},s,e||{});o.lazy?t(this).width()>10&&t(this).height()>10?t(this).one("mouseenter",function(){i(o,t(this))}):t(this).one("load",function(){t(this).one("mouseenter",function(){i(o,t(this))})}):t(this).width()>10&&t(this).height()>10?i(o,t(this)):t(this).one("load",function(){i(o,t(this))})},extmDestroy:function(){t(this).trigger("extmdestroy")},extmImageUpdate:function(e){t(this).trigger("updateImage",e)}})}(jQuery);
<|endoftext|>


<commit_before>define([
  'underscore',
  'backbone',
  ], function(_, Backbone) {

var Utils = {

  dateToString: function(date) {
    return date.toISOString().replace('T', ' ').split('.')[0];
  },

  randomName: function () {

    var names = ['Isaac', 'Albert', 'Neils', 'Charles', 'Louis', 'Sigmund', 'Galileo', 'Antoine Laurent', 'Johannes', 'Nicolaus', 'Michael', 'James Clerk', 'Claude', 'Franz', 'Werner', 'Linus', 'Rudolf', 'Erwin', 'Ernest', 'Paul', 'Andreas', 'Tycho', 'Ludwig', 'Max', 'Marie', 'William', 'Charles', 'Pierre Simon', 'Edwin', 'Joseph', 'Max', 'Francis', 'Enrico', 'Leonard', 'Justus', 'Arthur', 'William', 'Marcello', 'Christiaan', 'Carl', 'Albrecht', 'August', 'Robert', 'Murray', 'Emil', 'Dmitri', 'Sheldon', 'James', 'John', 'John von', 'Richard', 'Alfred', 'Stephen', 'Anton', 'Max', 'Gustav', 'Hans', 'Gregor', 'Heike', 'Thomas', 'Hermann', 'Paul', 'Ernst', 'Charles', 'Theodosius', 'Max', 'Jean Baptiste', 'William', 'Noam', 'Frederick', 'John', 'Louis Victor', 'Carl', 'Jean', 'George', 'Claude', 'Lynn', 'Karl', 'Konrad', 'Edward', 'Frederick', 'Gertrude', 'Hans', 'J. Robert', 'Edward', 'Willard', 'Ernst', 'Jonas', 'Emil', 'Trofim', 'Francis', 'Alfred', 'Alexander', 'Wilhelm'];

    return _.shuffle(names)[0];

  },

  randomInt: function(order) {
    return Math.round(Math.random() * Math.pow(10, order || 5));
  },

  // escape resource names (turning odd chars into -)
  escape : function(str) {
    str = str.trim();
    str = str.replace(/['"`]/g, '');          // remove quotes
    str = str.replace(/[^A-Za-z0-9]/g, '-');  // unallowed chars => -
    str = str.replace(/--+/g, '-');           // reduce --+ -> -
    return str.replace(/^-+|-+$/g, '');       // trim - at either end.
  },

  link_re: /(http:\/\/)([^ <>]*)/g,
  rendered: function(selector) {

    var html = $(selector).html().trim();

    // honor carriage returns.
    html = html.replace(/\n/g, '
');

    html = html.replace(/\@[A-Za-z0-9-]+/g, "$&");

    // process any links
    html = html.replace(this.link_re,
      "$&");

    $(selector).html(html);

    // process any tex
    try {
      MathJax.Hub.Queue(["Typeset", MathJax.Hub, $(selector).attr('id')]);
    } catch (err) {
      if (err.type != 'not_defined')
        console.log(err);
    }

    return $(selector);
  },

  scrollToBottom: function(selector, options) {
    options = _.extend({}, options);

    var prop = {scrollTop: $(selector).prop('scrollHeight') };
    if (options.animate) {
      $(selector).stop();
      $(selector).animate(prop);
    } else {
      $(selector).prop(prop);
    }
  },

};

return Utils;

});
<commit_msg>gruber's link regex<commit_after>define([
  'underscore',
  'backbone',
  ], function(_, Backbone) {

var Utils = {

  dateToString: function(date) {
    return date.toISOString().replace('T', ' ').split('.')[0];
  },

  randomName: function () {

    var names = ['Isaac', 'Albert', 'Neils', 'Charles', 'Louis', 'Sigmund', 'Galileo', 'Antoine Laurent', 'Johannes', 'Nicolaus', 'Michael', 'James Clerk', 'Claude', 'Franz', 'Werner', 'Linus', 'Rudolf', 'Erwin', 'Ernest', 'Paul', 'Andreas', 'Tycho', 'Ludwig', 'Max', 'Marie', 'William', 'Charles', 'Pierre Simon', 'Edwin', 'Joseph', 'Max', 'Francis', 'Enrico', 'Leonard', 'Justus', 'Arthur', 'William', 'Marcello', 'Christiaan', 'Carl', 'Albrecht', 'August', 'Robert', 'Murray', 'Emil', 'Dmitri', 'Sheldon', 'James', 'John', 'John von', 'Richard', 'Alfred', 'Stephen', 'Anton', 'Max', 'Gustav', 'Hans', 'Gregor', 'Heike', 'Thomas', 'Hermann', 'Paul', 'Ernst', 'Charles', 'Theodosius', 'Max', 'Jean Baptiste', 'William', 'Noam', 'Frederick', 'John', 'Louis Victor', 'Carl', 'Jean', 'George', 'Claude', 'Lynn', 'Karl', 'Konrad', 'Edward', 'Frederick', 'Gertrude', 'Hans', 'J. Robert', 'Edward', 'Willard', 'Ernst', 'Jonas', 'Emil', 'Trofim', 'Francis', 'Alfred', 'Alexander', 'Wilhelm'];

    return _.shuffle(names)[0];

  },

  randomInt: function(order) {
    return Math.round(Math.random() * Math.pow(10, order || 5));
  },

  // escape resource names (turning odd chars into -)
  escape : function(str) {
    str = str.trim();
    str = str.replace(/['"`]/g, '');          // remove quotes
    str = str.replace(/[^A-Za-z0-9]/g, '-');  // unallowed chars => -
    str = str.replace(/--+/g, '-');           // reduce --+ -> -
    return str.replace(/^-+|-+$/g, '');       // trim - at either end.
  },

  // link_re: /(http:\/\/)([^ <>]*)/g,
  link_re: /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i,

  rendered: function(selector) {

    var html = $(selector).html().trim();

    // honor carriage returns.
    html = html.replace(/\n/g, '
');

    html = html.replace(/\@[A-Za-z0-9-]+/g, "$&");

    // process any links
    html = html.replace(this.link_re,
      "$&");

    $(selector).html(html);

    // process any tex
    try {
      MathJax.Hub.Queue(["Typeset", MathJax.Hub, $(selector).attr('id')]);
    } catch (err) {
      if (err.type != 'not_defined')
        console.log(err);
    }

    return $(selector);
  },

  scrollToBottom: function(selector, options) {
    options = _.extend({}, options);

    var prop = {scrollTop: $(selector).prop('scrollHeight') };
    if (options.animate) {
      $(selector).stop();
      $(selector).animate(prop);
    } else {
      $(selector).prop(prop);
    }
  },

};

return Utils;

});
<|endoftext|>


<commit_before>var Game;

(function() {

/*
 * Game
 * ====
 *
 * The main Game constructor
 */

Game = function(options, plugins, categories) {
    if(!options) {
        var options = {};
        console.warn(warnings.noOptionsConstructor);
    }
    if(!plugins) {
        var plugins = [];
        console.warn(warnings.noPluginsConstructor);
    }
    if(!categories) {
        var categories = [];
        console.warn(warnings.noCategoriesConstructor);
    }

    EventEmitter(this);

    this.options    = options;

    this.Assets     = null;
    this.Camera     = null;
    this.Colliders  = null;
    this.Draw       = null;
    this.Sound      = null;
    this.World      = null;
    this.Plugins    = new Plugins(this, plugins);
    this.Categories = new Categories(this, categories);

    this.initTime   = null;
    this.loadTime   = null;
    this.startTime  = null;
    this.pauseTime  = null;
    this.stopTime   = null;

    this.playing    = false;
    this.timer      = null;
    this.frames     = 0;

    return this;
}

/* Setup Engine */
Game.prototype.init = function() {
    this.emit("beforeinit", [this]);
    // Throw an Error if a plugin is missing.
    // If Game[plugin] is null, that plugin is missing
    if(!this.Assets || !this.Camera || !this.Colliders || !this.Draw || !this.Sound || !this.World) {
        console.warn(warnings.missingPluginInit);
    }

    this.initTime = this.Utils.time();
    this.Plugins.Init(this);

    // Attach Draw.domElement to Game
    if(this.Draw && this.Draw.domElement) {
        this.domElement = this.Draw.domElement;
    }
    else {
        console.warn(warnings.noDomElement);
    }

    this.emit("init", [this]);
    return this;
}

/* Setup world */
Game.prototype.load = function() {
    if(!this.initTime) {
        this.init();
    }

    this.emit("beforeload", [this]);

    this.Plugins.Load(this);

    this.emit("load", [this]);
    return this;
}

/* Start game loop */
Game.prototype.start = function() {
    if(!this.playing) {
        this.startTime = this.Utils.time();
        if(!this.loadTime) {
            this.load();
        }

        this.emit("beforestart", [this]);

        this.timer = this.requestAnimationFrame(this.Loop);
        this.Loop();

        this.emit("start", [this]);
    }

    return this;
}

Game.prototype.pause = function() {
    if(this.playing) {
        this.emit("beforepause", [this]);
        this.pauseTime = this.Utils.time();
        this.cancelAnimationFrame(this.timer);
        this.emit("pause", [this]);
    }
}

Game.prototype.stop = function() {
    if(this.playing) {
        this.emit("beforestop", [this]);
        this.stopTime = this.Utils.time();
        this.cancelAnimationFrame(this.timer);
        this.emit("beforestopreset");
        this.reset();
        this.emit("stop", [this]);
    }

    return this;
}

Game.prototype.Loop = function() {
    this.emit("beforeframe", [this]);
    this.frames++;

    this.emit("beforeupdate", [this]);
    this.World.Update(this);
    this.Camera.Update(this);

    this.emit("beforedraw", [this]);
    this.World.Draw(this);

    this.emit("frame", [this]);
    return this;
}

Game.prototype.Utils = {
    capitalize: function(str) {
        return str.substr(0,1).toUpperCase() + str.substr(1).toLowerCase();
    },
    merge: function(first, second, blacklist) {
        if(!blacklist) var blacklist = [];

        for(key in second) {
            if(blacklist.indexOf(key) === -1) {
                first[key] = second[key];
            }
        }

        return first;
    },
    toRadians: function(deg) {
        return (deg * Math.PI) / 180;
    },
    toDegrees: function(rad) {
        return (rad * 180) / Math.PI;
    },
    time: (function() {
        if(performance && performance.now) {
            return function() { return performance.now(); }
        } else if(performance && performance.webkitNow) {
            return function() { return performance.webkitNow(); }
        } else {
            return function() { return Date.now(); }
        }
    })()
}

Game.prototype.plugin = function(plugin) {
    this.Plugins.add(plugin);

    this.emit("plugin", [this, plugin]);
    return this;
}

Game.prototype.category = function(category) {
    this.Categories.add(category);

    this.emit("category", [this, category]);
    return this;
}

/*
 * Plugins
 * =======
 *
 * The Plugins constructor
 * Provides the plugin system for Game
 */

var Plugins = function(game, plugins) {
    //Save Game for later
    this.game = game;

    this.plugins = {};

    //Add already provided plugins
    if(plugins instanceof Array) {
        for(var i = 0, len = Game.plugins.length; i < len; i++) {
            var plugin = new Game.plugins[i]();
            var index = plugins.indexOf(plugin.name);
            if(index !== -1) {
                this.add(plugin);
            }
        }
    }

    return this;
}

Plugins.prototype.add = function(plugin, override) {
    var type   = plugin.type = this.game.Utils.capitalize(plugin.type);
    var name   = plugin.name;
    var Plugin = this.get(name);

    if(!/^(add|remove|get|use|loop)$/.test(name)) {
        //This type is not yet registered
        if(!this.plugins[type]) {
            this.plugins[type] = [];
        }

        //A plugin with this name already exists, but may be overridden
        if(Plugin && override) {
            this.remove(name);
            this.add(plugin);
        }
        else if(!Plugin) {
            //Push to plugin pool
            this.plugins[type].push(plugin);
            //Use the new plugin. If one already exists, it will not be overridden.
            this.use(name);
        }
    }
    else {
        console.warn(warnings.invalidName, name);
    }

    return this;
}

Plugins.prototype.remove = function(name) {
    var Plugin = this.get(name);

    if(Plugin) {
        var type = Plugin.type;

        //Remove from plugin pool
        this.loop(function(plugin, index, array) {
            if(plugin.name === name) {
                array.splice(index, 1);
            }
        });
        //Remove from Game
        if(Plugin === this.game[type]) {
            this.game[type] = null;

            //Replace by last added plugin of the same type (if any)
            var plugins = this.plugins[type];
            if(plugins && plugins.length) {
                this.game[type] = plugins[ plugins.length-1 ];
            }
        }
    }

    return this;
}

Plugins.prototype.get = function(name, warn) {
    var Plugin = null;

    //Loop through all the plugins and get the right one out
    this.loop(function(plugin, index, array) {
        if(plugin.name === name) {
            Plugin = plugin;
            return true;
        }
    });

    //No plugin with this name exists
    if(!Plugin && warn) {
        console.warn(warnings.pluginDoesNotExist, name);
        console.trace();
    }

    return Plugin;
}

Plugins.prototype.use = function(name, override) {
    var plugin = this.get(name);

    //Plugin exists
    if(plugin) {
        var type = plugin.type;

        //Only proceed when no plugin of this type is in use, or the plugin in use may be overridden
        if(!this.game[type] || override) {
            this.game[type] = plugin;
            if(this.game.initTime && plugin.Init) {
                plugin.Init(this.game);
            }
        }
        else if(this.game[type]) {
            console.warn(warnings.useAlreadySet, name);
        }
    }
    else {
        console.warn(warnings.useNonExistentPlugin, name);
    }

    return this;
}

Plugins.prototype.loop = function(cb) {
    //Loop through types
    outer:
    for(type in this.plugins) {
        var plugins = this.plugins[type];

        //Loop through plugins (per type)
        for(var i = 0; i < plugins.length; i++) {
            var br = cb(plugins[i], i, plugins);
            // Break loop if `cb` returns `true` (or another truthy value)
            if(br) { break outer; }
        }
    }

    return this;
}

Plugins.prototype.Init = function() {
    var game = this.game;
    this.loop(function(plugin) {
        if(plugin.Init) {
            plugin.Init(game);
        }
    });

    return this;
}

Plugins.prototype.Load = function() {
    var game = this.game;
    this.loop(function(plugin) {
        if(plugin.Load) {
            plugin.Load(game);
        }
    });

    return this;
}

/*
 * Categories
 * ==========
 *
 * The main Categories constructor
 * Provides the categories system for Game
 */

var Categories = function(game, categories) {
    //Save Game for later
    this.game = game;

    //Add already provided catgories
    if(categories instanceof Array) {
        for(var i = 0, len = categories.length; i < len; i++) {
            this.add(categories[i]);
        }
    }

    return this;
}

Categories.prototype.add = function(category) {
    var name = category.name = this.game.Utils.capitalize(category.name);

    if(!/^(add|remove|get|loop)$/.test(name)) {
        this[name] = category;
        if(this.game.initTime && category.Init) {
            category.Init(this.game);
        }
    }
    else {
        console.warn(warnings.invalidName, name);
    }
}

Categories.prototype.remove = function(name) {
    var name = this.game.Utils.capitalize(name);

    this[name] = undefined;

    return this;
}

Categories.prototype.get = function(name) {
    var name = this.game.Utils.capitalize(name);

    return this[name] || null;
}

Categories.prototype.loop = function(cb) {
    for(name in this) {
        if(!/^(add|remove|get|loop)$/.test(name)) {
            cb(this[name], name);
        }
    }
}

/*
 * EventEmitter
 *
 * EventEmitter allows for easy event registering and emitting. The API is a copy of the Node.js EventEmitter class.
 */

var EventEmitter = function(obj) {
    if(!obj) obj = this;

    obj.__events = {};
    obj.__allEvents = [];

    obj.addListener = obj.on = function(event, listener) {
        if(!this.__events[event]) this.__events[event] = [];

        if(listener) {
            this.__events[event].push(listener);
        }
        else {
            this.__allEvents.push(event); // event is now the callback
        }

        return this;
    }

    obj.once = function(event, listener) {
        if(!this.__events[event]) this.__events[event] = [];

        if(listener) {
            var func = function() {
                listener.call(null, arguments);
                obj.removeListener(event, func);
            }
            this.__events[event].push(func);
        }
        else {
            var func = function() {
                event.call(null, arguments);
                obj.removeListener(func);
            }
            this.__allEvents.push(func);
        }

        return this;
    }

    obj.emit = function(event, args) {
        if(!args) args = [];
        if(this.__events[event]) {
            var listeners = this.__events[event];
            for(var i = 0, len = listeners.length; i < len; i++) {
                listeners[i].apply(null, args);
            }
        }
        var args = [event].concat(args);
        for(var i = 0, len = this.__allEvents.length; i < len; i++) {
            this.__allEvents[i].apply(null, args);
        }

        return this;
    }

    obj.removeListener = function(event, listener) {
        if(!listener) {
            var listener = event;
            for(event in this.__events) {
                this.removeListener(event, listener);
            }
        }
        else {
            var listeners = this.__events[event];
            if(listeners.indexOf(listener) !== -1) {
                listeners.splice(listeners.indexOf(listener), 1);
            }
            if(this.__allEvents.indexOf(listener) !== -1) {
                this.__allEvents.splice(this.__allEvents.indexOf(listener), 1);
            }
        }

        return this;
    }

    obj.removeAllListeners = function(event) {
        this.__events[event] = [];

        return this;
    }

    obj.listeners = function(event) {
        return this.__events[event] || [];
    }

    return obj;
}

Game.prototype.EventEmitter = EventEmitter;

Game.plugins = [];

/*
 * VARS
 * ====
 *
 * Some variables used  by Game.
 */

    var warnings = {
        "noOptionsConstructor": "WARN: No options passed to the Game constructor. Default options will be used.",
        "noPluginsConstructor": "WARN: No plugins passed to the Game constructor. Make sure to add plugins with 'Game.plugin(plugin);'.",
        "noCategoriesConstructor": "WARN: No categories passed to the Game constructor. Make sure to add categories with 'Game.category(category);'.",
        "pluginDoesNotExist": "WARN: A plugin with the name '%s' does not exist.",
        "useNonExistentPlugin": "WARN: You are trying to use() a non-existent plugin, named '%s'.",
        "useAlreadySet": "WARN: You are trying to use() a plugin, named '%s', but a plugin of this type is already in use. Please use Plugins.use(name, true) to override any plugins already in use.",
        "invalidName": "WARN: The name '%s' is invalid.",
        "noPluginPassed": "WARN: No plugin passed to Plugin.add().",
        "noCategoryPassed": "WARN: No category passed to Categories.add().",
        "missingPluginInit": "A vital plugin is missing after Game.init().",
        "noDomElement": "Game.Draw.domElement is empty."
    }

    var container = Game.prototype.container = document.createElement("div");
    container.setAttribute("id", "2D-loaders");
    container.setAttribute("style", "width:0;height:0;visibility:hidden;overflow:hidden");
    document.addEventListener("load", function() {
        document.body.appendChild(container);
    });

})();<commit_msg>Made game.load() set loadTime<commit_after>var Game;

(function() {

/*
 * Game
 * ====
 *
 * The main Game constructor
 */

Game = function(options, plugins, categories) {
    if(!options) {
        var options = {};
        console.warn(warnings.noOptionsConstructor);
    }
    if(!plugins) {
        var plugins = [];
        console.warn(warnings.noPluginsConstructor);
    }
    if(!categories) {
        var categories = [];
        console.warn(warnings.noCategoriesConstructor);
    }

    EventEmitter(this);

    this.options    = options;

    this.Assets     = null;
    this.Camera     = null;
    this.Colliders  = null;
    this.Draw       = null;
    this.Sound      = null;
    this.World      = null;
    this.Plugins    = new Plugins(this, plugins);
    this.Categories = new Categories(this, categories);

    this.initTime   = null;
    this.loadTime   = null;
    this.startTime  = null;
    this.pauseTime  = null;
    this.stopTime   = null;

    this.playing    = false;
    this.timer      = null;
    this.frames     = 0;

    return this;
}

/* Setup Engine */
Game.prototype.init = function() {
    this.emit("beforeinit", [this]);
    // Throw an Error if a plugin is missing.
    // If Game[plugin] is null, that plugin is missing
    if(!this.Assets || !this.Camera || !this.Colliders || !this.Draw || !this.Sound || !this.World) {
        console.warn(warnings.missingPluginInit);
    }

    this.initTime = this.Utils.time();
    this.Plugins.Init(this);

    // Attach Draw.domElement to Game
    if(this.Draw && this.Draw.domElement) {
        this.domElement = this.Draw.domElement;
    }
    else {
        console.warn(warnings.noDomElement);
    }

    this.emit("init", [this]);
    return this;
}

/* Setup world */
Game.prototype.load = function() {
    if(!this.initTime) {
        this.init();
    }

    this.emit("beforeload", [this]);

    this.loadTime = this.Utils.time();
    this.Plugins.Load(this);

    this.emit("load", [this]);
    return this;
}

/* Start game loop */
Game.prototype.start = function() {
    if(!this.playing) {
        this.startTime = this.Utils.time();
        if(!this.loadTime) {
            this.load();
        }

        this.emit("beforestart", [this]);

        this.timer = this.requestAnimationFrame(this.Loop);
        this.Loop();

        this.emit("start", [this]);
    }

    return this;
}

Game.prototype.pause = function() {
    if(this.playing) {
        this.emit("beforepause", [this]);
        this.pauseTime = this.Utils.time();
        this.cancelAnimationFrame(this.timer);
        this.emit("pause", [this]);
    }
}

Game.prototype.stop = function() {
    if(this.playing) {
        this.emit("beforestop", [this]);
        this.stopTime = this.Utils.time();
        this.cancelAnimationFrame(this.timer);
        this.emit("beforestopreset");
        this.reset();
        this.emit("stop", [this]);
    }

    return this;
}

Game.prototype.Loop = function() {
    this.emit("beforeframe", [this]);
    this.frames++;

    this.emit("beforeupdate", [this]);
    this.World.Update(this);
    this.Camera.Update(this);

    this.emit("beforedraw", [this]);
    this.World.Draw(this);

    this.emit("frame", [this]);
    return this;
}

Game.prototype.Utils = {
    capitalize: function(str) {
        return str.substr(0,1).toUpperCase() + str.substr(1).toLowerCase();
    },
    merge: function(first, second, blacklist) {
        if(!blacklist) var blacklist = [];

        for(key in second) {
            if(blacklist.indexOf(key) === -1) {
                first[key] = second[key];
            }
        }

        return first;
    },
    toRadians: function(deg) {
        return (deg * Math.PI) / 180;
    },
    toDegrees: function(rad) {
        return (rad * 180) / Math.PI;
    },
    time: (function() {
        if(performance && performance.now) {
            return function() { return performance.now(); }
        } else if(performance && performance.webkitNow) {
            return function() { return performance.webkitNow(); }
        } else {
            return function() { return Date.now(); }
        }
    })()
}

Game.prototype.plugin = function(plugin) {
    this.Plugins.add(plugin);

    this.emit("plugin", [this, plugin]);
    return this;
}

Game.prototype.category = function(category) {
    this.Categories.add(category);

    this.emit("category", [this, category]);
    return this;
}

/*
 * Plugins
 * =======
 *
 * The Plugins constructor
 * Provides the plugin system for Game
 */

var Plugins = function(game, plugins) {
    //Save Game for later
    this.game = game;

    this.plugins = {};

    //Add already provided plugins
    if(plugins instanceof Array) {
        for(var i = 0, len = Game.plugins.length; i < len; i++) {
            var plugin = new Game.plugins[i]();
            var index = plugins.indexOf(plugin.name);
            if(index !== -1) {
                this.add(plugin);
            }
        }
    }

    return this;
}

Plugins.prototype.add = function(plugin, override) {
    var type   = plugin.type = this.game.Utils.capitalize(plugin.type);
    var name   = plugin.name;
    var Plugin = this.get(name);

    if(!/^(add|remove|get|use|loop)$/.test(name)) {
        //This type is not yet registered
        if(!this.plugins[type]) {
            this.plugins[type] = [];
        }

        //A plugin with this name already exists, but may be overridden
        if(Plugin && override) {
            this.remove(name);
            this.add(plugin);
        }
        else if(!Plugin) {
            //Push to plugin pool
            this.plugins[type].push(plugin);
            //Use the new plugin. If one already exists, it will not be overridden.
            this.use(name);
        }
    }
    else {
        console.warn(warnings.invalidName, name);
    }

    return this;
}

Plugins.prototype.remove = function(name) {
    var Plugin = this.get(name);

    if(Plugin) {
        var type = Plugin.type;

        //Remove from plugin pool
        this.loop(function(plugin, index, array) {
            if(plugin.name === name) {
                array.splice(index, 1);
            }
        });
        //Remove from Game
        if(Plugin === this.game[type]) {
            this.game[type] = null;

            //Replace by last added plugin of the same type (if any)
            var plugins = this.plugins[type];
            if(plugins && plugins.length) {
                this.game[type] = plugins[ plugins.length-1 ];
            }
        }
    }

    return this;
}

Plugins.prototype.get = function(name, warn) {
    var Plugin = null;

    //Loop through all the plugins and get the right one out
    this.loop(function(plugin, index, array) {
        if(plugin.name === name) {
            Plugin = plugin;
            return true;
        }
    });

    //No plugin with this name exists
    if(!Plugin && warn) {
        console.warn(warnings.pluginDoesNotExist, name);
        console.trace();
    }

    return Plugin;
}

Plugins.prototype.use = function(name, override) {
    var plugin = this.get(name);

    //Plugin exists
    if(plugin) {
        var type = plugin.type;

        //Only proceed when no plugin of this type is in use, or the plugin in use may be overridden
        if(!this.game[type] || override) {
            this.game[type] = plugin;
            if(this.game.initTime && plugin.Init) {
                plugin.Init(this.game);
            }
        }
        else if(this.game[type]) {
            console.warn(warnings.useAlreadySet, name);
        }
    }
    else {
        console.warn(warnings.useNonExistentPlugin, name);
    }

    return this;
}

Plugins.prototype.loop = function(cb) {
    //Loop through types
    outer:
    for(type in this.plugins) {
        var plugins = this.plugins[type];

        //Loop through plugins (per type)
        for(var i = 0; i < plugins.length; i++) {
            var br = cb(plugins[i], i, plugins);
            // Break loop if `cb` returns `true` (or another truthy value)
            if(br) { break outer; }
        }
    }

    return this;
}

Plugins.prototype.Init = function() {
    var game = this.game;
    this.loop(function(plugin) {
        if(plugin.Init) {
            plugin.Init(game);
        }
    });

    return this;
}

Plugins.prototype.Load = function() {
    var game = this.game;
    this.loop(function(plugin) {
        if(plugin.Load) {
            plugin.Load(game);
        }
    });

    return this;
}

/*
 * Categories
 * ==========
 *
 * The main Categories constructor
 * Provides the categories system for Game
 */

var Categories = function(game, categories) {
    //Save Game for later
    this.game = game;

    //Add already provided catgories
    if(categories instanceof Array) {
        for(var i = 0, len = categories.length; i < len; i++) {
            this.add(categories[i]);
        }
    }

    return this;
}

Categories.prototype.add = function(category) {
    var name = category.name = this.game.Utils.capitalize(category.name);

    if(!/^(add|remove|get|loop)$/.test(name)) {
        this[name] = category;
        if(this.game.initTime && category.Init) {
            category.Init(this.game);
        }
    }
    else {
        console.warn(warnings.invalidName, name);
    }
}

Categories.prototype.remove = function(name) {
    var name = this.game.Utils.capitalize(name);

    this[name] = undefined;

    return this;
}

Categories.prototype.get = function(name) {
    var name = this.game.Utils.capitalize(name);

    return this[name] || null;
}

Categories.prototype.loop = function(cb) {
    for(name in this) {
        if(!/^(add|remove|get|loop)$/.test(name)) {
            cb(this[name], name);
        }
    }
}

/*
 * EventEmitter
 *
 * EventEmitter allows for easy event registering and emitting. The API is a copy of the Node.js EventEmitter class.
 */

var EventEmitter = function(obj) {
    if(!obj) obj = this;

    obj.__events = {};
    obj.__allEvents = [];

    obj.addListener = obj.on = function(event, listener) {
        if(!this.__events[event]) this.__events[event] = [];

        if(listener) {
            this.__events[event].push(listener);
        }
        else {
            this.__allEvents.push(event); // event is now the callback
        }

        return this;
    }

    obj.once = function(event, listener) {
        if(!this.__events[event]) this.__events[event] = [];

        if(listener) {
            var func = function() {
                listener.call(null, arguments);
                obj.removeListener(event, func);
            }
            this.__events[event].push(func);
        }
        else {
            var func = function() {
                event.call(null, arguments);
                obj.removeListener(func);
            }
            this.__allEvents.push(func);
        }

        return this;
    }

    obj.emit = function(event, args) {
        if(!args) args = [];
        if(this.__events[event]) {
            var listeners = this.__events[event];
            for(var i = 0, len = listeners.length; i < len; i++) {
                listeners[i].apply(null, args);
            }
        }
        var args = [event].concat(args);
        for(var i = 0, len = this.__allEvents.length; i < len; i++) {
            this.__allEvents[i].apply(null, args);
        }

        return this;
    }

    obj.removeListener = function(event, listener) {
        if(!listener) {
            var listener = event;
            for(event in this.__events) {
                this.removeListener(event, listener);
            }
        }
        else {
            var listeners = this.__events[event];
            if(listeners.indexOf(listener) !== -1) {
                listeners.splice(listeners.indexOf(listener), 1);
            }
            if(this.__allEvents.indexOf(listener) !== -1) {
                this.__allEvents.splice(this.__allEvents.indexOf(listener), 1);
            }
        }

        return this;
    }

    obj.removeAllListeners = function(event) {
        this.__events[event] = [];

        return this;
    }

    obj.listeners = function(event) {
        return this.__events[event] || [];
    }

    return obj;
}

Game.prototype.EventEmitter = EventEmitter;

Game.plugins = [];

/*
 * VARS
 * ====
 *
 * Some variables used  by Game.
 */

    var warnings = {
        "noOptionsConstructor": "WARN: No options passed to the Game constructor. Default options will be used.",
        "noPluginsConstructor": "WARN: No plugins passed to the Game constructor. Make sure to add plugins with 'Game.plugin(plugin);'.",
        "noCategoriesConstructor": "WARN: No categories passed to the Game constructor. Make sure to add categories with 'Game.category(category);'.",
        "pluginDoesNotExist": "WARN: A plugin with the name '%s' does not exist.",
        "useNonExistentPlugin": "WARN: You are trying to use() a non-existent plugin, named '%s'.",
        "useAlreadySet": "WARN: You are trying to use() a plugin, named '%s', but a plugin of this type is already in use. Please use Plugins.use(name, true) to override any plugins already in use.",
        "invalidName": "WARN: The name '%s' is invalid.",
        "noPluginPassed": "WARN: No plugin passed to Plugin.add().",
        "noCategoryPassed": "WARN: No category passed to Categories.add().",
        "missingPluginInit": "A vital plugin is missing after Game.init().",
        "noDomElement": "Game.Draw.domElement is empty."
    }

    var container = Game.prototype.container = document.createElement("div");
    container.setAttribute("id", "2D-loaders");
    container.setAttribute("style", "width:0;height:0;visibility:hidden;overflow:hidden");
    document.addEventListener("load", function() {
        document.body.appendChild(container);
    });

})();<|endoftext|>