<commit_before>"use strict";

import { Config } from "./../config";

const MINIMUM_HEIGHT_OFF_GROUND = 0.041666666666667;

let DrivingCurveBuilder = (function() {
  let build = function(roadNetwork, path) {
    let curvePositionX;
    let curvePositionZ;
    let i;
    let isFinalPathSegment;
    let isStraightIntersectionSegmentRequired;
    let isCurvedIntersectionSegmentRequired;
    let curve;
    let currentX;
    let currentZ;
    let nextX;
    let nextZ;
    let subsequentX;
    let subsequentZ;
    let curvePath = new THREE.CurvePath();
    let lineStartVector;
    let controlPointVector;
    let lineEndVector;

    curvePositionX = path[0][0];
    curvePositionZ = path[0][1];
    for (i = 0; i < path.length - 1; i++) {
      isFinalPathSegment = (i === path.length - 2);
      currentX = path[i][0];
      currentZ = path[i][1];
      nextX = path[i + 1][0];
      nextZ = path[i + 1][1];

      if (isFinalPathSegment === true) {
        subsequentX = path[i + 1][0];
        subsequentZ = path[i + 1][1];
      }
      else {
        subsequentX = path[i + 2][0];
        subsequentZ = path[i + 2][1];
      }

      isCurvedIntersectionSegmentRequired = !isFinalPathSegment &&
                                            !((Math.sign(nextX - currentX) === 0.0 && Math.sign(subsequentX - nextX) === 0.0) ||
                                              (Math.sign(nextZ - currentZ) === 0.0 && Math.sign(subsequentZ - nextZ) === 0.0));

      isStraightIntersectionSegmentRequired = !isCurvedIntersectionSegmentRequired;

      // Main straight segment
      lineStartVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);

      if (curvePositionZ > nextZ) {
        curvePositionX = nextX;
        curvePositionZ = (isCurvedIntersectionSegmentRequired ? (nextZ + 0.5) : (nextZ + Config.HALF_STREET_DEPTH));
      }
      else if (curvePositionZ < nextZ) {
        curvePositionX = nextX;
        curvePositionZ = (isCurvedIntersectionSegmentRequired ? (nextZ - 0.5) : (nextZ - Config.HALF_STREET_DEPTH));
      }
      else if (curvePositionX < nextX) {
        curvePositionX = (isCurvedIntersectionSegmentRequired ? (nextX - 0.5) : (nextX - Config.HALF_STREET_WIDTH));
        curvePositionZ = nextZ;
      }
      else if (curvePositionX > nextX) {
        curvePositionX = (isCurvedIntersectionSegmentRequired ? (nextX + 0.5) : (nextX + Config.HALF_STREET_WIDTH));
        curvePositionZ = nextZ;
      }

      lineEndVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);

      curve = new THREE.LineCurve3(lineStartVector, lineEndVector);
      curvePath.curves.push(curve);

      // Straight segment through intersection
      if (isStraightIntersectionSegmentRequired === true) {
        lineStartVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);

        if (curvePositionZ > nextZ) {
          curvePositionX = nextX;
          curvePositionZ = (isFinalPathSegment ? nextZ : (curvePositionZ - Config.STREET_DEPTH));
        }
        else if (curvePositionZ < nextZ) {
          curvePositionX = nextX;
          curvePositionZ = (isFinalPathSegment ? nextZ : (curvePositionZ + Config.STREET_DEPTH));
        }
        else if (curvePositionX < nextX) {
          curvePositionX = (isFinalPathSegment ? nextX : (curvePositionX + Config.STREET_WIDTH));
          curvePositionZ = nextZ;
        }
        else if (curvePositionX > nextX) {
          curvePositionX = (isFinalPathSegment ? nextX : (curvePositionX - Config.STREET_WIDTH));
          curvePositionZ = nextZ;
        }

        lineEndVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);

        curve = new THREE.LineCurve3(lineStartVector, lineEndVector);
        curvePath.curves.push(curve);
      }

      // Curve to next straight segment
      if (isCurvedIntersectionSegmentRequired === true) {
        lineStartVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);
        controlPointVector = new THREE.Vector3(nextX, roadNetwork.getRoadHeight(nextX, nextZ) + MINIMUM_HEIGHT_OFF_GROUND, nextZ);

        if (currentX < subsequentX) {
          curvePositionX += 0.5;
        }
        else if (currentX > subsequentX) {
          curvePositionX -= 0.5;
        }

        if (currentZ < subsequentZ) {
          curvePositionZ += 0.5;
        }
        else if (currentZ > subsequentZ) {
          curvePositionZ -= 0.5;
        }

        lineEndVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);

        curve = new THREE.QuadraticBezierCurve3(lineStartVector, controlPointVector, lineEndVector);
        curvePath.curves.push(curve);
      }
    }

    return curvePath;
  };

  return {
    build: build,
  };
})();

export { DrivingCurveBuilder };
<commit_msg>Better variable names<commit_after>"use strict";

import { Config } from "./../config";

const MINIMUM_HEIGHT_OFF_GROUND = 0.041666666666667;

let DrivingCurveBuilder = (function() {
  let build = function(roadNetwork, path) {
    let curvePositionX;
    let curvePositionZ;
    let i;
    let isFinalPathSegment;
    let isStraightIntersectionSegmentRequired;
    let isCurvedIntersectionSegmentRequired;
    let curve;
    let startX;
    let startZ;
    let middleX;
    let middleZ;
    let endX;
    let endZ;
    let curvePath = new THREE.CurvePath();
    let lineStartVector;
    let controlPointVector;
    let lineEndVector;

    curvePositionX = path[0][0];
    curvePositionZ = path[0][1];
    for (i = 0; i < path.length - 1; i++) {
      isFinalPathSegment = (i === path.length - 2);
      startX = path[i][0];
      startZ = path[i][1];
      middleX = path[i + 1][0];
      middleZ = path[i + 1][1];

      if (isFinalPathSegment === true) {
        endX = path[i + 1][0];
        endZ = path[i + 1][1];
      }
      else {
        endX = path[i + 2][0];
        endZ = path[i + 2][1];
      }

      isCurvedIntersectionSegmentRequired = !isFinalPathSegment &&
                                            !((Math.sign(middleX - startX) === 0.0 && Math.sign(endX - middleX) === 0.0) ||
                                              (Math.sign(middleZ - startZ) === 0.0 && Math.sign(endZ - middleZ) === 0.0));

      isStraightIntersectionSegmentRequired = !isCurvedIntersectionSegmentRequired;

      // Main straight segment
      lineStartVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);

      if (curvePositionZ > middleZ) {
        curvePositionX = middleX;
        curvePositionZ = (isCurvedIntersectionSegmentRequired ? (middleZ + 0.5) : (middleZ + Config.HALF_STREET_DEPTH));
      }
      else if (curvePositionZ < middleZ) {
        curvePositionX = middleX;
        curvePositionZ = (isCurvedIntersectionSegmentRequired ? (middleZ - 0.5) : (middleZ - Config.HALF_STREET_DEPTH));
      }
      else if (curvePositionX < middleX) {
        curvePositionX = (isCurvedIntersectionSegmentRequired ? (middleX - 0.5) : (middleX - Config.HALF_STREET_WIDTH));
        curvePositionZ = middleZ;
      }
      else if (curvePositionX > middleX) {
        curvePositionX = (isCurvedIntersectionSegmentRequired ? (middleX + 0.5) : (middleX + Config.HALF_STREET_WIDTH));
        curvePositionZ = middleZ;
      }

      lineEndVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);

      curve = new THREE.LineCurve3(lineStartVector, lineEndVector);
      curvePath.curves.push(curve);

      // Straight segment through intersection
      if (isStraightIntersectionSegmentRequired === true) {
        lineStartVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);

        if (curvePositionZ > middleZ) {
          curvePositionX = middleX;
          curvePositionZ = (isFinalPathSegment ? middleZ : (curvePositionZ - Config.STREET_DEPTH));
        }
        else if (curvePositionZ < middleZ) {
          curvePositionX = middleX;
          curvePositionZ = (isFinalPathSegment ? middleZ : (curvePositionZ + Config.STREET_DEPTH));
        }
        else if (curvePositionX < middleX) {
          curvePositionX = (isFinalPathSegment ? middleX : (curvePositionX + Config.STREET_WIDTH));
          curvePositionZ = middleZ;
        }
        else if (curvePositionX > middleX) {
          curvePositionX = (isFinalPathSegment ? middleX : (curvePositionX - Config.STREET_WIDTH));
          curvePositionZ = middleZ;
        }

        lineEndVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);

        curve = new THREE.LineCurve3(lineStartVector, lineEndVector);
        curvePath.curves.push(curve);
      }

      // Curve to next straight segment
      if (isCurvedIntersectionSegmentRequired === true) {
        lineStartVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);
        controlPointVector = new THREE.Vector3(middleX, roadNetwork.getRoadHeight(middleX, middleZ) + MINIMUM_HEIGHT_OFF_GROUND, middleZ);

        if (startX < endX) {
          curvePositionX += 0.5;
        }
        else if (startX > endX) {
          curvePositionX -= 0.5;
        }

        if (startZ < endZ) {
          curvePositionZ += 0.5;
        }
        else if (startZ > endZ) {
          curvePositionZ -= 0.5;
        }

        lineEndVector = new THREE.Vector3(curvePositionX, roadNetwork.getRoadHeight(curvePositionX, curvePositionZ) + MINIMUM_HEIGHT_OFF_GROUND, curvePositionZ);

        curve = new THREE.QuadraticBezierCurve3(lineStartVector, controlPointVector, lineEndVector);
        curvePath.curves.push(curve);
      }
    }

    return curvePath;
  };

  return {
    build: build,
  };
})();

export { DrivingCurveBuilder };
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'ChoiceWithOtherView',
  extends: 'foam.u2.View',
  requires: [
    'foam.u2.layout.Rows'
  ],
  properties: [
    {
      class: 'foam.u2.ViewSpec',
      name: 'choiceView',
      required: true
    },
    {
      documentation: 'When this value is selected, the "Other" view will appear.',
      name: 'otherKey',
      required: true
    },
    {
      documentation: 'The label to display above the "Other" view.',
      name: 'otherLabel',
      value: 'Please specify'
    },
    {
      documentation: 'The default value to prefill the "Other" view with.',
      name: 'otherDefault',
      value: ''
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'otherView',
      value: { class: 'foam.u2.TextField', onKey: true }
    },

    'data',
    'choiceView_',
    'choiceData_',
    'otherData_',
    {
      class: 'Boolean',
      name: 'showOther_',
      expression: function(choiceData_, otherKey) {
        return foam.util.equals(choiceData_, otherKey);
      }
    }
  ],
  reactions: [
    ['', 'propertyChange.choiceData_', 'toData'],
    ['', 'propertyChange.otherData_', 'toData'],
    ['', 'propertyChange.data', 'fromData'],
    ['', 'propertyChange.choiceView_', 'fromData']
  ],
  listeners: [
    {
      name: 'toData',
      code: function() {
        this.data = this.showOther_ ? this.otherData_ : this.choiceData_;
      }
    },
    {
      name: 'fromData',
      code: function() {
        if ( ! this.choiceView_ ) return;
        this.choiceView_.data = this.data;
        if ( this.choiceView_.choice == null ) {
          this.choiceData_ = this.otherKey;
          this.otherData_ = this.data;
        } else {
          this.otherData_ = this.otherDefault;
        }
      }
    }
  ],
  methods: [
    function initE() {
      var self = this;
      self.SUPER();
      self.add(self.slot(function(choiceView, otherView) {
        return self.Rows.create()
          .tag(choiceView, { data$: self.choiceData_$ }, self.choiceView_$)
          .start('m3')
            .show(self.showOther_$)
            .add(self.otherLabel$)
          .end()
          .start()
            .show(self.showOther_$)
            .tag(otherView, { data$: self.otherData_$ })
          .end();
      }));
    }
  ]
});<commit_msg>prevent feedback loop<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'ChoiceWithOtherView',
  extends: 'foam.u2.View',
  requires: [
    'foam.u2.layout.Rows'
  ],
  properties: [
    {
      class: 'foam.u2.ViewSpec',
      name: 'choiceView',
      required: true
    },
    {
      documentation: 'When this value is selected, the "Other" view will appear.',
      name: 'otherKey',
      required: true
    },
    {
      documentation: 'The label to display above the "Other" view.',
      name: 'otherLabel',
      value: 'Please specify'
    },
    {
      documentation: 'The default value to prefill the "Other" view with.',
      name: 'otherDefault',
      value: ''
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'otherView',
      value: { class: 'foam.u2.TextField', onKey: true }
    },

    'data',
    'choiceView_',
    'choiceData_',
    'otherData_',
    {
      class: 'Boolean',
      name: 'showOther_',
      expression: function(choiceData_, otherKey) {
        return foam.util.equals(choiceData_, otherKey);
      }
    },
    'preventFeedback_'
  ],
  reactions: [
    ['', 'propertyChange.choiceData_', 'toData'],
    ['', 'propertyChange.otherData_', 'toData'],
    ['', 'propertyChange.data', 'fromData'],
    ['', 'propertyChange.choiceView_', 'fromData']
  ],
  listeners: [
    {
      name: 'toData',
      code: function() {
        if ( this.preventFeedback_ ) return;
        this.preventFeedback_ = true;
        this.data = this.showOther_ ? this.otherData_ : this.choiceData_;
        this.preventFeedback_ = false;
      }
    },
    {
      name: 'fromData',
      code: function() {
        if ( ! this.choiceView_ || this.preventFeedback_ ) return;
        this.preventFeedback_ = true;
        this.choiceView_.data = this.data;
        if ( this.choiceView_.choice == null ) {
          this.choiceData_ = this.otherKey;
          this.otherData_ = this.data;
        } else {
          this.otherData_ = this.otherDefault;
        }
        this.preventFeedback_ = false;
      }
    }
  ],
  methods: [
    function initE() {
      var self = this;
      self.SUPER();
      self.add(self.slot(function(choiceView, otherView) {
        return self.Rows.create()
          .tag(choiceView, { data$: self.choiceData_$ }, self.choiceView_$)
          .start('m3')
            .show(self.showOther_$)
            .add(self.otherLabel$)
          .end()
          .start()
            .show(self.showOther_$)
            .tag(otherView, { data$: self.otherData_$ })
          .end();
      }));
    }
  ]
});<|endoftext|>


<commit_before>import assert from 'assert';
import PanelsActionCreator from '../actions/panels-action-creator';
import SculptureActionCreator from '../actions/sculpture-action-creator';
import MoleGameActionCreator from '../actions/mole-game-action-creator';
import TrackedPanels from '../utils/tracked-panels';
import COLORS from '../constants/colors';

export default class MoleGameLogic {
  // These are automatically added to the sculpture store
  static trackedProperties = {
    panelCount: 0, // Game progress (0..30)
    panels: new TrackedPanels()  // panel -> state
  };

  constructor(store, config) {
    this.store = store;
    this.config = config;
    this.gameConfig = config.MOLE_GAME;

    this._complete = false;

    // Unique panel objects: panelKey -> panel
    this._panels = {};
    this.config.GAME_STRIPS.forEach(stripId => {
      this._lights.get(stripId).panelIds.forEach((panelId) => {
        const key = this._hash(stripId, panelId);
        this._panels[key] = { stripId, panelId, key };
      });
    });

    // Force RGB strips to black.
    // FIXME: This is a temporary fix for a firmware bug not respecting intensity
    this._lights.setColor(this.config.LIGHTS.RGB_STRIPS, null, COLORS.BLACK);

    this.moleGameActionCreator = new MoleGameActionCreator(this.store.dispatcher);
  }

  get data() {
    return this.store.data.get('mole');
  }

  /*!
   * Starts the game logic. Only one of the sculptures should call this method.
   */
  start() {
    this._initRemainingPanels();
    this._complete = false;
    this.data.set('panelCount', 0);
    this.data.get('panels').clear();
    this._registerMoveDelay(0); // Request a new active panel immediately
  }

  /**
   * _remainingPanels is a set of panel keys for panels with STATE_OFF,
   * and are used to select random panels.
   */
  _initRemainingPanels() {
    this._remainingPanels = new Set();
    Object.keys(this._panels).forEach((panelkey) => this._remainingPanels.add(panelkey));
  }

  end() {
    this.config.GAME_STRIPS.forEach(stripId => this._lights.deactivateAll(stripId));
  }

  /**
   * handleActionPayload must _synchronously_ change tracked data in sculpture store.
   * Any asynchronous behavior must happen by dispatching actions.
   * We're _not_ allowed to dispatch actions synchronously.
   */
  handleActionPayload(payload) {
    if (this._complete) return;

    const actionHandlers = {
      [PanelsActionCreator.PANEL_PRESSED]: this._actionPanelPressed.bind(this),
      [MoleGameActionCreator.AVAIL_PANEL]: this._actionAvailPanel.bind(this),
      [MoleGameActionCreator.DEAVAIL_PANEL]: this._actionDeavailPanel.bind(this),
      [SculptureActionCreator.FINISH_STATUS_ANIMATION]: this._actionFinishStatusAnimation.bind(this),
      [SculptureActionCreator.MERGE_STATE]: this._actionMergeState.bind(this),
    };

    const actionHandler = actionHandlers[payload.actionType];
    if (actionHandler) actionHandler(payload);
  }

  /**
   * We only have a status animation at the end of the game
   */
  _actionFinishStatusAnimation() {
    this._complete = true;
    // There is currently no transition out, so we can synchronously start the next game
    this.store.moveToNextGame();
  }

  /**
   * Asynchronous panel activation
   */
  _actionAvailPanel(panel) {
    this._availPanel(panel);
  }

  /**
   * Asynchronous panel deactivation
   */
  _actionDeavailPanel(panel) {
    this._deavailPanel(panel);
  }

  /**
   * Local action:
   *
   * If an active panel is pressed:
   * 1) Turn panel to location color
   * 2) Wait a short moment
   * 3) Avail the next panel
   * 4) increase/decrease # of simulaneously active panels
   */
  _actionPanelPressed(payload) {
    let {stripId, panelId, pressed} = payload;

    const state = this.data.get('panels').getPanelState(stripId, panelId);
    if (state === TrackedPanels.STATE_OFF) {
      if (pressed) {
        this._lights.setColor(stripId, panelId, this.store.locationColor);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.ACTIVE_INTENSITY);
      }
      else {
        this._lights.setToDefaultColor(stripId, panelId);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.INACTIVE_INTENSITY);
      }
      return;
    }

    // If an active panel was touched
    if (state === TrackedPanels.STATE_ON) {
      this._colorPanel(payload);

      // Only for master:
      if (this.store.isMaster()) {
        this._remainingPanels.delete(this._getPanelKey(payload));
        // If we have a timeout on this panel, kill the timeout
        this._removeTimeout(this._getPanelKey(payload));
        this._advanceGame();
      }
    }
  }

  /**
   * Advance game. Should only be called by master
   */
  _advanceGame() {
    let panelCount = this.data.get("panelCount") + 1;
    this.data.set('panelCount', panelCount);
    if (panelCount === this.gameConfig.GAME_END) {
      this._lights.deactivateAll();
      this.store.setSuccessStatus();
    }
    else {
      // Determine whether to add, remove of keep # of simultaneous panels
      const addPanels = 1 + (this.gameConfig.NUM_ACTIVE_PANELS[panelCount] ? this.gameConfig.NUM_ACTIVE_PANELS[panelCount] : 0);

      for (let i=0; i<addPanels; i++) {
        // Wait before next panel
        this._registerMoveDelay(this.gameConfig.PANEL_SUCCESS_DELAY);
      }
    }
  }

  /*!
   * Remote action
   * If we're master, we're responsible for correctly advancing the game
   */
  _actionMergeState(payload) {
    if (!payload.mole) return; // Only handle mole state

    const moleData = this.data;
    const moleChanges = payload.mole;
    const moleProps = payload.metadata.props.mole;

    // Master owns the panelCount field
    if (!this.store.isMaster()) {
      if (moleChanges.hasOwnProperty('panelCount')) {
        moleData.set('panelCount', moleChanges.panelCount, moleProps.panelCount);
      }
    }

    // Iterate over changed panel states
    if (moleChanges.hasOwnProperty('panels')) {
      const panels = moleData.get('panels');
      const changedPanels = moleChanges.panels;
      for (let panelKey of Object.keys(changedPanels)) {
        const newstate = changedPanels[panelKey];
        const oldstate = panels.getPanelStateByKey(panelKey);

        // Slaves just merge
        if (!this.store.isMaster()) {
          panels.setPanelStateByKey(panelKey, newstate, moleProps.panels[panelKey]);
        }
        else {
          // Master will only accept state modification to STATE_IGNORED
          if (newstate !== oldstate && newstate === TrackedPanels.STATE_IGNORED) {
            panels.setPanelStateByKey(panelKey, newstate);

            // oldstate was ON
            // Normal situation (no race condition): oldstate === ON
            if (this._hasTimeout(panelKey)) { // Normal situation (no race condition): oldstate === ON
              console.log('Merge normal (after new panel)');
              if (oldstate !== TrackedPanels.STATE_ON) {
                assert('state must be STATE_ON');
              }

              this._remainingPanels.delete(panelKey);
              this._removeTimeout(panelKey);
              this._advanceGame();
            }
            // oldstate was OFF
            else {
              if (oldstate !== TrackedPanels.STATE_OFF) {
                assert('state must be STATE_OFF');
              }

              // If we don't have a timeout any longer, we have a race condition.
              // We could be in a few states:
              // 1_ PANEL_SUCCESS_TIMER is active
              // Master already hit the panel => Master wins.
              // This is a NOP since master will pass on the correct colors automatically

              // 2) PANEL_MOVE_DELAY timer is active
              // Master just deactivated the panel, waiting to activate the next one
              // => Kill timer and advance the game
              if (oldstate === TrackedPanels.STATE_OFF && this._panels[panelKey].moveDelay) {
                console.log('merged during MOVE_DELAY');
                clearTimeout(this._panels[panelKey].moveDelay);
                delete this._panels[panelKey].moveDelay;
                this._remainingPanels.delete(panelKey);
                this._advanceGame();
              }

              // 3) The next panel was already made active
              // Accept the change. We also need to reset the intensity
              //
              // Note: This can never be the last move, as the last panel would then be
              // reactivated, not OFF.
              else if (oldstate === TrackedPanels.STATE_OFF && !this._panels[panelKey].moveDelay) {
                console.log('merged after new panel');
                this._remainingPanels.delete(panelKey);
                // FIXME: If this is the last panel, we may need to go to _winGame()
                this.data.set('panelCount', this.data.get("panelCount") + 1);
                this._lights.setIntensity(this._panels[panelKey].stripId, this._panels[panelKey].panelId, this.gameConfig.COLORED_PANEL_INTENSITY);
              }
              else {
                console.log('merge: Master wins - slave is late');
              }
            }
            // Make sure changes are merged by all slaves
            this.store.reassertChanges();
          }
        }
      }
    }
  }

  _hash(stripId, panelId) {
    return `${stripId},${panelId}`;
  }

  _getPanelKey({stripId, panelId}) {
    return this._hash(stripId, panelId);
  }

  /**
   * Request the next active panel, as the game progresses
   * Returns {panel, lifetime}
   */
  _nextActivePanel(count) {
    if (count < this.gameConfig.INITIAL_PANELS.length) {
      const panel = this.gameConfig.INITIAL_PANELS[count];
      const panelkey = this._getPanelKey(panel);
      return { panelkey, lifetime: this._getPanelLifetime(count) }; // No timeout
    }
    return { panelkey: this._getRandomPanel(), lifetime: this._getPanelLifetime(count)};
  }

  /**
   * Selects and returns a random panelkey from _remainingPanels
   */
  _getRandomPanel() {
    const idx = Math.floor(Math.random() * this._remainingPanels.size);
    const iter = this._remainingPanels.values();
    let curr = iter.next();
    for (let i=0; i<idx; i++) curr = iter.next();
    return curr.value;
  }

  _getPanelLifetime(count) {
    // find last and next lifetime values for interpolation
    let last, next;
    for (let elem of this.gameConfig.PANEL_LIFETIME) {
      if (!last || elem.count <= count) last = elem;
      next = elem;
      if (elem.count > count) break;
    }

    let min, max;
    if (last === next) {
      min = last.range[0];
      max = last.range[1];
    }
    else {
      min = last.range[0] + (next.range[0] - last.range[0]) * (count - last.count) / (next.count - last.count);
      max = last.range[1] + (next.range[1] - last.range[1]) * (count - last.count) / (next.count - last.count);
    }
    return 1000 * (Math.random() * (max - min) + min);
  }

  /**
   * Called if an active panel times out: We'll move the panel (deavail+pause+avail).
   */
  _panelTimeout(oldPanel) {
    const key = this._getPanelKey(oldPanel);
    delete this._panels[key].timeout;
    this.moleGameActionCreator.sendDeavailPanel(oldPanel);
    this._registerMoveDelay(this.gameConfig.PANEL_MOVE_DELAY, key);
  }

  _requestPanel(oldPanelKey = null) {
    if (oldPanelKey) delete this._panels[oldPanelKey].moveDelay;
    const {panelkey, lifetime} = this._nextActivePanel(this.data.get("panelCount"));
    if (!panelkey) {
      console.error('No panel key!');
    }
    this.moleGameActionCreator.sendAvailPanel(this._panels[panelkey]);
    this._panels[panelkey].timeout = setTimeout(this._panelTimeout.bind(this, this._panels[panelkey]), lifetime);
  }

  _registerMoveDelay(delay, panelKey = null) {
    const tid = setTimeout(this._requestPanel.bind(this, panelKey), delay);
    if (panelKey) this._panels[panelKey].moveDelay = tid;
  }

  /**
   * Check if we have a timeout registered on the given panel
   */
  _hasTimeout(key) {
    return this._panels[key].timeout !== undefined;
  }

  /**
   * Removes a timeout with the given panel key
   */
  _removeTimeout(key) {
    if (this._hasTimeout(key)) {
      clearTimeout(this._panels[key].timeout);
      delete this._panels[key].timeout;
    }
  }

  /**
   * Only called from a timeout
   */
  _availPanel(panel) {
    this._setPanelState(panel, TrackedPanels.STATE_ON);
    this._remainingPanels.delete(this._getPanelKey(panel));
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.ACTIVE_PANEL_INTENSITY);
  }

  /**
   * Only called from a timeout
   */
  _deavailPanel(panel) {
    this._remainingPanels.add(this._getPanelKey(panel));
    this._setPanelState(panel, TrackedPanels.STATE_OFF);
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.INACTIVE_PANEL_INTENSITY);
  }

  /**
   * Called by the sculpture first hitting an ON panel
   */
  _colorPanel(panel) {
    this._setPanelState(panel, TrackedPanels.STATE_IGNORED);
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.COLORED_PANEL_INTENSITY);
    this._lights.setColor(panel.stripId, panel.panelId, this.store.locationColor);
  }

  get _lights() {
    return this.store.data.get('lights');
  }

  _setPanelState({stripId, panelId}, state) {
    this.data.get('panels').setPanelState(stripId, panelId, state);
  }

}
<commit_msg>Framework for mole game out transition<commit_after>import assert from 'assert';
import PanelsActionCreator from '../actions/panels-action-creator';
import SculptureActionCreator from '../actions/sculpture-action-creator';
import MoleGameActionCreator from '../actions/mole-game-action-creator';
import TrackedPanels from '../utils/tracked-panels';
import COLORS from '../constants/colors';
import PanelAnimation from '../animation/panel-animation';
import Frame from '../animation/frame';

export default class MoleGameLogic {
  // These are automatically added to the sculpture store
  static trackedProperties = {
    panelCount: 0, // Game progress (0..30)
    panels: new TrackedPanels()  // panel -> state
  };

  constructor(store, config) {
    this.store = store;
    this.config = config;
    this.gameConfig = config.MOLE_GAME;

    this._complete = false;

    // Unique panel objects: panelKey -> panel
    this._panels = {};
    this.config.GAME_STRIPS.forEach(stripId => {
      this._lights.get(stripId).panelIds.forEach((panelId) => {
        const key = this._hash(stripId, panelId);
        this._panels[key] = { stripId, panelId, key };
      });
    });

    // Force RGB strips to black.
    // FIXME: This is a temporary fix for a firmware bug not respecting intensity
    this._lights.setColor(this.config.LIGHTS.RGB_STRIPS, null, COLORS.BLACK);

    this.sculptureActionCreator = new SculptureActionCreator(this.store.dispatcher);
    this.moleGameActionCreator = new MoleGameActionCreator(this.store.dispatcher);
  }

  get data() {
    return this.store.data.get('mole');
  }

  /*!
   * Starts the game logic. Only one of the sculptures should call this method.
   */
  start() {
    this._initRemainingPanels();
    this._complete = false;
    this.data.set('panelCount', 0);
    this.data.get('panels').clear();
    this._registerMoveDelay(0); // Request a new active panel immediately
  }

  /**
   * _remainingPanels is a set of panel keys for panels with STATE_OFF,
   * and are used to select random panels.
   */
  _initRemainingPanels() {
    this._remainingPanels = new Set();
    Object.keys(this._panels).forEach((panelkey) => this._remainingPanels.add(panelkey));
  }

  end() {
    this.config.GAME_STRIPS.forEach(stripId => this._lights.deactivateAll(stripId));
  }

  /**
   * handleActionPayload must _synchronously_ change tracked data in sculpture store.
   * Any asynchronous behavior must happen by dispatching actions.
   * We're _not_ allowed to dispatch actions synchronously.
   */
  handleActionPayload(payload) {
    if (this._complete) return;

    const actionHandlers = {
      [PanelsActionCreator.PANEL_PRESSED]: this._actionPanelPressed.bind(this),
      [MoleGameActionCreator.AVAIL_PANEL]: this._actionAvailPanel.bind(this),
      [MoleGameActionCreator.DEAVAIL_PANEL]: this._actionDeavailPanel.bind(this),
      [SculptureActionCreator.MERGE_STATE]: this._actionMergeState.bind(this),
    };

    const actionHandler = actionHandlers[payload.actionType];
    if (actionHandler) actionHandler(payload);
  }

  /**
   * Asynchronous panel activation
   */
  _actionAvailPanel(panel) {
    this._availPanel(panel);
  }

  /**
   * Asynchronous panel deactivation
   */
  _actionDeavailPanel(panel) {
    this._deavailPanel(panel);
  }

  /**
   * Local action:
   *
   * If an active panel is pressed:
   * 1) Turn panel to location color
   * 2) Wait a short moment
   * 3) Avail the next panel
   * 4) increase/decrease # of simulaneously active panels
   */
  _actionPanelPressed(payload) {
    let {stripId, panelId, pressed} = payload;

    const state = this.data.get('panels').getPanelState(stripId, panelId);
    if (state === TrackedPanels.STATE_OFF) {
      if (pressed) {
        this._lights.setColor(stripId, panelId, this.store.locationColor);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.ACTIVE_INTENSITY);
      }
      else {
        this._lights.setToDefaultColor(stripId, panelId);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.INACTIVE_INTENSITY);
      }
      return;
    }

    // If an active panel was touched
    if (state === TrackedPanels.STATE_ON) {
      this._colorPanel(payload);

      // Only for master:
      if (this.store.isMaster()) {
        this._remainingPanels.delete(this._getPanelKey(payload));
        // If we have a timeout on this panel, kill the timeout
        this._removeTimeout(this._getPanelKey(payload));
        this._advanceGame();
      }
    }
  }

  _winGame() {
    // Count all panel colors
    const colorCount = { };
    this.config.GAME_STRIPS.forEach(stripId => {
      const panelIds = this._lights.get(stripId).panelIds;
      panelIds.forEach((panelId) => {
        const col = this._lights.getColor(stripId, panelId);
        colorCount[col] = (colorCount[col] || 0) + 1;
      });
    });

    // Determine winning color
    const winningColor = Object.entries(colorCount).reduce((a, b) => a[1] > b[1] ? a : b)[0];
    console.log(`Winning color: ${winningColor}`);

    // Transition animation:
    // 1) Turn off non-winning colors
    // 2) Turn off all colors and start next game
    const transitionFrames = [
      new Frame(() => {
        this._lights.deactivateAll();
      }, 0),
      new Frame(() => {
        this.config.GAME_STRIPS.forEach(stripId => {
          const panelIds = this._lights.get(stripId).panelIds;
          panelIds.forEach((panelId) => {
            if (this._lights.getColor(stripId, panelId) !== winningColor) {
              this._lights.setIntensity(stripId, panelId, 0);
            }
          });
        });
      }, 1000),
      new Frame(() => {
        this.config.GAME_STRIPS.forEach(stripId => {
          this._lights.setIntensity(stripId, null, 0);
        });
        this._complete = true;
        setTimeout(() => this.sculptureActionCreator.sendStartNextGame(), 1000);
      }, 5000),
    ];

    const transitionAnimation = new PanelAnimation(transitionFrames);
    this.store.playAnimation(transitionAnimation);
  }

  /**
   * Advance game. Should only be called by master
   */
  _advanceGame() {
    let panelCount = this.data.get("panelCount") + 1;
    this.data.set('panelCount', panelCount);
    if (panelCount === this.gameConfig.GAME_END) {
      this._winGame();
    }
    else {
      // Determine whether to add, remove of keep # of simultaneous panels
      const addPanels = 1 + (this.gameConfig.NUM_ACTIVE_PANELS[panelCount] ? this.gameConfig.NUM_ACTIVE_PANELS[panelCount] : 0);

      for (let i=0; i<addPanels; i++) {
        // Wait before next panel
        this._registerMoveDelay(this.gameConfig.PANEL_SUCCESS_DELAY);
      }
    }
  }

  /*!
   * Remote action
   * If we're master, we're responsible for correctly advancing the game
   */
  _actionMergeState(payload) {
    if (!payload.mole) return; // Only handle mole state

    const moleData = this.data;
    const moleChanges = payload.mole;
    const moleProps = payload.metadata.props.mole;

    // Master owns the panelCount field
    if (!this.store.isMaster()) {
      if (moleChanges.hasOwnProperty('panelCount')) {
        moleData.set('panelCount', moleChanges.panelCount, moleProps.panelCount);
      }
    }

    // Iterate over changed panel states
    if (moleChanges.hasOwnProperty('panels')) {
      const panels = moleData.get('panels');
      const changedPanels = moleChanges.panels;
      for (let panelKey of Object.keys(changedPanels)) {
        const newstate = changedPanels[panelKey];
        const oldstate = panels.getPanelStateByKey(panelKey);

        // Slaves just merge
        if (!this.store.isMaster()) {
          panels.setPanelStateByKey(panelKey, newstate, moleProps.panels[panelKey]);
        }
        else {
          // Master will only accept state modification to STATE_IGNORED
          if (newstate !== oldstate && newstate === TrackedPanels.STATE_IGNORED) {
            panels.setPanelStateByKey(panelKey, newstate);

            // oldstate was ON
            // Normal situation (no race condition): oldstate === ON
            if (this._hasTimeout(panelKey)) { // Normal situation (no race condition): oldstate === ON
              console.log('Merge normal (after new panel)');
              if (oldstate !== TrackedPanels.STATE_ON) {
                assert('state must be STATE_ON');
              }

              this._remainingPanels.delete(panelKey);
              this._removeTimeout(panelKey);
              this._advanceGame();
            }
            // oldstate was OFF
            else {
              if (oldstate !== TrackedPanels.STATE_OFF) {
                assert('state must be STATE_OFF');
              }

              // If we don't have a timeout any longer, we have a race condition.
              // We could be in a few states:
              // 1_ PANEL_SUCCESS_TIMER is active
              // Master already hit the panel => Master wins.
              // This is a NOP since master will pass on the correct colors automatically

              // 2) PANEL_MOVE_DELAY timer is active
              // Master just deactivated the panel, waiting to activate the next one
              // => Kill timer and advance the game
              if (oldstate === TrackedPanels.STATE_OFF && this._panels[panelKey].moveDelay) {
                console.log('merged during MOVE_DELAY');
                clearTimeout(this._panels[panelKey].moveDelay);
                delete this._panels[panelKey].moveDelay;
                this._remainingPanels.delete(panelKey);
                this._advanceGame();
              }

              // 3) The next panel was already made active
              // Accept the change. We also need to reset the intensity
              //
              // Note: This can never be the last move, as the last panel would then be
              // reactivated, not OFF.
              else if (oldstate === TrackedPanels.STATE_OFF && !this._panels[panelKey].moveDelay) {
                console.log('merged after new panel');
                this._remainingPanels.delete(panelKey);
                // FIXME: If this is the last panel, we may need to go to _winGame()
                this.data.set('panelCount', this.data.get("panelCount") + 1);
                this._lights.setIntensity(this._panels[panelKey].stripId, this._panels[panelKey].panelId, this.gameConfig.COLORED_PANEL_INTENSITY);
              }
              else {
                console.log('merge: Master wins - slave is late');
              }
            }
            // Make sure changes are merged by all slaves
            this.store.reassertChanges();
          }
        }
      }
    }
  }

  _hash(stripId, panelId) {
    return `${stripId},${panelId}`;
  }

  _getPanelKey({stripId, panelId}) {
    return this._hash(stripId, panelId);
  }

  /**
   * Request the next active panel, as the game progresses
   * Returns {panel, lifetime}
   */
  _nextActivePanel(count) {
    if (count < this.gameConfig.INITIAL_PANELS.length) {
      const panel = this.gameConfig.INITIAL_PANELS[count];
      const panelkey = this._getPanelKey(panel);
      return { panelkey, lifetime: this._getPanelLifetime(count) }; // No timeout
    }
    return { panelkey: this._getRandomPanel(), lifetime: this._getPanelLifetime(count)};
  }

  /**
   * Selects and returns a random panelkey from _remainingPanels
   */
  _getRandomPanel() {
    const idx = Math.floor(Math.random() * this._remainingPanels.size);
    const iter = this._remainingPanels.values();
    let curr = iter.next();
    for (let i=0; i<idx; i++) curr = iter.next();
    return curr.value;
  }

  _getPanelLifetime(count) {
    // find last and next lifetime values for interpolation
    let last, next;
    for (let elem of this.gameConfig.PANEL_LIFETIME) {
      if (!last || elem.count <= count) last = elem;
      next = elem;
      if (elem.count > count) break;
    }

    let min, max;
    if (last === next) {
      min = last.range[0];
      max = last.range[1];
    }
    else {
      min = last.range[0] + (next.range[0] - last.range[0]) * (count - last.count) / (next.count - last.count);
      max = last.range[1] + (next.range[1] - last.range[1]) * (count - last.count) / (next.count - last.count);
    }
    return 1000 * (Math.random() * (max - min) + min);
  }

  /**
   * Called if an active panel times out: We'll move the panel (deavail+pause+avail).
   */
  _panelTimeout(oldPanel) {
    const key = this._getPanelKey(oldPanel);
    delete this._panels[key].timeout;
    this.moleGameActionCreator.sendDeavailPanel(oldPanel);
    this._registerMoveDelay(this.gameConfig.PANEL_MOVE_DELAY, key);
  }

  _requestPanel(oldPanelKey = null) {
    if (oldPanelKey) delete this._panels[oldPanelKey].moveDelay;
    const {panelkey, lifetime} = this._nextActivePanel(this.data.get("panelCount"));
    if (!panelkey) {
      console.error('No panel key!');
    }
    this.moleGameActionCreator.sendAvailPanel(this._panels[panelkey]);
    this._panels[panelkey].timeout = setTimeout(this._panelTimeout.bind(this, this._panels[panelkey]), lifetime);
  }

  _registerMoveDelay(delay, panelKey = null) {
    const tid = setTimeout(this._requestPanel.bind(this, panelKey), delay);
    if (panelKey) this._panels[panelKey].moveDelay = tid;
  }

  /**
   * Check if we have a timeout registered on the given panel
   */
  _hasTimeout(key) {
    return this._panels[key].timeout !== undefined;
  }

  /**
   * Removes a timeout with the given panel key
   */
  _removeTimeout(key) {
    if (this._hasTimeout(key)) {
      clearTimeout(this._panels[key].timeout);
      delete this._panels[key].timeout;
    }
  }

  /**
   * Only called from a timeout
   */
  _availPanel(panel) {
    this._setPanelState(panel, TrackedPanels.STATE_ON);
    this._remainingPanels.delete(this._getPanelKey(panel));
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.ACTIVE_PANEL_INTENSITY);
  }

  /**
   * Only called from a timeout
   */
  _deavailPanel(panel) {
    this._remainingPanels.add(this._getPanelKey(panel));
    this._setPanelState(panel, TrackedPanels.STATE_OFF);
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.INACTIVE_PANEL_INTENSITY);
  }

  /**
   * Called by the sculpture first hitting an ON panel
   */
  _colorPanel(panel) {
    this._setPanelState(panel, TrackedPanels.STATE_IGNORED);
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.COLORED_PANEL_INTENSITY);
    this._lights.setColor(panel.stripId, panel.panelId, this.store.locationColor);
  }

  get _lights() {
    return this.store.data.get('lights');
  }

  _setPanelState({stripId, panelId}, state) {
    this.data.get('panels').setPanelState(stripId, panelId, state);
  }

}
<|endoftext|>


<commit_before>define([
    "controller/channel",
    "model/model",
    "view/view",
    "queue/queue",
    "events/events",
    "events/states",
    "utils/strings",
    "utils/event",
    "utils/logger",
], function(Channel, Model, View, Queue, Events, States, Strings, Event, Logger) {
    /**
     * gPlayer main controller
     *
     * Responsible for the general flow and creation of the view and models
     */
    var Controller = function(config) {
        this.init(config);
    };

    Controller.prototype = {
        /**
         * Referances
         */
        _view: null,
        _model: null,
        _queue: null,
        _notifications: null,

        _channels: [],
        _state: States.IDLE,

        audioCtx: null,
        mute: false,
        volume: 1,

        /**
         * Constructor function for the main controller
         *
         * Registering important instances to hold here
         *
         * @param {Object} config
         * @param {Object}
         * @return this for chaining
         */
        init: function(config) {
            Logger.debug('CONTROLLER::INIT FIRED');

            this._model = new Model(config);
            this._notifications = new Event();
            this._view = new View(this, this._notifications);
            this._queue = new Queue(this._notifications);

            this.subscribe();

            return this;
        },

        /**
         *
         */
        subscribe: function() {
            var self = this;

            this.getNotifications().on(Events.STATE_CHANGED, function(state) {
                self._state = state;
            });

            this.getNotifications().on(Events.PLAY, function(item) {
                console.log(['CONTROLLER::ITEM ARRIVED', item]);
                self.channelAdd(item);
                self.changeState(States.PLAYING);
            });

            this.getNotifications().on(Events.PAUSE, function() {
                self.changeState(States.PAUSED);
            });

            $(window).bind('resize', function() {
                self.getNotifications().fire(Events.RESIZE);
            });

            return this;
        },

        /**
         * Bootstraps the main controller
         */
        bootstrap: function(container) {
            this._model.setup(container);
            this._view.setup();
            this.loop();
        },

        /**
         * Getter for the Queue object, set on constructor
         */
        getQueue: function() {
            return this._queue;
        },

        /**
         * Getter for the Model object, set on constructor
         */
        getModel: function() {
            return this._model;
        },

        /**
         * Getter for the Notifications object, set on constructor
         */
        getNotifications: function() {
            return this._notifications;
        },

        /**
         * Changes the state of the player by boardcasting the proper event on change.
         * This is the only place modifies the state of the player
         *
         * @param {String} newState (ENUM from States object)
         */
        changeState: function(newState) {
            if (newState === this._state) return;

            this.getNotifications().fire(Events.STATE_CHANGED, newState);
        },

        /**
         *
         */
        isPlaying: function() {
            for (var i = 0, len = this._channels.length; i < len; i++) {
                if ('undefined' !== typeof(this._channels[i]) && true === this._channels[i].isPlaying())
                    return true;
            }

            return false;
        },

        /**
         *
         */
        channelAdd: function (item) {
            var channelNew = new Channel({
                parent: this,
                item: item
            });

            this._channels.push(channelNew);
            channelNew.mediaPlay();

            return this;
        },

        /**
         *
         */
        getActiveChannel: function() {
            return this._channels[0]; //channel 0 is the active.. 1 is usually created only while crossfading and even then.. 0 is the active.
        },

        isCrossfade: function() {
            return this._model.crossfade.enabled;
        },

        isMute: function() {
            return this.mute;
        },

        /**
         *
         */
        getVolume: function() {
            return this.volume;
        },

        _onEnd: function(crossfade) {
            if (true === crossfade) {
                setTimeout($.proxy(function() {
                    this.activeChannelCleanup();
                }, this), this._model.crossfade.onBeforeEnd * 1000);
            } else {
                this.activeChannelCleanup();
            }

            this.getNotifications().fire(Events.PLAY_NEXT);
            //api.onEnd();
        },

        /**
         *
         */
        setVolume: function(volume) {
            this.volume = (1 <= volume) ? 1 : volume; // can not be greater than 1
            this.mute = (0 >= volume) ? true : false;

            //update view.
            this._view.getPlayerView().setVolumeBar();

            //updates the channel with volume value.
            for (var i = 0, len = this._channels.length; i < len; i++) {
                if ('undefined' !== typeof(this._channels[i]))
                    this._channels[i].setVolume();
            }

            /*
            var interactiveObj = $('.groovy-interactive');
            interactiveObj.find('.groovy-scrubber').find('img').eq(0).css({
                transform: 'scaleY('+arg+')'
            });

            if(arg == 0){
                cthis.find('.scrub-bg-img-reflect').fadeOut('slow');
            } else {
                cthis.find('.scrub-bg-img-reflect').fadeIn('slow');
            }
            last_vol = arg;
            */
        },

        /**
         *
         */
        loop: function() {
            // verify everything needed for the loop exists. Run loop only when playing.
            if ('undefined' === typeof(this.getActiveChannel()) || !this.isPlaying()) {
                return this._runLoop();
            }

            var scrubberWidth = this._view.getPlayerView().getScrubberWidth(),
                currentTime = $('.groovy-current-time'),
                duration = $('.groovy-duration'),
                scrubberOffset = 0,
                scrubberOffsetPercent = 0,
                scrubberOffsetPixels = 0,
                timeTotal = this.getActiveAudio().duration,
                timeCurr = this.getActiveAudio().currentTime,
                timeOffset = 3;

            /*
            // might not work.. audiobuffer is per channel.. odd location for referances, check!.
            if (this.audioBuffer && 'placeholder' !== this.audioBuffer) {
                timeTotal = this.audioBuffer.duration;
                timeCurr = this.audioCtx.currentTime;
                //console.log(this.audioBuffer.currentTime);
            }*/

            scrubberOffset = ((timeCurr - this._model.style.interactiveSelectOffset) / timeTotal);
            scrubberOffsetPercent = scrubberOffset * 100;

            // console.log(_scrubbar.children('.scrub-prog'), scrubberOffsetPercent, timeTotal, '-timecurr ', timeCurr, scrubberWidth);
            if (null == this.audioBuffer) {
                $('body').find('.groovy-spectrum-progress').css({
                    width: scrubberOffsetPercent + '%'
                });

                $('body').find('.groovy-scrubber-progress').css({
                    width: scrubberOffsetPercent + '%'
                });

                $('body').find('.groovy-play-bar').css({
                    width: scrubberOffsetPercent + '%'
                });
            }

            if (true === this._model.spectrum.enabled)
                this.drawSpectrum();

            if (true === this._model.timerDynamic) {
                scrubberOffsetPixels = scrubberOffset * scrubberWidth;

                currentTime.css({
                    left: scrubberOffsetPercent + '%'
                });

                if (scrubberOffsetPixels > scrubberWidth - 30) {
                    currentTime.css({
                        opacity: 1 - (((scrubberOffsetPixels - (scrubberWidth - 30)) / 30))
                    });
                } else {
                    if (1 !== Number(currentTime.css('opacity'))) {
                        currentTime.css({
                            opacity: 1
                        });
                    }
                };
            };

            //console.info(_currTime, timeCurr, this.formatTime(timeCurr))
            currentTime.text(new Strings(timeCurr).formatTime());
            duration.text(new Strings(timeTotal).formatTime());

            // notify channel
            for (var i = 0, len = this._channels.length; i < len; i++) {
                if ('undefined' !== typeof(this._channels[i])) {
                    this._channels[i].loop();
                } else {
                    console.log(['DEBUG', this._channels, i]);
                }
            }

            this._runLoop();
        },

        /**
         *
         */
        _runLoop: function() {
            requestAnimFrame($.proxy(function() {
                this.loop();
            }, this));

            return this;
        },

        /**
         *
         */
        activeChannelCleanup: function() {
            if ('undefined' !== typeof(this.getActiveChannel())) {
                this.getActiveChannel().destory();
                console.log(this._channels);
                this._channels.shift();
            }

            return this;
        },

        /**
         *
         */
        pause: function() {
            this.activeChannelCleanup();

            return this;
        },

        /**
         *
         */
        getCtx: function() {
            if (null === this.audioCtx) {
                this.audioCtx = new window.AudioContext;
            }

            return this.audioCtx;
        },

        /**
         * Based on a Fiddle I wrote: http://jsfiddle.net/Fc8Jr/
        **/
        drawSpectrum: function() {
            var frequencyByteData = this.getActiveChannel().frequencyByteData,
                canvas = this._view.getPlayerView().getCanvasReferances();

            var width = canvas.el.active.background.width,
                height = canvas.el.active.background.height;

            var BAR_WIDTH = 3,
                SPACER_WIDTH = 1;

            /* test.. disabled.
            var gradient = canvas.context.active.background.createLinearGradient(0, 0, width, height);
            gradient.addColorStop(1,'#000000');
            gradient.addColorStop(0.75,'#ff0000');
            gradient.addColorStop(0.25,'#ffff00');
            gradient.addColorStop(0,'#ffffff');
            */

            //canvas.el.active.background.width = width;
            canvas.context.active.background.clearRect(0, 0, width, height);
            canvas.context.active.background.fillStyle = '#'+ this._model.spectrum.color.bg;

            canvas.context.active.progress.clearRect(0, 0, width, height);
            canvas.context.active.progress.fillStyle = '#'+ this._model.spectrum.color.progress;

            for (var i = 0, magnitude = 0, len = frequencyByteData.length; i < len; i++) {
                magnitude = Math.ceil(frequencyByteData[i] * height / 256);
                if (2 > magnitude) magnitude = 2;

                canvas.context.active.background.fillRect(i * (SPACER_WIDTH + BAR_WIDTH), height, BAR_WIDTH, -magnitude);
                canvas.context.active.progress.fillRect(i * (SPACER_WIDTH + BAR_WIDTH), height, BAR_WIDTH, -magnitude);

                //canvas.context.active.background.fillRect(i/256 * width, height, width/len, -magnitude);
                //canvas.context.active.progress.fillRect(i/256 * width, height, width/len, -magnitude);
            }

            if (true === this._model.reflection) {
                canvas.context.reflect.background.clearRect(0, 0, width, height);
                canvas.context.reflect.background.drawImage(canvas.el.active.background, 0, 0);

                canvas.context.reflect.progress.clearRect(0, 0, width, height);
                canvas.context.reflect.progress.drawImage(canvas.el.active.progress, 0, 0);
            }
        },

        /**
         *
         */
        startedPlaying: function() {
            console.log('CONTROLLER::startedPlaying fired -- oh well.. started, not sure i want to keep this');
        },

        /**
         *
         */
        getActiveAudio: function() {
            return this.getActiveChannel().audioEl;
        }
    };

    window.AudioContext = window.AudioContext || window.webkitAudioContext;

    window.requestAnimFrame = (function() {
        return window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            window.oRequestAnimationFrame ||
            window.msRequestAnimationFrame ||
            function(callback, el) {
                window.setTimeout(callback, 1000 / 60);
            };
    })();

    return Controller;
});<commit_msg>pause wired to action<commit_after>define([
    "controller/channel",
    "model/model",
    "view/view",
    "queue/queue",
    "events/events",
    "events/states",
    "utils/strings",
    "utils/event",
    "utils/logger",
], function(Channel, Model, View, Queue, Events, States, Strings, Event, Logger) {
    /**
     * gPlayer main controller
     *
     * Responsible for the general flow and creation of the view and models
     */
    var Controller = function(config) {
        this.init(config);
    };

    Controller.prototype = {
        /**
         * Referances
         */
        _view: null,
        _model: null,
        _queue: null,
        _notifications: null,

        _channels: [],
        _state: States.IDLE,

        audioCtx: null,
        mute: false,
        volume: 1,

        /**
         * Constructor function for the main controller
         *
         * Registering important instances to hold here
         *
         * @param {Object} config
         * @param {Object}
         * @return this for chaining
         */
        init: function(config) {
            Logger.debug('CONTROLLER::INIT FIRED');

            this._model = new Model(config);
            this._notifications = new Event();
            this._view = new View(this, this._notifications);
            this._queue = new Queue(this._notifications);

            this.subscribe();

            return this;
        },

        /**
         *
         */
        subscribe: function() {
            var self = this;

            this.getNotifications().on(Events.STATE_CHANGED, function(state) {
                self._state = state;
            });

            this.getNotifications().on(Events.PLAY, function(item) {
                console.log(['CONTROLLER::ITEM ARRIVED', item]);
                self.channelAdd(item);
                self.changeState(States.PLAYING);
            });

            this.getNotifications().on(Events.PAUSE, function() {
                self.changeState(States.PAUSED);
                self.pause();
            });

            $(window).bind('resize', function() {
                self.getNotifications().fire(Events.RESIZE);
            });

            return this;
        },

        /**
         * Bootstraps the main controller
         */
        bootstrap: function(container) {
            this._model.setup(container);
            this._view.setup();
            this.loop();
        },

        /**
         * Getter for the Queue object, set on constructor
         */
        getQueue: function() {
            return this._queue;
        },

        /**
         * Getter for the Model object, set on constructor
         */
        getModel: function() {
            return this._model;
        },

        /**
         * Getter for the Notifications object, set on constructor
         */
        getNotifications: function() {
            return this._notifications;
        },

        /**
         * Changes the state of the player by boardcasting the proper event on change.
         * This is the only place modifies the state of the player
         *
         * @param {String} newState (ENUM from States object)
         */
        changeState: function(newState) {
            if (newState === this._state) return;

            this.getNotifications().fire(Events.STATE_CHANGED, newState);
        },

        /**
         *
         */
        isPlaying: function() {
            for (var i = 0, len = this._channels.length; i < len; i++) {
                if ('undefined' !== typeof(this._channels[i]) && true === this._channels[i].isPlaying())
                    return true;
            }

            return false;
        },

        /**
         *
         */
        channelAdd: function (item) {
            var channelNew = new Channel({
                parent: this,
                item: item
            });

            this._channels.push(channelNew);
            channelNew.mediaPlay();

            return this;
        },

        /**
         *
         */
        getActiveChannel: function() {
            return this._channels[0]; //channel 0 is the active.. 1 is usually created only while crossfading and even then.. 0 is the active.
        },

        isCrossfade: function() {
            return this._model.crossfade.enabled;
        },

        isMute: function() {
            return this.mute;
        },

        /**
         *
         */
        getVolume: function() {
            return this.volume;
        },

        _onEnd: function(crossfade) {
            if (true === crossfade) {
                setTimeout($.proxy(function() {
                    this.activeChannelCleanup();
                }, this), this._model.crossfade.onBeforeEnd * 1000);
            } else {
                this.activeChannelCleanup();
            }

            this.getNotifications().fire(Events.PLAY_NEXT);
            //api.onEnd();
        },

        /**
         *
         */
        setVolume: function(volume) {
            this.volume = (1 <= volume) ? 1 : volume; // can not be greater than 1
            this.mute = (0 >= volume) ? true : false;

            //update view.
            this._view.getPlayerView().setVolumeBar();

            //updates the channel with volume value.
            for (var i = 0, len = this._channels.length; i < len; i++) {
                if ('undefined' !== typeof(this._channels[i]))
                    this._channels[i].setVolume();
            }

            /*
            var interactiveObj = $('.groovy-interactive');
            interactiveObj.find('.groovy-scrubber').find('img').eq(0).css({
                transform: 'scaleY('+arg+')'
            });

            if(arg == 0){
                cthis.find('.scrub-bg-img-reflect').fadeOut('slow');
            } else {
                cthis.find('.scrub-bg-img-reflect').fadeIn('slow');
            }
            last_vol = arg;
            */
        },

        /**
         *
         */
        loop: function() {
            // verify everything needed for the loop exists. Run loop only when playing.
            if ('undefined' === typeof(this.getActiveChannel()) || !this.isPlaying()) {
                return this._runLoop();
            }

            var scrubberWidth = this._view.getPlayerView().getScrubberWidth(),
                currentTime = $('.groovy-current-time'),
                duration = $('.groovy-duration'),
                scrubberOffset = 0,
                scrubberOffsetPercent = 0,
                scrubberOffsetPixels = 0,
                timeTotal = this.getActiveAudio().duration,
                timeCurr = this.getActiveAudio().currentTime,
                timeOffset = 3;

            /*
            // might not work.. audiobuffer is per channel.. odd location for referances, check!.
            if (this.audioBuffer && 'placeholder' !== this.audioBuffer) {
                timeTotal = this.audioBuffer.duration;
                timeCurr = this.audioCtx.currentTime;
                //console.log(this.audioBuffer.currentTime);
            }*/

            scrubberOffset = ((timeCurr - this._model.style.interactiveSelectOffset) / timeTotal);
            scrubberOffsetPercent = scrubberOffset * 100;

            // console.log(_scrubbar.children('.scrub-prog'), scrubberOffsetPercent, timeTotal, '-timecurr ', timeCurr, scrubberWidth);
            if (null == this.audioBuffer) {
                $('body').find('.groovy-spectrum-progress').css({
                    width: scrubberOffsetPercent + '%'
                });

                $('body').find('.groovy-scrubber-progress').css({
                    width: scrubberOffsetPercent + '%'
                });

                $('body').find('.groovy-play-bar').css({
                    width: scrubberOffsetPercent + '%'
                });
            }

            if (true === this._model.spectrum.enabled)
                this.drawSpectrum();

            if (true === this._model.timerDynamic) {
                scrubberOffsetPixels = scrubberOffset * scrubberWidth;

                currentTime.css({
                    left: scrubberOffsetPercent + '%'
                });

                if (scrubberOffsetPixels > scrubberWidth - 30) {
                    currentTime.css({
                        opacity: 1 - (((scrubberOffsetPixels - (scrubberWidth - 30)) / 30))
                    });
                } else {
                    if (1 !== Number(currentTime.css('opacity'))) {
                        currentTime.css({
                            opacity: 1
                        });
                    }
                };
            };

            //console.info(_currTime, timeCurr, this.formatTime(timeCurr))
            currentTime.text(new Strings(timeCurr).formatTime());
            duration.text(new Strings(timeTotal).formatTime());

            // notify channel
            for (var i = 0, len = this._channels.length; i < len; i++) {
                if ('undefined' !== typeof(this._channels[i])) {
                    this._channels[i].loop();
                } else {
                    console.log(['DEBUG', this._channels, i]);
                }
            }

            this._runLoop();
        },

        /**
         *
         */
        _runLoop: function() {
            requestAnimFrame($.proxy(function() {
                this.loop();
            }, this));

            return this;
        },

        /**
         *
         */
        activeChannelCleanup: function() {
            if ('undefined' !== typeof(this.getActiveChannel())) {
                this.getActiveChannel().destory();
                console.log(this._channels);
                this._channels.shift();
            }

            return this;
        },

        /**
         *
         */
        pause: function() {
            this.activeChannelCleanup();

            return this;
        },

        /**
         *
         */
        getCtx: function() {
            if (null === this.audioCtx) {
                this.audioCtx = new window.AudioContext;
            }

            return this.audioCtx;
        },

        /**
         * Based on a Fiddle I wrote: http://jsfiddle.net/Fc8Jr/
        **/
        drawSpectrum: function() {
            var frequencyByteData = this.getActiveChannel().frequencyByteData,
                canvas = this._view.getPlayerView().getCanvasReferances();

            var width = canvas.el.active.background.width,
                height = canvas.el.active.background.height;

            var BAR_WIDTH = 3,
                SPACER_WIDTH = 1;

            /* test.. disabled.
            var gradient = canvas.context.active.background.createLinearGradient(0, 0, width, height);
            gradient.addColorStop(1,'#000000');
            gradient.addColorStop(0.75,'#ff0000');
            gradient.addColorStop(0.25,'#ffff00');
            gradient.addColorStop(0,'#ffffff');
            */

            //canvas.el.active.background.width = width;
            canvas.context.active.background.clearRect(0, 0, width, height);
            canvas.context.active.background.fillStyle = '#'+ this._model.spectrum.color.bg;

            canvas.context.active.progress.clearRect(0, 0, width, height);
            canvas.context.active.progress.fillStyle = '#'+ this._model.spectrum.color.progress;

            for (var i = 0, magnitude = 0, len = frequencyByteData.length; i < len; i++) {
                magnitude = Math.ceil(frequencyByteData[i] * height / 256);
                if (2 > magnitude) magnitude = 2;

                canvas.context.active.background.fillRect(i * (SPACER_WIDTH + BAR_WIDTH), height, BAR_WIDTH, -magnitude);
                canvas.context.active.progress.fillRect(i * (SPACER_WIDTH + BAR_WIDTH), height, BAR_WIDTH, -magnitude);

                //canvas.context.active.background.fillRect(i/256 * width, height, width/len, -magnitude);
                //canvas.context.active.progress.fillRect(i/256 * width, height, width/len, -magnitude);
            }

            if (true === this._model.reflection) {
                canvas.context.reflect.background.clearRect(0, 0, width, height);
                canvas.context.reflect.background.drawImage(canvas.el.active.background, 0, 0);

                canvas.context.reflect.progress.clearRect(0, 0, width, height);
                canvas.context.reflect.progress.drawImage(canvas.el.active.progress, 0, 0);
            }
        },

        /**
         *
         */
        startedPlaying: function() {
            console.log('CONTROLLER::startedPlaying fired -- oh well.. started, not sure i want to keep this');
        },

        /**
         *
         */
        getActiveAudio: function() {
            return this.getActiveChannel().audioEl;
        }
    };

    window.AudioContext = window.AudioContext || window.webkitAudioContext;

    window.requestAnimFrame = (function() {
        return window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            window.oRequestAnimationFrame ||
            window.msRequestAnimationFrame ||
            function(callback, el) {
                window.setTimeout(callback, 1000 / 60);
            };
    })();

    return Controller;
});<|endoftext|>


<commit_before>/**
 * Runtime.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/*global ActiveXObject:true */

/**
Defines constructor for Flash runtime.

@class moxie/runtime/flash/Runtime
@private
*/
define("moxie/runtime/flash/Runtime", [
	"moxie/core/utils/Basic",
	"moxie/core/utils/Env",
	"moxie/core/utils/Dom",
	"moxie/core/Exceptions",
	"moxie/runtime/Runtime"
], function(Basic, Env, Dom, x, Runtime) {

	var type = 'flash', extensions = {};

	/**
	Get the version of the Flash Player

	@method getShimVersion
	@private
	@return {Number} Flash Player version
	*/
	function getShimVersion() {
		var version;

		try {
			version = navigator.plugins['Shockwave Flash'];
			version = version.description;
		} catch (e1) {
			try {
				version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
			} catch (e2) {
				version = '0.0';
			}
		}
		version = version.match(/\d+/g);
		return parseFloat(version[0] + '.' + version[1]);
	}

	/**
	Constructor for the Flash Runtime

	@class FlashRuntime
	@extends Runtime
	*/
	function FlashRuntime(options) {
		var I = this, initTimer;

		options = Basic.extend({ swf_url: Env.swf_url }, options);

		Runtime.call(this, options, type, {
			access_binary: function(value) {
				return value && I.mode === 'browser';
			},
			access_image_binary: function(value) {
				return value && I.mode === 'browser';
			},
			display_media: Runtime.capTrue,
			do_cors: Runtime.capTrue,
			drag_and_drop: false,
			report_upload_progress: function() {
				return I.mode === 'client';
			},
			resize_image: Runtime.capTrue,
			return_response_headers: false,
			return_response_type: function(responseType) {
				return !Basic.arrayDiff(responseType, ['', 'text', 'json', 'document']) || I.mode === 'browser';
			},
			return_status_code: function(code) {
				return I.mode === 'browser' || !Basic.arrayDiff(code, [200, 404]);
			},
			select_file: Runtime.capTrue,
			select_multiple: Runtime.capTrue,
			send_binary_string: function(value) {
				return value && I.mode === 'browser';
			},
			send_browser_cookies: function(value) {
				return value && I.mode === 'browser';
			},
			send_custom_headers: function(value) {
				return value && I.mode === 'browser';
			},
			send_multipart: Runtime.capTrue,
			slice_blob: Runtime.capTrue,
			stream_upload: function(value) {
				return value && I.mode === 'browser';
			},
			summon_file_dialog: false,
			upload_filesize: function(size) {
				return Basic.parseSizeStr(size) <= 2097152 || I.mode === 'client';
			},
			use_http_method: function(methods) {
				return !Basic.arrayDiff(methods, ['GET', 'POST']);
			}
		}, {
			// capabilities that require specific mode
			access_binary: function(value) {
				return value ? 'browser' : 'client';
			},
			access_image_binary: function(value) {
				return value ? 'browser' : 'client';
			},
			report_upload_progress: function(value) {
				return value ? 'browser' : 'client';
			},
			return_response_type: function(responseType) {
				return Basic.arrayDiff(responseType, ['', 'text', 'json', 'document']) ? 'browser' : ['client', 'browser'];
			},
			return_status_code: function(code) {
				return Basic.arrayDiff(code, [200, 404]) ? 'browser' : ['client', 'browser'];
			},
			send_binary_string: function(value) {
				return value ? 'browser' : 'client';
			},
			send_browser_cookies: function(value) {
				return value ? 'browser' : 'client';
			},
			send_custom_headers: function(value) {
				return value ? 'browser' : 'client';
			},
			stream_upload: function(value) {
				return value ? 'client' : 'browser';
			},
			upload_filesize: function(size) {
				return Basic.parseSizeStr(size) >= 2097152 ? 'client' : 'browser';
			}
		}, 'client');

		// minimal requirement for Flash Player version
		if (getShimVersion() < 11.3) {
			this.mode = false; // with falsy mode, runtime won't operable, no matter what the mode was before
		}

		Basic.extend(this, {

			getShim: function() {
				return Dom.get(this.uid);
			},

			shimExec: function(component, action) {
				var args = [].slice.call(arguments, 2);
				return I.getShim().exec(this.uid, component, action, args);
			},

			init: function() {
				var html, el, container;

				container = this.getShimContainer();

				// if not the minimal height, shims are not initialized in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)
				Basic.extend(container.style, {
					position: 'absolute',
					top: '-8px',
					left: '-8px',
					width: '9px',
					height: '9px',
					overflow: 'hidden'
				});

				// insert flash object
				html = '<object id="' + this.uid + '" type="application/x-shockwave-flash" data="' +  options.swf_url + '" ';

				if (Env.browser === 'IE') {
					html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
				}

				html += 'width="100%" height="100%" style="outline:0">'  +
					'<param name="movie" value="' + options.swf_url + '" />' +
					'<param name="flashvars" value="uid=' + escape(this.uid) + '&target=' + Env.global_event_dispatcher + '" />' +
					'<param name="wmode" value="transparent" />' +
					'<param name="allowscriptaccess" value="always" />' +
				'</object>';

				if (Env.browser === 'IE') {
					el = document.createElement('div');
					container.appendChild(el);
					el.outerHTML = html;
					el = container = null; // just in case
				} else {
					container.innerHTML = html;
				}

				// Init is dispatched by the shim
				initTimer = setTimeout(function() {
					if (I && !I.initialized) { // runtime might be already destroyed by this moment
						I.trigger("Error", new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR));
					}
				}, 5000);
			},

			destroy: (function(destroy) { // extend default destroy method
				return function() {
					destroy.call(I);
					clearTimeout(initTimer); // initialization check might be still onwait
					options = initTimer = destroy = I = null;
				};
			}(this.destroy))

		}, extensions);
	}

	Runtime.addConstructor(type, FlashRuntime);

	return extensions;
});
<commit_msg>Runtime, Flash: Set default mode depending on the options and requested caps, rather then request it explicitly.<commit_after>/**
 * Runtime.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/*global ActiveXObject:true */

/**
Defines constructor for Flash runtime.

@class moxie/runtime/flash/Runtime
@private
*/
define("moxie/runtime/flash/Runtime", [
	"moxie/core/utils/Basic",
	"moxie/core/utils/Env",
	"moxie/core/utils/Dom",
	"moxie/core/Exceptions",
	"moxie/runtime/Runtime"
], function(Basic, Env, Dom, x, Runtime) {

	var type = 'flash', extensions = {};

	/**
	Get the version of the Flash Player

	@method getShimVersion
	@private
	@return {Number} Flash Player version
	*/
	function getShimVersion() {
		var version;

		try {
			version = navigator.plugins['Shockwave Flash'];
			version = version.description;
		} catch (e1) {
			try {
				version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
			} catch (e2) {
				version = '0.0';
			}
		}
		version = version.match(/\d+/g);
		return parseFloat(version[0] + '.' + version[1]);
	}

	/**
	Constructor for the Flash Runtime

	@class FlashRuntime
	@extends Runtime
	*/
	function FlashRuntime(options) {
		var I = this, initTimer;

		options = Basic.extend({ swf_url: Env.swf_url }, options);

		Runtime.call(this, options, type, {
			access_binary: function(value) {
				return value && I.mode === 'browser';
			},
			access_image_binary: function(value) {
				return value && I.mode === 'browser';
			},
			display_media: Runtime.capTrue,
			do_cors: Runtime.capTrue,
			drag_and_drop: false,
			report_upload_progress: function() {
				return I.mode === 'client';
			},
			resize_image: Runtime.capTrue,
			return_response_headers: false,
			return_response_type: function(responseType) {
				return !Basic.arrayDiff(responseType, ['', 'text', 'json', 'document']) || I.mode === 'browser';
			},
			return_status_code: function(code) {
				return I.mode === 'browser' || !Basic.arrayDiff(code, [200, 404]);
			},
			select_file: Runtime.capTrue,
			select_multiple: Runtime.capTrue,
			send_binary_string: function(value) {
				return value && I.mode === 'browser';
			},
			send_browser_cookies: function(value) {
				return value && I.mode === 'browser';
			},
			send_custom_headers: function(value) {
				return value && I.mode === 'browser';
			},
			send_multipart: Runtime.capTrue,
			slice_blob: Runtime.capTrue,
			stream_upload: function(value) {
				return value && I.mode === 'browser';
			},
			summon_file_dialog: false,
			upload_filesize: function(size) {
				return Basic.parseSizeStr(size) <= 2097152 || I.mode === 'client';
			},
			use_http_method: function(methods) {
				return !Basic.arrayDiff(methods, ['GET', 'POST']);
			}
		}, {
			// capabilities that require specific mode
			access_binary: function(value) {
				return value ? 'browser' : 'client';
			},
			access_image_binary: function(value) {
				return value ? 'browser' : 'client';
			},
			report_upload_progress: function(value) {
				return value ? 'browser' : 'client';
			},
			return_response_type: function(responseType) {
				return Basic.arrayDiff(responseType, ['', 'text', 'json', 'document']) ? 'browser' : ['client', 'browser'];
			},
			return_status_code: function(code) {
				return Basic.arrayDiff(code, [200, 404]) ? 'browser' : ['client', 'browser'];
			},
			send_binary_string: function(value) {
				return value ? 'browser' : 'client';
			},
			send_browser_cookies: function(value) {
				return value ? 'browser' : 'client';
			},
			send_custom_headers: function(value) {
				return value ? 'browser' : 'client';
			},
			stream_upload: function(value) {
				return value ? 'client' : 'browser';
			},
			upload_filesize: function(size) {
				return Basic.parseSizeStr(size) >= 2097152 ? 'client' : 'browser';
			}
		});

		// minimal requirement for Flash Player version
		if (getShimVersion() < 11.3) {
			this.mode = false; // with falsy mode, runtime won't operable, no matter what the mode was before
		}

		Basic.extend(this, {

			getShim: function() {
				return Dom.get(this.uid);
			},

			shimExec: function(component, action) {
				var args = [].slice.call(arguments, 2);
				return I.getShim().exec(this.uid, component, action, args);
			},

			init: function() {
				var html, el, container;

				container = this.getShimContainer();

				// if not the minimal height, shims are not initialized in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)
				Basic.extend(container.style, {
					position: 'absolute',
					top: '-8px',
					left: '-8px',
					width: '9px',
					height: '9px',
					overflow: 'hidden'
				});

				// insert flash object
				html = '<object id="' + this.uid + '" type="application/x-shockwave-flash" data="' +  options.swf_url + '" ';

				if (Env.browser === 'IE') {
					html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
				}

				html += 'width="100%" height="100%" style="outline:0">'  +
					'<param name="movie" value="' + options.swf_url + '" />' +
					'<param name="flashvars" value="uid=' + escape(this.uid) + '&target=' + Env.global_event_dispatcher + '" />' +
					'<param name="wmode" value="transparent" />' +
					'<param name="allowscriptaccess" value="always" />' +
				'</object>';

				if (Env.browser === 'IE') {
					el = document.createElement('div');
					container.appendChild(el);
					el.outerHTML = html;
					el = container = null; // just in case
				} else {
					container.innerHTML = html;
				}

				// Init is dispatched by the shim
				initTimer = setTimeout(function() {
					if (I && !I.initialized) { // runtime might be already destroyed by this moment
						I.trigger("Error", new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR));
					}
				}, 5000);
			},

			destroy: (function(destroy) { // extend default destroy method
				return function() {
					destroy.call(I);
					clearTimeout(initTimer); // initialization check might be still onwait
					options = initTimer = destroy = I = null;
				};
			}(this.destroy))

		}, extensions);
	}

	Runtime.addConstructor(type, FlashRuntime);

	return extensions;
});
<|endoftext|>


<commit_before>import { dispatch, dispatchAsync } from '../dispatcher/Dispatcher';
import ActionTypes from '../constants/ActionTypes';
import AuthService from '../services/AuthService';

export default {

    validateInvitation: (token) => {
        let promise = AuthService.validateInvitation(token);
        dispatchAsync(promise, {
            request: ActionTypes.REQUEST_VALIDATE_INVITATION_USER,
            success: ActionTypes.REQUEST_VALIDATE_INVITATION_USER_SUCCESS,
            failure: ActionTypes.REQUEST_VALIDATE_INVITATION_USER_ERROR
        }, {token});
    },

    validateUsername: (username) => {
        let promise = AuthService.validateUsername(username);
        dispatchAsync(promise, {
            request: ActionTypes.REQUEST_VALIDATE_USERNAME,
            success: ActionTypes.REQUEST_VALIDATE_USERNAME_SUCCESS,
            failure: ActionTypes.REQUEST_VALIDATE_USERNAME_ERROR
        }, {username});
    },

    connectRegister: (token, resource, accessToken, resourceId, expireTime, profile) => {
        dispatch(ActionTypes.CONNECT_REGISTER_ACCOUNT, {
            token,
            resource,
            accessToken,
            resourceId,
            expireTime,
            profile
        });
    },

    connect: (resource, accessToken, resourceId, expireTime, refreshToken) => {
        let promise = AuthService.connect(resource, accessToken, resourceId, expireTime, refreshToken);
        dispatchAsync(promise, {
            request: ActionTypes.CONNECT_ACCOUNT,
            success: ActionTypes.CONNECT_ACCOUNT_SUCCESS,
            failure: ActionTypes.CONNECT_ACCOUNT_ERROR
        }, {resource, accessToken, resourceId, expireTime});
        return promise;
    }
}
<commit_msg>QS-1056: add refreshToken when dispatching action<commit_after>import { dispatch, dispatchAsync } from '../dispatcher/Dispatcher';
import ActionTypes from '../constants/ActionTypes';
import AuthService from '../services/AuthService';

export default {

    validateInvitation: (token) => {
        let promise = AuthService.validateInvitation(token);
        dispatchAsync(promise, {
            request: ActionTypes.REQUEST_VALIDATE_INVITATION_USER,
            success: ActionTypes.REQUEST_VALIDATE_INVITATION_USER_SUCCESS,
            failure: ActionTypes.REQUEST_VALIDATE_INVITATION_USER_ERROR
        }, {token});
    },

    validateUsername: (username) => {
        let promise = AuthService.validateUsername(username);
        dispatchAsync(promise, {
            request: ActionTypes.REQUEST_VALIDATE_USERNAME,
            success: ActionTypes.REQUEST_VALIDATE_USERNAME_SUCCESS,
            failure: ActionTypes.REQUEST_VALIDATE_USERNAME_ERROR
        }, {username});
    },

    connectRegister: (token, resource, accessToken, resourceId, expireTime, profile) => {
        dispatch(ActionTypes.CONNECT_REGISTER_ACCOUNT, {
            token,
            resource,
            accessToken,
            resourceId,
            expireTime,
            profile
        });
    },

    connect: (resource, accessToken, resourceId, expireTime, refreshToken) => {
        let promise = AuthService.connect(resource, accessToken, resourceId, expireTime, refreshToken);
        dispatchAsync(promise, {
            request: ActionTypes.CONNECT_ACCOUNT,
            success: ActionTypes.CONNECT_ACCOUNT_SUCCESS,
            failure: ActionTypes.CONNECT_ACCOUNT_ERROR
        }, {resource, accessToken, resourceId, expireTime, refreshToken});
        return promise;
    }
}
<|endoftext|>