<commit_before>/**
 *  Startup or transition into Handshake game
 *  o Start ambient sound
 *   Handshake interaction:
 *  o play handshake play
 *  o stop ambient sound
 *   Mole Game:
 *  We have 3 sounds
 *  1) Light activated: Panel comes on with 'neutral' color using ACTIVE_PANEL_INTENSITY
 *  2) Success interaction: panel turn active and state == TrackedPanels.STATE_IGNORED
 *  3) Fail interaction: panel turn active and state == TrackedPanels.STATE_OFF
 *  Q: Sound on light deactivated? (similar to 1) but opposite)
 *   Success animation
 *  o Play Success sound (missing)
 *   Transition animation to Disk Game
 *  o For each light on event, play lighteffect sound (4 events)
 *  o FIXME: Some sort of demo/helper to clarify interaction
 *   Disk Game
 *  o Start ambient sound (FIXME: Fading vs. loop, new sound from Alain?)
 *  o Start loop sound (loop)
 *  o Start distance sound (loop)
 *  o Calculate distance from success (positive and negative distance)
 *    -> use distance to modulate pitch of distance sound
 *  Level success animation
 *  o On success of level 1 and 2, play success sound
 *  o On perimeter light on event, play lighteffect sound
 *   Success animation
 *  o On success of level 3, play show sound
 *  o For each light off event, play lighteffect sound (3 events)
 *   Transition animation to Simon Game
 *  o No sounds
 *   Simon Game
 *  o On animation playback, play panel sounds
 *  o On correct local interaction, play panel sounds
 *  o On free play, play panel sounds
 *  o On fail local interaction, play failure sound
 *  o On level success, play success sound
 *   Light show and transition back to Start State
 *  o play show sound
 */

import _ from 'lodash';

import SculptureStore from '../game-logic/sculpture-store';
import HandshakeGameLogic from '../game-logic/logic/handshake-game-logic';
import GAMES from '../game-logic/constants/games';
import TrackedPanels from '../game-logic/utils/tracked-panels';
import Disk from '../game-logic/utils/disk';
import * as AudioAPI from './audio-api';
const {Sound, VCFSound} = AudioAPI;

export default class AudioView {
  constructor(store, config) {
    this.store = store;
    this.config = config;
    AudioAPI.init();
  }

  reset() {
  }

  /**
   * Loads all sounds.
   * @param {function(err)} callback Called when done
   */
  load(callback) {
    let lfoFreq = 1/3;
    if (this.config.hasOwnProperty('HANDSHAKE_HARDWARE') &&
        this.config.HANDSHAKE_HARDWARE.hasOwnProperty('PULSE_DELAY')) {
      lfoFreq = 1000/this.config.HANDSHAKE_HARDWARE.PULSE_DELAY;
    }

    // Maps logical sound identifiers to filenames. We'll load these sounds next.
    this.sounds = {
      alone: {
        ambient: new VCFSound({url: 'sounds/Alone_Mode/Pulse_Amb_Loop.wav', lfoFreq, fadeIn: 3, gain: 0.4}),
        handshake: new Sound({url: 'sounds/Alone_Mode/Hand_Shake_01.wav'})
      },
      mole: {
        success: new Sound({url: 'sounds/Game_01/G01_Success_01.wav', gain: 0.5}),
        failure: new Sound({url: 'sounds/Game_01/G01_Negative_01.wav', gain: 0.5}),
        panels: [0, 1, 2].map(stripId => _.range(10).map(panelId => new Sound({url: `sounds/Game_01/G01_LED_${("0"+(stripId*10+panelId+1)).slice(-2)}.wav`, gain: 0.33})))
      },
      disk: {
        ambient: new Sound({url: 'sounds/Game_02/G02_Amb_Breath_Loop_01.wav', loop: true}),
        disk2: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_C2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        disk1: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_Eb2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        disk0: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_G2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        lighteffect: new Sound({url: 'sounds/Game_02/G02_Lights_01.wav'}),
        success: new Sound({url: 'sounds/Game_02/G02_Success_01.wav'}),
        show: new Sound({url: 'sounds/Game_02/G02_Success_final_01.wav', gain: 0.5}),
        shadowTransition: new Sound({url: 'sounds/Game_02/G02_Lights_01.wav', gain: 2.0}),
      },
      simon: {
        panels: [0, 1, 2].map(stripId => _.range(10).map(panelId => new Sound({url: `sounds/Game_03/G03_LED_${("0"+(stripId*10+panelId+1)).slice(-2)}.wav`, gain: 0.5}))),
        success: new Sound({url: 'sounds/Game_03/G03_Success_01.wav', gain: 0.5}),
        failure: new Sound({url: 'sounds/Game_03/G03_Negative_01.wav', gain: 0.5}),
        show: new Sound({url: 'sounds/Game_03/G03_Light_Show_01.wav', gain: 1})
      }
    };

    // Traverses this.sounds and replace the filenames with valid sound objects.
    // Populates this._promises
    this._promises = [];
    this._traverse(this.sounds, this._promises);

    // _traverse() will create promises. We call the callback once all promises resolve
    console.log(`${this._promises.length} promises created`);
    Promise.all(this._promises)
      // Don't listen to events until we've loaded all sounds
      .then(() => {
        this.store.on(SculptureStore.EVENT_CHANGE, this._handleChanges.bind(this));
        this.store.on(SculptureStore.EVENT_LOCAL_CHANGE, this._handleLocalChanges.bind(this));
      })
      .then(() => callback(null))
      .catch((reason) => callback(reason));
  }

  /*
   * Traverses sound config objects and replaces nodes with valid, loaded, sounds
   * populates the given promises array with promises of loaded sounds
   */
  _traverse(soundconf, promises) {
    for (let key in soundconf) {
      const value = soundconf[key];
      let sound;
      if (typeof value === 'string') sound = soundconf[key] = new Sound({url: value});
      else if (value instanceof Sound) sound = value;

      if (sound) promises.push(sound.load());
      else this._traverse(value, promises);
    }
  }

  _handleChanges(changes) {
    if (this.store.isPlayingHandshakeGame) this._handleHandshakeGame(changes);
    if (this.store.isPlayingMoleGame) this._handleMoleGame(changes);
    if (this.store.isPlayingDiskGame) this._handleDiskGame(changes);
    if (this.store.isPlayingSimonGame) this._handleSimonGame(changes);
  }

  _handleLocalChanges() {
    if (this.store.isPlayingDiskGame) this._handleLocalDiskGame();
  }

  _handleHandshakeGame(changes) {
    // On startup, or when Start State becomes active, play ambient sound
    if (changes.currentGame === GAMES.HANDSHAKE) this.sounds.alone.ambient.play();

    if (changes.handshake && changes.handshake.state === HandshakeGameLogic.STATE_ACTIVATING) {
      // FIXME: Determine volume based on if _our_ hand initiated the handshake
//      if (changes.handshakes[this.store.me]) -> max volume, else low volume
//      gain: (stripId === simongame.currentStrip) ? 1 : 0.1

      this.sounds.alone.ambient.stop();
      this.sounds.alone.handshake.play();
    }
  }

  _handleMoleGame(changes) {
    const lightChanges = changes.lights;
    if (!lightChanges || !this.store.isReady) return;

    /*
      Mole game sounds:
      o If a panel got activated (changes.lights.<stripId>.panels.<panelId>.active === true)
      changes.mole.panels.<id> == STATE_IGNORED: Just turned -> success?
      state.mole.panels.<id> == STATE_OFF: failure
    */
    // If a panel got activated (changes.lights.<stripId>.panels.<panelId>.active === true)
    for (let stripId in lightChanges) {
      for (let panelId in lightChanges[stripId].panels) {
        const panelChange = lightChanges[stripId].panels[panelId];
        // FIXME: To get correct multi-player sounds, we should listen only for STATE changes,
        // as the active flag might not be correctly set
        if (panelChange.active === true) {
          const panelkey = `${stripId},${panelId}`;
          if (changes.mole && changes.mole.panels) {
            const state = changes.mole.panels[panelkey];
            if (state === TrackedPanels.STATE_IGNORED) { // Panel was turned -> success
              this.sounds.mole.success.play();
            }
          }
          else {
            if (this.config.MOLE_GAME.ENABLE_FAILURE_SOUND) {
              const state = this.store.data.get('mole').get('panels').get(panelkey);
              if (!state || state === TrackedPanels.STATE_OFF) {
                this.sounds.mole.failure.play();
              }
            }
          }
        }
        else if (panelChange.intensity > 90) {
          this.sounds.mole.panels[stripId][panelId].play();
        }
      }
    }
  }

  _handleDiskGame(changes) {
    if (changes.disk && changes.disk.hasOwnProperty('winning')) {
      console.log(`Sound Winning: ${changes.disk.winning}`);
    }
    if (changes.disk && changes.disk.hasOwnProperty('winning') && changes.disk.winning) {
      // End of game
      if (this.store.data.get('disk').get('level') >= this.config.DISK_GAME.LEVELS.length) {
        this.sounds.disk.disk0.stop();
        this.sounds.disk.disk1.stop();
        this.sounds.disk.disk2.stop();
        this.sounds.disk.show.play();
      }
      // End of level
      else {
        this.sounds.disk.success.play();
      }
    }

    // On start of disk game
    // FIXME: Is transition part of the disk game?
    if (changes.currentGame === GAMES.DISK) {
      // Start all sounds in silent mode
      this.sounds.disk.disk0.play({gain: 0});
      this.sounds.disk.disk1.play({gain: 0});
      this.sounds.disk.disk2.play({gain: 0});
    }

    // On start of level
//    if (changes.hasOwnProperty('disk') &&
//        changes.disk.hasOwnProperty('level') &&
//        changes.disk.level < this.config.DISK_GAME.LEVELS.length) {
//    }

    if (changes.disk && changes.disk.disks) {
      const disksChanges = changes.disk.disks;
      const disks = this.store.data.get('disk').get('disks');

      for (let disk of ['disk0', 'disk1', 'disk2']) {
        if (disksChanges.hasOwnProperty(disk) &&
            disksChanges[disk].hasOwnProperty('targetSpeed')) {
          if (disksChanges[disk].targetSpeed === 0) this.sounds.disk[disk].fadeOut();
          else this.sounds.disk[disk].fadeIn();
        }
      }

    }
    // FIXME level success and final success

    // Shadow transition sound
    const lightChanges = changes.lights;
    const stripId = '6';
    if (lightChanges && lightChanges.hasOwnProperty(stripId)) {
      this.sounds.disk.shadowTransition.play();
    }
  }

  _handleLocalDiskGame() {
    const diskgame = this.store.currentGameLogic;
    for (let diskId of ['disk0', 'disk1', 'disk2']) {
      const err = diskgame.getDiskError(diskId);
      const pulseFreq = this._calcFreq(err);
      if (this.sounds.disk[diskId].source) {
        this.sounds.disk[diskId].source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      }
    }
//      for (let disk of ['disk0', 'disk1', 'disk2']) {
//        const score = diskgame.getDiskScore(disk);
//        const pulseFreq = this._calcSingleFreq(score);
//        if (this.sounds.disk[disk].source) this.sounds.disk[disk].source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
//      }
      /* code for global score sound
      const score = diskgame.getScore(disks);
      //console.log(`score: ${score}`);
      // score = 0 -> 540
      const pulseFreq = this._calcFreq(score);
      // FIXME: Set our loopFreq property instead and deal with audio node stuff internally
      //console.log(`pulseFreq: ${pulseFreq}`);
      if (this.sounds.disk.disk0.source) this.sounds.disk.disk0.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      if (this.sounds.disk.disk1.source) this.sounds.disk.disk1.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      if (this.sounds.disk.disk2.source) this.sounds.disk.disk2.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      */
  }

  _calcSingleFreq(score) {
    function map(value, inMin, inMax, outMin, outMax) {
      return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
    }
    if (score > 45) {
      return map(score, 180, 45, 0.5, 3);
    }
    else {
      return map(score, 45, 0, 3, 10);
    }
  }

  _calcFreq(score) {
    function map(value, inMin, inMax, outMin, outMax) {
      return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
    }
    if (score > 200) {
      return map(score, 540, 200, 0.5, 1);
    }
    else if (score > 100) {
      return map(score, 200, 100, 1, 4);
    }
    else if (score > 50) {
      return map(score, 100, 50, 4, 10);
    }
    else {
      return map(score, 50, 0, 10, 32);
    }
  }

  _handleSimonGame(changes) {
    const simongame = this.store.currentGameLogic;
    if (changes.status === SculptureStore.STATUS_SUCCESS) {
      if (simongame.complete) this.sounds.simon.show.play();
      else this.sounds.simon.success.play();
    }
    if (changes.status === SculptureStore.STATUS_FAILURE) this.sounds.simon.failure.play();

    const lightChanges = changes.lights;
    if (!lightChanges || !this.store.isReady) return;

    // FIXME: This is a hack to support lower volume on non-primary panel
    for (let stripId in lightChanges) {
      if (this.config.GAME_STRIPS.includes(stripId)) {
        for (let panelId in lightChanges[stripId].panels) {
          const panelChange = lightChanges[stripId].panels[panelId];
          if (panelChange.active || panelChange.intensity > 90) {
            this.sounds.simon.panels[stripId][panelId].play({gain: (stripId === simongame.currentStrip) ? 1 : 0.1});
          }
        }
      }
    }
  }
}
<commit_msg>Simplified and improved mole game sounds, use different sound for the last panel<commit_after>/**
 *  Startup or transition into Handshake game
 *  o Start ambient sound
 *   Handshake interaction:
 *  o play handshake play
 *  o stop ambient sound
 *   Mole Game:
 *  We have 3 sounds
 *  1) Light activated: Panel comes on with 'neutral' color using ACTIVE_PANEL_INTENSITY
 *  2) Success interaction: panel turn active and state == TrackedPanels.STATE_IGNORED
 *  3) Fail interaction: panel turn active and state == TrackedPanels.STATE_OFF
 *  Q: Sound on light deactivated? (similar to 1) but opposite)
 *   Success animation
 *  o Play Success sound (missing)
 *   Transition animation to Disk Game
 *  o For each light on event, play lighteffect sound (4 events)
 *  o FIXME: Some sort of demo/helper to clarify interaction
 *   Disk Game
 *  o Start ambient sound (FIXME: Fading vs. loop, new sound from Alain?)
 *  o Start loop sound (loop)
 *  o Start distance sound (loop)
 *  o Calculate distance from success (positive and negative distance)
 *    -> use distance to modulate pitch of distance sound
 *  Level success animation
 *  o On success of level 1 and 2, play success sound
 *  o On perimeter light on event, play lighteffect sound
 *   Success animation
 *  o On success of level 3, play show sound
 *  o For each light off event, play lighteffect sound (3 events)
 *   Transition animation to Simon Game
 *  o No sounds
 *   Simon Game
 *  o On animation playback, play panel sounds
 *  o On correct local interaction, play panel sounds
 *  o On free play, play panel sounds
 *  o On fail local interaction, play failure sound
 *  o On level success, play success sound
 *   Light show and transition back to Start State
 *  o play show sound
 */

import _ from 'lodash';

import SculptureStore from '../game-logic/sculpture-store';
import HandshakeGameLogic from '../game-logic/logic/handshake-game-logic';
import GAMES from '../game-logic/constants/games';
import TrackedPanels from '../game-logic/utils/tracked-panels';
import Disk from '../game-logic/utils/disk';
import * as AudioAPI from './audio-api';
const {Sound, VCFSound} = AudioAPI;

export default class AudioView {
  constructor(store, config) {
    this.store = store;
    this.config = config;
    AudioAPI.init();
  }

  reset() {
  }

  /**
   * Loads all sounds.
   * @param {function(err)} callback Called when done
   */
  load(callback) {
    let lfoFreq = 1/3;
    if (this.config.hasOwnProperty('HANDSHAKE_HARDWARE') &&
        this.config.HANDSHAKE_HARDWARE.hasOwnProperty('PULSE_DELAY')) {
      lfoFreq = 1000/this.config.HANDSHAKE_HARDWARE.PULSE_DELAY;
    }

    // Maps logical sound identifiers to filenames. We'll load these sounds next.
    this.sounds = {
      alone: {
        ambient: new VCFSound({url: 'sounds/Alone_Mode/Pulse_Amb_Loop.wav', lfoFreq, fadeIn: 3, gain: 0.4}),
        handshake: new Sound({url: 'sounds/Alone_Mode/Hand_Shake_01.wav'})
      },
      mole: {
        success: new Sound({url: 'sounds/Game_01/G01_Success_01.wav', gain: 0.5}),
        failure: new Sound({url: 'sounds/Game_01/G01_Negative_01.wav', gain: 0.5}),
        panels: [0, 1, 2].map(stripId => _.range(10).map(panelId => new Sound({url: `sounds/Game_01/G01_LED_${("0"+(stripId*10+panelId+1)).slice(-2)}.wav`, gain: 0.33})))
      },
      disk: {
        ambient: new Sound({url: 'sounds/Game_02/G02_Amb_Breath_Loop_01.wav', loop: true}),
        disk2: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_C2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        disk1: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_Eb2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        disk0: new Sound({url: 'sounds/Game_02/G02_Disk_Loop_G2.wav', loop: true, gain: 0.3, fadeIn: 2}),
        lighteffect: new Sound({url: 'sounds/Game_02/G02_Lights_01.wav'}),
        success: new Sound({url: 'sounds/Game_02/G02_Success_01.wav'}),
        show: new Sound({url: 'sounds/Game_02/G02_Success_final_01.wav', gain: 0.5}),
        shadowTransition: new Sound({url: 'sounds/Game_02/G02_Lights_01.wav', gain: 2.0}),
      },
      simon: {
        panels: [0, 1, 2].map(stripId => _.range(10).map(panelId => new Sound({url: `sounds/Game_03/G03_LED_${("0"+(stripId*10+panelId+1)).slice(-2)}.wav`, gain: 0.5}))),
        success: new Sound({url: 'sounds/Game_03/G03_Success_01.wav', gain: 0.5}),
        failure: new Sound({url: 'sounds/Game_03/G03_Negative_01.wav', gain: 0.5}),
        show: new Sound({url: 'sounds/Game_03/G03_Light_Show_01.wav', gain: 1})
      }
    };

    // Traverses this.sounds and replace the filenames with valid sound objects.
    // Populates this._promises
    this._promises = [];
    this._traverse(this.sounds, this._promises);

    // _traverse() will create promises. We call the callback once all promises resolve
    console.log(`${this._promises.length} promises created`);
    Promise.all(this._promises)
      // Don't listen to events until we've loaded all sounds
      .then(() => {
        this.store.on(SculptureStore.EVENT_CHANGE, this._handleChanges.bind(this));
        this.store.on(SculptureStore.EVENT_LOCAL_CHANGE, this._handleLocalChanges.bind(this));
      })
      .then(() => callback(null))
      .catch((reason) => callback(reason));
  }

  /*
   * Traverses sound config objects and replaces nodes with valid, loaded, sounds
   * populates the given promises array with promises of loaded sounds
   */
  _traverse(soundconf, promises) {
    for (let key in soundconf) {
      const value = soundconf[key];
      let sound;
      if (typeof value === 'string') sound = soundconf[key] = new Sound({url: value});
      else if (value instanceof Sound) sound = value;

      if (sound) promises.push(sound.load());
      else this._traverse(value, promises);
    }
  }

  _handleChanges(changes) {
    if (this.store.isPlayingHandshakeGame) this._handleHandshakeGame(changes);
    if (this.store.isPlayingMoleGame) this._handleMoleGame(changes);
    if (this.store.isPlayingDiskGame) this._handleDiskGame(changes);
    if (this.store.isPlayingSimonGame) this._handleSimonGame(changes);
  }

  _handleLocalChanges() {
    if (this.store.isPlayingDiskGame) this._handleLocalDiskGame();
  }

  _handleHandshakeGame(changes) {
    // On startup, or when Start State becomes active, play ambient sound
    if (changes.currentGame === GAMES.HANDSHAKE) this.sounds.alone.ambient.play();

    if (changes.handshake && changes.handshake.state === HandshakeGameLogic.STATE_ACTIVATING) {
      // FIXME: Determine volume based on if _our_ hand initiated the handshake
//      if (changes.handshakes[this.store.me]) -> max volume, else low volume
//      gain: (stripId === simongame.currentStrip) ? 1 : 0.1

      this.sounds.alone.ambient.stop();
      this.sounds.alone.handshake.play();
    }
  }

  _handleMoleGame(changes) {
    const lightChanges = changes.lights;
    const moleChanges = changes.mole;
    if (!lightChanges && !moleChanges || !this.store.isReady) return;

    const molegame = this.store.currentGameLogic;

    /*
      Mole game sounds:
      o If a panel got activated (changes.lights.<stripId>.panels.<panelId>.active === true)
      changes.mole.panels.<id> == STATE_IGNORED: Just turned -> success?
      state.mole.panels.<id> == STATE_OFF: failure
    */

    if (moleChanges && moleChanges.panelCount > 0) {
      if (moleChanges.panelCount === this.config.MOLE_GAME.GAME_END) {
        this.sounds.simon.success.play();
      }
      else {
        this.sounds.mole.success.play();
      }
    }

    // If a panel got activated (changes.lights.<stripId>.panels.<panelId>.active === true)
    for (let stripId in lightChanges) {
      for (let panelId in lightChanges[stripId].panels) {
        const panelChange = lightChanges[stripId].panels[panelId];
        // FIXME: To get correct multi-player sounds, we should listen only for STATE changes,
        // as the active flag might not be correctly set
        if (panelChange.active === false && panelChange.intensity > 90) {
          this.sounds.mole.panels[stripId][panelId].play();
        }
      }
    }
  }

  _handleDiskGame(changes) {
    if (changes.disk && changes.disk.hasOwnProperty('winning')) {
      console.log(`Sound Winning: ${changes.disk.winning}`);
    }
    if (changes.disk && changes.disk.hasOwnProperty('winning') && changes.disk.winning) {
      // End of game
      if (this.store.data.get('disk').get('level') >= this.config.DISK_GAME.LEVELS.length) {
        this.sounds.disk.disk0.stop();
        this.sounds.disk.disk1.stop();
        this.sounds.disk.disk2.stop();
        this.sounds.disk.show.play();
      }
      // End of level
      else {
        this.sounds.disk.success.play();
      }
    }

    // On start of disk game
    // FIXME: Is transition part of the disk game?
    if (changes.currentGame === GAMES.DISK) {
      // Start all sounds in silent mode
      this.sounds.disk.disk0.play({gain: 0});
      this.sounds.disk.disk1.play({gain: 0});
      this.sounds.disk.disk2.play({gain: 0});
    }

    // On start of level
//    if (changes.hasOwnProperty('disk') &&
//        changes.disk.hasOwnProperty('level') &&
//        changes.disk.level < this.config.DISK_GAME.LEVELS.length) {
//    }

    if (changes.disk && changes.disk.disks) {
      const disksChanges = changes.disk.disks;
      const disks = this.store.data.get('disk').get('disks');

      for (let disk of ['disk0', 'disk1', 'disk2']) {
        if (disksChanges.hasOwnProperty(disk) &&
            disksChanges[disk].hasOwnProperty('targetSpeed')) {
          if (disksChanges[disk].targetSpeed === 0) this.sounds.disk[disk].fadeOut();
          else this.sounds.disk[disk].fadeIn();
        }
      }

    }
    // FIXME level success and final success

    // Shadow transition sound
    const lightChanges = changes.lights;
    const stripId = '6';
    if (lightChanges && lightChanges.hasOwnProperty(stripId)) {
      this.sounds.disk.shadowTransition.play();
    }
  }

  _handleLocalDiskGame() {
    const diskgame = this.store.currentGameLogic;
    for (let diskId of ['disk0', 'disk1', 'disk2']) {
      const err = diskgame.getDiskError(diskId);
      const pulseFreq = this._calcFreq(err);
      if (this.sounds.disk[diskId].source) {
        this.sounds.disk[diskId].source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      }
    }
//      for (let disk of ['disk0', 'disk1', 'disk2']) {
//        const score = diskgame.getDiskScore(disk);
//        const pulseFreq = this._calcSingleFreq(score);
//        if (this.sounds.disk[disk].source) this.sounds.disk[disk].source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
//      }
      /* code for global score sound
      const score = diskgame.getScore(disks);
      //console.log(`score: ${score}`);
      // score = 0 -> 540
      const pulseFreq = this._calcFreq(score);
      // FIXME: Set our loopFreq property instead and deal with audio node stuff internally
      //console.log(`pulseFreq: ${pulseFreq}`);
      if (this.sounds.disk.disk0.source) this.sounds.disk.disk0.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      if (this.sounds.disk.disk1.source) this.sounds.disk.disk1.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      if (this.sounds.disk.disk2.source) this.sounds.disk.disk2.source.loopEnd = (pulseFreq === 0 ? 0 : 1/pulseFreq);
      */
  }

  _calcSingleFreq(score) {
    function map(value, inMin, inMax, outMin, outMax) {
      return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
    }
    if (score > 45) {
      return map(score, 180, 45, 0.5, 3);
    }
    else {
      return map(score, 45, 0, 3, 10);
    }
  }

  _calcFreq(score) {
    function map(value, inMin, inMax, outMin, outMax) {
      return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
    }
    if (score > 200) {
      return map(score, 540, 200, 0.5, 1);
    }
    else if (score > 100) {
      return map(score, 200, 100, 1, 4);
    }
    else if (score > 50) {
      return map(score, 100, 50, 4, 10);
    }
    else {
      return map(score, 50, 0, 10, 32);
    }
  }

  _handleSimonGame(changes) {
    const simongame = this.store.currentGameLogic;
    if (changes.status === SculptureStore.STATUS_SUCCESS) {
      if (simongame.complete) this.sounds.simon.show.play();
      else this.sounds.simon.success.play();
    }
    if (changes.status === SculptureStore.STATUS_FAILURE) this.sounds.simon.failure.play();

    const lightChanges = changes.lights;
    if (!lightChanges || !this.store.isReady) return;

    // FIXME: This is a hack to support lower volume on non-primary panel
    for (let stripId in lightChanges) {
      if (this.config.GAME_STRIPS.includes(stripId)) {
        for (let panelId in lightChanges[stripId].panels) {
          const panelChange = lightChanges[stripId].panels[panelId];
          if (panelChange.active || panelChange.intensity > 90) {
            this.sounds.simon.panels[stripId][panelId].play({gain: (stripId === simongame.currentStrip) ? 1 : 0.1});
          }
        }
      }
    }
  }
}
<|endoftext|>


<commit_before>import { hierarchy,
         pack } from 'd3-hierarchy';
import { scaleOrdinal,
         schemeCategory20 } from 'd3-scale';
import { select } from 'd3-selection';
import { transition } from 'd3-transition';

import VisComponent from 'candela/VisComponent';

import content from './index.jade';

export default class Bubble extends VisComponent {
  constructor (el, options) {
    super(el, options);

    // Respond to "add" and "delete" events from the data window; these are all
    // that will be needed to update the hierarchy data.
    options.dataWindow.on('added', d => this.add(d));
    options.dataWindow.on('deleted', d => this.remove(d));

    // Retain a function that specifies what the interval is between data
    // updates.
    this.interval = options.interval || (() => 0);

    // Construct an initial hierarchy.
    this.data = {
      children: [
        {
          cluster: 'non-anomalous',
          value: 0
        },
        {
          cluster: 'anomalous',
          value: 0,
          children: []
        }
      ]
    };

    select(this.el)
      .html(content({
        width: '620.5px',
        height: '400px'
      }));
  }

  render () {
    let bubbles = pack()
      .size([620.5, 400])
      .padding(3);

    let root = hierarchy(this.data)
      .sum(d => d.value || 0.1);

    bubbles(root);

    const color = scaleOrdinal(schemeCategory20);

    select(this.el)
      .select('svg')
      .selectAll('g')
      .data(root.descendants())
      .enter()
      .append('g')
      .append('circle')
      .style('stroke-width', '1px')
      .style('stroke', 'black');

    const t = transition()
      .duration(this.interval());

    select(this.el)
      .select('svg')
      .selectAll('g')
      .transition(t)
      .attr('transform', d => `translate(${d.x}, ${d.y})`);

    select(this.el)
      .select('svg')
      .selectAll('g')
      .select('circle')
      .transition(t)
      .attr('r', d => d.r)
      .style('fill', (d, i) => {
        if (d.depth === 0) {
          return 'lightgray';
        } else if (d.depth === 1) {
          return 'gray';
        } else {
          return color(d.data.cluster);
        }
      });
  }

  add (d) {
    if (d.anomalous) {
      if (this.getCluster(d.cluster) === undefined) {
        this.makeCluster(d.cluster);
      }

      this.incrementCluster(d.cluster);
    } else {
      this.incrementNormal();
    }
  }

  remove (d) {
    if (d.anomalous) {
      this.decrementCluster(d.cluster);
    } else {
      this.decrementNormal();
    }
  }

  incrementNormal () {
    this.data.children[0].value++;
  }

  decrementNormal () {
    this.data.children[0].value--;
  }

  getCluster (which) {
    return this.data.children[1].children[which];
  }

  makeCluster (which) {
    this.data.children[1].children[which] = {
      cluster: which,
      value: 0
    };
  }

  incrementCluster (which) {
    this.data.children[1].children[which].value++;
  }

  decrementCluster (which) {
    this.data.children[1].children[which].value--;
  }
}
<commit_msg>Use a linear easing function for smoother animation<commit_after>import { easeLinear } from 'd3-ease';
import { hierarchy,
         pack } from 'd3-hierarchy';
import { scaleOrdinal,
         schemeCategory20 } from 'd3-scale';
import { select } from 'd3-selection';
import { transition } from 'd3-transition';

import VisComponent from 'candela/VisComponent';

import content from './index.jade';

export default class Bubble extends VisComponent {
  constructor (el, options) {
    super(el, options);

    // Respond to "add" and "delete" events from the data window; these are all
    // that will be needed to update the hierarchy data.
    options.dataWindow.on('added', d => this.add(d));
    options.dataWindow.on('deleted', d => this.remove(d));

    // Retain a function that specifies what the interval is between data
    // updates.
    this.interval = options.interval || (() => 0);

    // Construct an initial hierarchy.
    this.data = {
      children: [
        {
          cluster: 'non-anomalous',
          value: 0
        },
        {
          cluster: 'anomalous',
          value: 0,
          children: []
        }
      ]
    };

    select(this.el)
      .html(content({
        width: '620.5px',
        height: '400px'
      }));
  }

  render () {
    let bubbles = pack()
      .size([620.5, 400])
      .padding(3);

    let root = hierarchy(this.data)
      .sum(d => d.value || 0.1);

    bubbles(root);

    const color = scaleOrdinal(schemeCategory20);

    select(this.el)
      .select('svg')
      .selectAll('g')
      .data(root.descendants())
      .enter()
      .append('g')
      .append('circle')
      .style('stroke-width', '1px')
      .style('stroke', 'black');

    const t = transition()
      .duration(this.interval())
      .ease(easeLinear);

    select(this.el)
      .select('svg')
      .selectAll('g')
      .transition(t)
      .attr('transform', d => `translate(${d.x}, ${d.y})`);

    select(this.el)
      .select('svg')
      .selectAll('g')
      .select('circle')
      .transition(t)
      .attr('r', d => d.r)
      .style('fill', (d, i) => {
        if (d.depth === 0) {
          return 'lightgray';
        } else if (d.depth === 1) {
          return 'gray';
        } else {
          return color(d.data.cluster);
        }
      });
  }

  add (d) {
    if (d.anomalous) {
      if (this.getCluster(d.cluster) === undefined) {
        this.makeCluster(d.cluster);
      }

      this.incrementCluster(d.cluster);
    } else {
      this.incrementNormal();
    }
  }

  remove (d) {
    if (d.anomalous) {
      this.decrementCluster(d.cluster);
    } else {
      this.decrementNormal();
    }
  }

  incrementNormal () {
    this.data.children[0].value++;
  }

  decrementNormal () {
    this.data.children[0].value--;
  }

  getCluster (which) {
    return this.data.children[1].children[which];
  }

  makeCluster (which) {
    this.data.children[1].children[which] = {
      cluster: which,
      value: 0
    };
  }

  incrementCluster (which) {
    this.data.children[1].children[which].value++;
  }

  decrementCluster (which) {
    this.data.children[1].children[which].value--;
  }
}
<|endoftext|>


<commit_before>const test = require('tape')
const MinHeap = require('../DataStructures/MinHeap')
/**
 * Find k max elements from given input array.
 *
 * @param {array} input
 * @param {number} k
 * @return {array}
 */
function findMaxElements(input, k) {
  const heap = new MinHeap()

  for (let i = 0; i < input.length; i++) {
    if (i < k) {
      heap.insert(input[i])
    } else if (input[i] > heap.findMin()) {
      heap.replace(input[i])
    }
  }

  const result = []
  while (heap.length) result.push(heap.extractMin())

  return result
}

test('it should return k max elements from given array', assert => {
  assert.deepEqual(findMaxElements([1, 2, 5, 3, 10, 6, 0, 7], 3), [6, 7, 10])
  assert.deepEqual(findMaxElements([3, 1, 2], 5), [1, 2, 3])
  assert.end()
})
<commit_msg>Use heap iterator<commit_after>const test = require('tape')
const MinHeap = require('../DataStructures/MinHeap')
/**
 * Find k max elements from given input array.
 *
 * @param {array} input
 * @param {number} k
 * @return {array}
 */
function findMaxElements(input, k) {
  const heap = new MinHeap()

  for (let i = 0; i < input.length; i++) {
    if (i < k) {
      heap.insert(input[i])
    } else if (input[i] > heap.findMin()) {
      heap.replace(input[i])
    }
  }

  return [...heap]
}

test('it should return k max elements from given array', assert => {
  assert.deepEqual(findMaxElements([1, 2, 5, 3, 10, 6, 0, 7], 3), [6, 7, 10])
  assert.deepEqual(findMaxElements([3, 1, 2], 5), [1, 2, 3])
  assert.end()
})
<|endoftext|>


<commit_before>import Buildfire, { components } from 'buildfire';
import Handlebars from './lib/handlebars';

window.detailView = {
    init: (place) => {
        //Add filter control
        let view = document.getElementById('detailView');
        let screenWidth = window.innerWidth;
        const title = place.title;

        let context = {
            width: screenWidth,
            image: place.image,
            title: title,
            description: place.description,
            distance: place.distance,
            address: place.address.name,
            actionItems: place.actionItems && place.actionItems.length > 0,
            lat: place.address.lat,
            lng: place.address.lng
        };

        let req = new XMLHttpRequest();
        req.open('GET', './templates/detail.hbs');
        req.send();
        req.onload = () => {
            let theTemplate = Handlebars.compile(req.responseText);

            // Pass our data to the template
            let theCompiledHtml = theTemplate(context);

            // Add the compiled html to the page
            view.innerHTML = theCompiledHtml;

            //TODO: Move to common location
            let mapTypeId = window.google.maps.MapTypeId.ROADMAP,
                zoomTo = 14, //city
                centerOn = {lat: place.address.lat, lng: place.address.lng};

            let options = {
                gestureHandling: 'greedy',
                streetViewControl: false,
                mapTypeControl: false,
                fullscreenControl: false,
                zoom: zoomTo,
                center: centerOn,
                mapTypeId: mapTypeId,
                disableDefaultUI: true
            };

            /**
             * Get Directions
             */
             let directionsButton = document.getElementById('directionsBtn');
             directionsButton.className = 'btn btn-primary';
             directionsButton.addEventListener('click', getDirections);

             let contactButton = document.getElementById('contactBtn');
             contactButton.className = 'btn btn-success';
             contactButton.addEventListener('click', showContact);

             function getDirections() {
                let hbsContext = context;
                Buildfire.getContext((err, context) => {
                    if (context && context.device && context.device.platform === 'ios') {
                        Buildfire.navigation.openWindow(`https://maps.apple.com/?q=${hbsContext.title}&t=m&daddr=${hbsContext.lat},${hbsContext.lng}`, '_system');
                    } else {
                        Buildfire.navigation.openWindow(`http://maps.google.com/maps?daddr=${hbsContext.lat},${hbsContext.lng}`, '_system');
                    }
                });
             }

             function showContact() {
                const { actionItems } = place;
                window.buildfire.actionItems.list(actionItems, {}, (err, actionItem) => {
                    if (err) return console.error(err);
                    console.log(actionItem);
                });
             }

            /**
             * Carousel
             */
            if (place.carousel && place.carousel.length) {
                let targetNode = document.getElementById('carouselView');
                // Set carousel's height to a 16:9 aspect ratio
                targetNode.style.height = `${window.innerWidth / 16 * 9}px`;
                new components.carousel.view({
                    selector: targetNode,
                    items: place.carousel
                });
            }

            /**
             * Google Maps
             */

            let map = new window.google.maps.Map(document.getElementById('smallMap'), options);

            const iconBaseUrl = 'https://app.buildfire.com/app/media/',
                icon = {
                    url: iconBaseUrl + 'google_marker_green_icon.png',
                    // This marker is 20 pixels wide by 20 pixels high.
                    scaledSize: new window.google.maps.Size(20, 20),
                    // The origin for this image is (0, 0).
                    origin: new window.google.maps.Point(0, 0),
                    // The anchor for this image is at the center of the circle
                    anchor: new window.google.maps.Point(10, 10)
                };

            new window.google.maps.Marker({
                position: place.address,
                map,
                icon
            });
        };
    },

};
<commit_msg>Safe guard contact button<commit_after>import Buildfire, { components } from 'buildfire';
import Handlebars from './lib/handlebars';

window.detailView = {
    init: (place) => {
        //Add filter control
        let view = document.getElementById('detailView');
        let screenWidth = window.innerWidth;
        const title = place.title;

        let context = {
            width: screenWidth,
            image: place.image,
            title: title,
            description: place.description,
            distance: place.distance,
            address: place.address.name,
            actionItems: place.actionItems && place.actionItems.length > 0,
            lat: place.address.lat,
            lng: place.address.lng
        };

        let req = new XMLHttpRequest();
        req.open('GET', './templates/detail.hbs');
        req.send();
        req.onload = () => {
            let theTemplate = Handlebars.compile(req.responseText);

            // Pass our data to the template
            let theCompiledHtml = theTemplate(context);

            // Add the compiled html to the page
            view.innerHTML = theCompiledHtml;

            //TODO: Move to common location
            let mapTypeId = window.google.maps.MapTypeId.ROADMAP,
                zoomTo = 14, //city
                centerOn = {lat: place.address.lat, lng: place.address.lng};

            let options = {
                gestureHandling: 'greedy',
                streetViewControl: false,
                mapTypeControl: false,
                fullscreenControl: false,
                zoom: zoomTo,
                center: centerOn,
                mapTypeId: mapTypeId,
                disableDefaultUI: true
            };

            /**
             * Get Directions
             */
             let directionsButton = document.getElementById('directionsBtn');
             directionsButton.className = 'btn btn-primary';
             directionsButton.addEventListener('click', getDirections);

             let contactButton = document.getElementById('contactBtn');
             if (contactButton) {
                contactButton.className = 'btn btn-success';
                contactButton.addEventListener('click', showContact);
             }

             function getDirections() {
                let hbsContext = context;
                Buildfire.getContext((err, context) => {
                    if (context && context.device && context.device.platform === 'ios') {
                        Buildfire.navigation.openWindow(`https://maps.apple.com/?q=${hbsContext.title}&t=m&daddr=${hbsContext.lat},${hbsContext.lng}`, '_system');
                    } else {
                        Buildfire.navigation.openWindow(`http://maps.google.com/maps?daddr=${hbsContext.lat},${hbsContext.lng}`, '_system');
                    }
                });
             }

             function showContact() {
                const { actionItems } = place;
                window.buildfire.actionItems.list(actionItems, {}, (err, actionItem) => {
                    if (err) return console.error(err);
                    console.log(actionItem);
                });
             }

            /**
             * Carousel
             */
            if (place.carousel && place.carousel.length) {
                let targetNode = document.getElementById('carouselView');
                // Set carousel's height to a 16:9 aspect ratio
                targetNode.style.height = `${window.innerWidth / 16 * 9}px`;
                new components.carousel.view({
                    selector: targetNode,
                    items: place.carousel
                });
            }

            /**
             * Google Maps
             */

            let map = new window.google.maps.Map(document.getElementById('smallMap'), options);

            const iconBaseUrl = 'https://app.buildfire.com/app/media/',
                icon = {
                    url: iconBaseUrl + 'google_marker_green_icon.png',
                    // This marker is 20 pixels wide by 20 pixels high.
                    scaledSize: new window.google.maps.Size(20, 20),
                    // The origin for this image is (0, 0).
                    origin: new window.google.maps.Point(0, 0),
                    // The anchor for this image is at the center of the circle
                    anchor: new window.google.maps.Point(10, 10)
                };

            new window.google.maps.Marker({
                position: place.address,
                map,
                icon
            });
        };
    },

};
<|endoftext|>


<commit_before>let os = require('os');
let ipcRenderer = require('electron').ipcRenderer;
let delayOnExecution = 50; // in milliseconds

document.addEventListener('keyup', handleGlobalKeyPress);

let vue = new Vue({
    el: '#vue-root',
    data: {
        userInput: '',
        searchResults: [],
        autoFocus: true,
        stylesheetPath: os.platform() === 'win32' ? './styles/css/windows.css' : './styles/css/mac.css'
    },
    methods: {
        handleKeyPress: (event) => {
            if (event.key === 'Enter') {
                handleEnterPress();
            }
            if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
                event.preventDefault();
                let direction = event.key === 'ArrowDown' ? 'next' : 'prev';
                handleChangeActive(direction);
            }
        }
    },
    watch: {
        userInput: (val) => {
            ipcRenderer.send('get-search', val);
        }
    }
});

ipcRenderer.on('get-search-response', (event, arg) => {
    let searchResults = arg;
    let idIndex = 0;

    searchResults.forEach((s) => {
        s.id = `search-result-item-${idIndex}`;
        s.active = false;
        idIndex++;
    });

    if (searchResults.length > 0) {
        searchResults[0].active = true;
    }

    vue.searchResults = searchResults;
});

function handleChangeActive(direction) {
    let next;

    for (let i = 0; i < vue.searchResults.length; i++) {
        if (vue.searchResults[i].active) {
            next = direction === 'next' ? i + 1 : i - 1;
        }
    }

    vue.searchResults.forEach((s) => {
        s.active = false;
    });

    if (next < 0) {
        next = vue.searchResults.length - 1;
    }
    else if (next >= vue.searchResults.length) {
        next = 0;
    }

    vue.searchResults[next].active = true;
    scrollIntoView(vue.searchResults[next]);
}

function scrollIntoView(searchResult) {
    el = document.getElementById(searchResult.id);
    el.scrollIntoView();
}

function handleEnterPress() {
    let activeSearchResults = vue.searchResults.filter((s) => {
        return s.active;
    });

    if (activeSearchResults.length > 0) {
        resetUserInput();
        setTimeout(() => {
            execute(activeSearchResults[0].executionArgument)
        }, delayOnExecution);
    }
}

function execute(executionArgument) {
    ipcRenderer.send('execute', executionArgument);
}

function resetUserInput() {
    vue.userInput = '';
}

function handleGlobalKeyPress(event) {
    if (event.key === 'F6' || (event.key === 'l' && event.ctrlKey)) {
        focusOnInput();
    }
}

function focusOnInput() {
    document.getElementById('user-input').focus();
}<commit_msg>Always scroll back to top when searching for something new<commit_after>let os = require('os');
let ipcRenderer = require('electron').ipcRenderer;
let delayOnExecution = 50; // in milliseconds

document.addEventListener('keyup', handleGlobalKeyPress);

let vue = new Vue({
    el: '#vue-root',
    data: {
        userInput: '',
        searchResults: [],
        autoFocus: true,
        stylesheetPath: os.platform() === 'win32' ? './styles/css/windows.css' : './styles/css/mac.css'
    },
    methods: {
        handleKeyPress: (event) => {
            if (event.key === 'Enter') {
                handleEnterPress();
            }
            if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
                event.preventDefault();
                let direction = event.key === 'ArrowDown' ? 'next' : 'prev';
                handleChangeActive(direction);
            }
        }
    },
    watch: {
        userInput: (val) => {
            ipcRenderer.send('get-search', val);
        }
    }
});

ipcRenderer.on('get-search-response', (event, arg) => {
    updateSearchResults(arg);
});

function updateSearchResults(searchResults) {
    let idIndex = 0;

    searchResults.forEach((s) => {
        s.id = `search-result-item-${idIndex}`;
        s.active = false;
        idIndex++;
    });

    if (searchResults.length > 0) {
        searchResults[0].active = true;
    }

    vue.searchResults = searchResults;

    if (vue.searchResults.length > 0) {
        scrollIntoView(vue.searchResults[0]);
    }
}

function handleChangeActive(direction) {
    let next;

    for (let i = 0; i < vue.searchResults.length; i++) {
        if (vue.searchResults[i].active) {
            next = direction === 'next' ? i + 1 : i - 1;
        }
    }

    vue.searchResults.forEach((s) => {
        s.active = false;
    });

    if (next < 0) {
        next = vue.searchResults.length - 1;
    }
    else if (next >= vue.searchResults.length) {
        next = 0;
    }

    vue.searchResults[next].active = true;
    scrollIntoView(vue.searchResults[next]);
}

function scrollIntoView(searchResult) {
    el = document.getElementById(searchResult.id);
    el.scrollIntoView();
}

function handleEnterPress() {
    let activeSearchResults = vue.searchResults.filter((s) => {
        return s.active;
    });

    if (activeSearchResults.length > 0) {
        resetUserInput();
        setTimeout(() => {
            execute(activeSearchResults[0].executionArgument)
        }, delayOnExecution);
    }
}

function execute(executionArgument) {
    ipcRenderer.send('execute', executionArgument);
}

function resetUserInput() {
    vue.userInput = '';
}

function handleGlobalKeyPress(event) {
    if (event.key === 'F6' || (event.key === 'l' && event.ctrlKey)) {
        focusOnInput();
    }
}

function focusOnInput() {
    document.getElementById('user-input').focus();
}<|endoftext|>


<commit_before>"use strict";

/**
 * Renderer
 * @class Renderer
 */
class Renderer {

  /** @constructor */
  constructor() {

    /**
     * DOM container
     * @type {Object}
     */
    this.fpsContainer = document.querySelector("#fps");

    /**
     * DOM container
     * @type {Object}
     */
    this.neuronContainer = document.querySelector("#neurons");

    /**
     * DOM container
     * @type {Object}
     */
    this.canvasContainer = document.querySelector("#canvas");

    /**
     * Canvas 2D context
     * @type {Object}
     */
    this.ctx = this.canvasContainer.getContext("2d");

    /**
     * Canvas background color
     * @type {String}
     */
    this.canvasBackgroundColor = "#241C1F";

    /**
     * Width
     * @type {Number}
     */
    this.spectrum = 0;

    /**
     * Radius Factor
     * @type {Number}
     */
    this.radiusFactor = this.randomFactor();

    /**
     * Expand Mode
     * @type {Number}
     */
    this.mode = 1;

    /**
     * Width
     * @type {Number}
     */
    this.width = 0;

    /**
     * Height
     * @type {Number}
     */
    this.height = 0;

    /**
     * Running timer
     * @type {Number}
     */
    this.now = 0;

    /**
     * Active entities
     * @type {Array}
     */
    this.entities = [];

    /**
     * Last entity amount
     * @type {Number}
     */
    this.lastEntityAmount = 0;

    /**
     * Fps
     * @type {Number}
     */
    this.fps = 0;

    /**
     * Fps helper
     * @type {Number}
     */
    this.lastFps = (new Date()) * 1 - 1;

    /**
     * Next FPS update
     * @type {Number}
     */
    this.nextFpsUpdate = 0;

    /**
     * GC friendly iterator
     * @type {Number}
     */
    this.ii = 0;

  };

  /**
   * Generate random factor
   * @method randomFactor
   */
  randomFactor(entity) {

    return (parseFloat((Math.random() / 200).toFixed(5)));

  };

  /**
   * Entity exists
   * @method entityExists
   */
  entityExists(entity) {

    var id = entity.id;

    for (let ii = 0, length = this.entities.length; ii < length; ++ii) {
      if (this.entities[ii].id === id) return (true);
    };

    return (false);

  };

  /**
   * Add a new entity
   * @method addEntity
   */
  addEntity(entity) {

    if (!this.entityExists(entity)) {
      this.entities.push(entity);
    }

  };

  /**
   * Calculate
   * @method calculate
   */
  calculate() {

    this.ii = this.entities.length;

    while (this.ii) {

      --this.ii;

      this.entities[this.ii].radius = this.ii * this.radiusFactor;

      if (this.entities[this.ii].angle >= this.spectrum) {
        this.mode = 0;
      }

      this.entities[this.ii].angle += this.mode ? 0.0002 : -0.0004;

      if (this.entities[this.ii].angle < 0) {
        this.mode = 1;
        this.radiusFactor = this.randomFactor();
      }

      this.entities[this.ii].x = (this.width / 2) + (Math.PI * this.entities[this.ii].angle * this.ii) * Math.cos(this.entities[this.ii].angle * this.ii);
      this.entities[this.ii].y = (this.height / 2) + (Math.PI * this.entities[this.ii].angle * this.ii) * Math.sin(this.entities[this.ii].angle * this.ii);

    };

  };

  /**
   * Draw
   * @method draw
   */
  draw() {

    this.ii = this.entities.length;

    while (this.ii) {

      --this.ii;

      this.ctx.fillStyle = "white";
      this.ctx.beginPath();
      this.ctx.arc(this.entities[this.ii].x, this.entities[this.ii].y, this.entities[this.ii].radius, 0, Math.PI * 2, false);

      // Fill the color to the arc that we just created
      this.ctx.fill();

    };

  };

  /**
   * Fps
   * @method calculateFPS
   */
  calculateFPS() {

    this.fps = 1E3 / (new Date() - this.lastFps);

    this.lastFps = new Date();

  };

  /**
   * Update
   * @method update
   */
  update() {

    this.now = Date.now();

    this.calculateFPS();

    if (this.nextFpsUpdate <= this.now) {
      this.fpsContainer.innerHTML = this.fps.toFixed(1);
      this.nextFpsUpdate = this.now + 1E3;
    }

    if (this.lastEntityAmount !== this.entities.length) {
      this.neuronContainer.innerHTML = this.lastEntityAmount = this.entities.length;
    }

    /** Clear */
    this.ctx.rect(0, 0, this.width, this.height);
    this.ctx.fillStyle = this.canvasBackgroundColor;
    this.ctx.fill();

    this.draw();

    this.calculate();

    window.requestAnimFrame(() => this.update());

  };

  /**
   * Resize
   * @method resize
   */
  resize() {

    this.width = window.innerWidth;

    this.height = window.innerHeight;

    this.canvasContainer.width = this.width;

    this.canvasContainer.height = this.height;

    this.spectrum = (this.width / CONFIG.Entities) / 2.5;

  };

  /**
   * Run
   * @method run
   */
  run() {

    this.resize();

    window.addEventListener('resize', e => this.resize(), false);

    this.update();

  };

};

window.Renderer = Renderer;<commit_msg>Remove comment<commit_after>"use strict";

/**
 * Renderer
 * @class Renderer
 */
class Renderer {

  /** @constructor */
  constructor() {

    /**
     * DOM container
     * @type {Object}
     */
    this.fpsContainer = document.querySelector("#fps");

    /**
     * DOM container
     * @type {Object}
     */
    this.neuronContainer = document.querySelector("#neurons");

    /**
     * DOM container
     * @type {Object}
     */
    this.canvasContainer = document.querySelector("#canvas");

    /**
     * Canvas 2D context
     * @type {Object}
     */
    this.ctx = this.canvasContainer.getContext("2d");

    /**
     * Canvas background color
     * @type {String}
     */
    this.canvasBackgroundColor = "#241C1F";

    /**
     * Width
     * @type {Number}
     */
    this.spectrum = 0;

    /**
     * Radius Factor
     * @type {Number}
     */
    this.radiusFactor = this.randomFactor();

    /**
     * Expand Mode
     * @type {Number}
     */
    this.mode = 1;

    /**
     * Width
     * @type {Number}
     */
    this.width = 0;

    /**
     * Height
     * @type {Number}
     */
    this.height = 0;

    /**
     * Running timer
     * @type {Number}
     */
    this.now = 0;

    /**
     * Active entities
     * @type {Array}
     */
    this.entities = [];

    /**
     * Last entity amount
     * @type {Number}
     */
    this.lastEntityAmount = 0;

    /**
     * Fps
     * @type {Number}
     */
    this.fps = 0;

    /**
     * Fps helper
     * @type {Number}
     */
    this.lastFps = (new Date()) * 1 - 1;

    /**
     * Next FPS update
     * @type {Number}
     */
    this.nextFpsUpdate = 0;

    /**
     * GC friendly iterator
     * @type {Number}
     */
    this.ii = 0;

  };

  /**
   * Generate random factor
   * @method randomFactor
   */
  randomFactor(entity) {

    return (parseFloat((Math.random() / 200).toFixed(5)));

  };

  /**
   * Entity exists
   * @method entityExists
   */
  entityExists(entity) {

    var id = entity.id;

    for (let ii = 0, length = this.entities.length; ii < length; ++ii) {
      if (this.entities[ii].id === id) return (true);
    };

    return (false);

  };

  /**
   * Add a new entity
   * @method addEntity
   */
  addEntity(entity) {

    if (!this.entityExists(entity)) {
      this.entities.push(entity);
    }

  };

  /**
   * Calculate
   * @method calculate
   */
  calculate() {

    this.ii = this.entities.length;

    while (this.ii) {

      --this.ii;

      this.entities[this.ii].radius = this.ii * this.radiusFactor;

      if (this.entities[this.ii].angle >= this.spectrum) {
        this.mode = 0;
      }

      this.entities[this.ii].angle += this.mode ? 0.0002 : -0.0004;

      if (this.entities[this.ii].angle < 0) {
        this.mode = 1;
        this.radiusFactor = this.randomFactor();
      }

      this.entities[this.ii].x = (this.width / 2) + (Math.PI * this.entities[this.ii].angle * this.ii) * Math.cos(this.entities[this.ii].angle * this.ii);
      this.entities[this.ii].y = (this.height / 2) + (Math.PI * this.entities[this.ii].angle * this.ii) * Math.sin(this.entities[this.ii].angle * this.ii);

    };

  };

  /**
   * Draw
   * @method draw
   */
  draw() {

    this.ii = this.entities.length;

    while (this.ii) {

      --this.ii;

      this.ctx.fillStyle = "white";
      this.ctx.beginPath();
      this.ctx.arc(this.entities[this.ii].x, this.entities[this.ii].y, this.entities[this.ii].radius, 0, Math.PI * 2, false);
      this.ctx.fill();

    };

  };

  /**
   * Fps
   * @method calculateFPS
   */
  calculateFPS() {

    this.fps = 1E3 / (new Date() - this.lastFps);

    this.lastFps = new Date();

  };

  /**
   * Update
   * @method update
   */
  update() {

    this.now = Date.now();

    this.calculateFPS();

    if (this.nextFpsUpdate <= this.now) {
      this.fpsContainer.innerHTML = this.fps.toFixed(1);
      this.nextFpsUpdate = this.now + 1E3;
    }

    if (this.lastEntityAmount !== this.entities.length) {
      this.neuronContainer.innerHTML = this.lastEntityAmount = this.entities.length;
    }

    /** Clear */
    this.ctx.rect(0, 0, this.width, this.height);
    this.ctx.fillStyle = this.canvasBackgroundColor;
    this.ctx.fill();

    this.draw();

    this.calculate();

    window.requestAnimFrame(() => this.update());

  };

  /**
   * Resize
   * @method resize
   */
  resize() {

    this.width = window.innerWidth;

    this.height = window.innerHeight;

    this.canvasContainer.width = this.width;

    this.canvasContainer.height = this.height;

    this.spectrum = (this.width / CONFIG.Entities) / 2.5;

  };

  /**
   * Run
   * @method run
   */
  run() {

    this.resize();

    window.addEventListener('resize', e => this.resize(), false);

    this.update();

  };

};

window.Renderer = Renderer;<|endoftext|>


<commit_before><commit_msg>The Bot can now see stafftalk!<commit_after><|endoftext|>


<commit_before>import Cell from "./cell"

const HEX_WIDTH = 148
const HEX_HEIGHT = 130
const HEX_OFFSET_X = 115
const HEX_OFFSET_Y = 132

export default class Renderer {

    /**
     * @param {Board} board
     * @param {Game} game
     */
    constructor(board, game) {
        /** @type {PIXI.Application} */
        this.pixiApp = new PIXI.Application()

        /** @type {Board} */
        this.board = board

        /** @type {Game} */
        this.game = game

        /** @type {PIXI.Sprite[]} */
        this.hexes = []
    }

    /**
     * @return {HTMLCanvasElement|WindowProxy|null}
     */
    getView() {
        return this.pixiApp.view
    }

    createBoard() {
        let self = this
        Cell.getTypes().forEach(function (cellType) {
            PIXI.loader.add('hex.' + cellType, 'assets/ryanshenk.hex.' + cellType + '.png')
        })
        PIXI.loader.load(function(loader, resources) {
            self.board.children.forEach(function (cell) {
                let hex = self.createHex(cell, resources)
                self.pixiApp.stage.addChild(hex)
                self.hexes.push(hex)
            })
        })
        this.pixiApp.ticker.add(function() {
            self.hexes.forEach(function (hex) {
                hex.text.text = hex.cell.unit !== null ? 'UNIT' : ''
            })
        })
    }

    /**
     * @param {Cell} cell
     * @param {Resource[]} resources
     * @return {PIXI.Sprite}
     */
    createHex(cell, resources) {
        let hex = new PIXI.Sprite(resources['hex.' + cell.type].texture)

        hex.cell = cell
        hex.text = new PIXI.Text(cell.text)
        hex.addChild(hex.text)

        hex.pivot.x = HEX_WIDTH / 2
        hex.pivot.y = HEX_HEIGHT / 2

        let size = this.calculateSize()

        hex.width = size * HEX_WIDTH
        hex.height = size * HEX_HEIGHT

        let coordinate = cell.coordinate
        hex.x = size * HEX_OFFSET_X * coordinate.x + this.pixiApp.renderer.width / 2
        hex.y = size * HEX_OFFSET_Y * (coordinate.z + coordinate.x / 2) + this.pixiApp.renderer.height / 2
        addCoordinateAsText(hex, coordinate)

        hex.interactive = true
        let self = this
        hex.on('mouseup', function () {
            self.game.cellClick(this.cell)
        })

        return hex

        function addCoordinateAsText(hex, coordinate) {
            let xText = new PIXI.Text(coordinate.x, getTextStyle('#FF0000'))
            xText.x = HEX_WIDTH - 50
            xText.y = HEX_HEIGHT / 2 - 10
            hex.addChild(xText)

            let yText = new PIXI.Text(coordinate.y, getTextStyle('#00FF00'))
            yText.x = 35
            yText.y = 10
            hex.addChild(yText)

            let zText = new PIXI.Text(coordinate.z, getTextStyle('#0000FF'))
            zText.x = 35
            zText.y = HEX_HEIGHT - 35
            hex.addChild(zText)
        }

        /**
         * @param {string} color
         * @return {PIXI.TextStyle}
         */
        function getTextStyle(color) {
            return new PIXI.TextStyle({
                fontWeight: 900,
                fill: color,
                dropShadow: true,
                dropShadowBlur: 15
            })
        }
    }

    /**
     * @return {number}
     */
    calculateSize() {
        let xSize = this.pixiApp.renderer.width / HEX_OFFSET_X / (this.board.size * 2 + 1)
        let ySize = this.pixiApp.renderer.height / HEX_OFFSET_Y / (this.board.size * 2 + 1)

        return Math.min(xSize, ySize)
    }

}
<commit_msg>Unit is rendered as BLACK CHESS PAWN (U+265F)<commit_after>import Cell from "./cell"

const HEX_WIDTH = 148
const HEX_HEIGHT = 130
const HEX_OFFSET_X = 115
const HEX_OFFSET_Y = 132

export default class Renderer {

    /**
     * @param {Board} board
     * @param {Game} game
     */
    constructor(board, game) {
        /** @type {PIXI.Application} */
        this.pixiApp = new PIXI.Application()

        /** @type {Board} */
        this.board = board

        /** @type {Game} */
        this.game = game

        /** @type {PIXI.Sprite[]} */
        this.hexes = []
    }

    /**
     * @return {HTMLCanvasElement|WindowProxy|null}
     */
    getView() {
        return this.pixiApp.view
    }

    createBoard() {
        let self = this
        Cell.getTypes().forEach(function (cellType) {
            PIXI.loader.add('hex.' + cellType, 'assets/ryanshenk.hex.' + cellType + '.png')
        })
        PIXI.loader.load(function(loader, resources) {
            self.board.children.forEach(function (cell) {
                let hex = self.createHex(cell, resources)
                self.pixiApp.stage.addChild(hex)
                self.hexes.push(hex)
            })
        })
        this.pixiApp.ticker.add(function() {
            self.hexes.forEach(function (hex) {
                hex.text.text = hex.cell.unit !== null ? '♟' : ''
            })
        })
    }

    /**
     * @param {Cell} cell
     * @param {Resource[]} resources
     * @return {PIXI.Sprite}
     */
    createHex(cell, resources) {
        let hex = new PIXI.Sprite(resources['hex.' + cell.type].texture)

        hex.cell = cell

        hex.pivot.x = HEX_WIDTH / 2
        hex.pivot.y = HEX_HEIGHT / 2

        let size = this.calculateSize()

        hex.width = size * HEX_WIDTH
        hex.height = size * HEX_HEIGHT

        let coordinate = cell.coordinate
        hex.x = size * HEX_OFFSET_X * coordinate.x + this.pixiApp.renderer.width / 2
        hex.y = size * HEX_OFFSET_Y * (coordinate.z + coordinate.x / 2) + this.pixiApp.renderer.height / 2
        addCoordinateAsText(hex, coordinate)

        hex.text = new PIXI.Text(cell.text, {
            fill: '#FFFFFF',
            stroke: '#AA0000',
            strokeThickness: 10,
            fontSize: 80,
            //fontWeight: 900,
            dropShadow: true,
            dropShadowBlur: 30
        })
        hex.text.x = HEX_WIDTH / 2 - 50
        hex.text.y = HEX_HEIGHT / 2 - 50
        hex.addChild(hex.text)

        hex.interactive = true
        let self = this
        hex.on('mouseup', function () {
            self.game.cellClick(this.cell)
        })

        return hex

        function addCoordinateAsText(hex, coordinate) {
            let xText = new PIXI.Text(coordinate.x, getTextStyle('#FF0000'))
            xText.x = HEX_WIDTH - 50
            xText.y = HEX_HEIGHT / 2 - 10
            hex.addChild(xText)

            let yText = new PIXI.Text(coordinate.y, getTextStyle('#00FF00'))
            yText.x = 35
            yText.y = 10
            hex.addChild(yText)

            let zText = new PIXI.Text(coordinate.z, getTextStyle('#0000FF'))
            zText.x = 35
            zText.y = HEX_HEIGHT - 35
            hex.addChild(zText)
        }

        /**
         * @param {string} color
         * @return {PIXI.TextStyle}
         */
        function getTextStyle(color) {
            return new PIXI.TextStyle({
                fontWeight: 900,
                fill: color,
                dropShadow: true,
                dropShadowBlur: 15
            })
        }
    }

    /**
     * @return {number}
     */
    calculateSize() {
        let xSize = this.pixiApp.renderer.width / HEX_OFFSET_X / (this.board.size * 2 + 1)
        let ySize = this.pixiApp.renderer.height / HEX_OFFSET_Y / (this.board.size * 2 + 1)

        return Math.min(xSize, ySize)
    }

}
<|endoftext|>


<commit_before>/**
 * @file RaisedButton component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import classNames from 'classnames';

import BaseButton from '../_BaseButton';
import TipProvider from '../TipProvider';
import Theme from '../Theme';

import PureRender from '../_vendors/PureRender';
import Util from '../_vendors/Util';
import Position from '../_statics/Position';

@PureRender
class RaisedButton extends Component {

    static Theme = Theme;
    static TipPosition = Position;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.startRipple = ::this.startRipple;
        this.endRipple = ::this.endRipple;

    }

    startRipple(e) {
        this.refs.baseButton.startRipple(e);
    }

    endRipple() {
        this.refs.baseButton.endRipple();
    }

    render() {

        const {children, className, ...restProps} = this.props,

            buttonClassName = classNames('raised-button', {
                [className]: className
            });

        return (
            <BaseButton {...restProps}
                        ref="baseButton"
                        className={buttonClassName}>
                {children}
            </BaseButton>
        );

    }
};

if (process.env.NODE_ENV === 'development') {
    const PropTypes = require('prop-types');
    RaisedButton.propTypes = {

        /**
         * The CSS class name of the root element.
         */
        className: PropTypes.string,

        /**
         * Override the styles of the root element.
         */
        style: PropTypes.object,

        /**
         * The button theme.Can be primary,highlight,success,warning,error.
         */
        theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

        /**
         * If true,the button will have rounded corners.
         */
        isRounded: PropTypes.bool,

        /**
         * If true,the button will be round.
         */
        isCircular: PropTypes.bool,

        /**
         * The text of the button.
         */
        value: PropTypes.any,

        /**
         * The type of button.Can be reset,submit or button.
         */
        type: PropTypes.string,

        /**
         * Disables the button if set to true.
         */
        disabled: PropTypes.bool,

        /**
         * If true,the button will be have loading effect.
         */
        isLoading: PropTypes.bool,

        /**
         * If true,the element's ripple effect will be disabled.
         */
        disableTouchRipple: PropTypes.bool,

        /**
         * Use this property to display an icon.It will display on the left.
         */
        iconCls: PropTypes.string,

        /**
         * Use this property to display an icon.It will display on the right.
         */
        rightIconCls: PropTypes.string,

        /**
         * If true,the ripple effect will be display centered.
         */
        rippleDisplayCenter: PropTypes.bool,

        tip: PropTypes.string,
        tipPosition: PropTypes.oneOf(Util.enumerateValue(TipProvider.Position)),

        /**
         * You can create a complicated renderer callback instead of value prop.
         */
        renderer: PropTypes.func,

        /**
         * Callback function fired when the button is touch-tapped.
         */
        onTouchTap: PropTypes.func

    };
}

RaisedButton.defaultProps = {

    className: '',
    style: null,
    theme: Theme.DEFAULT,

    isRounded: false,
    isCircular: false,

    value: '',
    disabled: false,
    type: 'button',
    isLoading: false,
    disableTouchRipple: false,

    rippleDisplayCenter: false,

    iconCls: '',
    rightIconCls: '',

    tip: null,
    tipPosition: TipProvider.Position.BOTTOM

};

export default RaisedButton;<commit_msg>update RaisedButton<commit_after>/**
 * @file RaisedButton component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import classNames from 'classnames';

import BaseButton from '../_BaseButton';
import TipProvider from '../TipProvider';
import Theme from '../Theme';

import PureRender from '../_vendors/PureRender';
import Util from '../_vendors/Util';
import Position from '../_statics/Position';

@PureRender
class RaisedButton extends Component {

    static Theme = Theme;
    static TipPosition = Position;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.startRipple = ::this.startRipple;
        this.endRipple = ::this.endRipple;

    }

    startRipple(e) {
        this.refs.baseButton.startRipple(e);
    }

    endRipple() {
        this.refs.baseButton.endRipple();
    }

    render() {

        const {children, className, ...restProps} = this.props,

            buttonClassName = classNames('raised-button', {
                [className]: className
            });

        return (
            <BaseButton {...restProps}
                        ref="baseButton"
                        className={buttonClassName}>
                {children}
            </BaseButton>
        );

    }
};

if (process.env.NODE_ENV === 'development') {

    const PropTypes = require('prop-types');

    RaisedButton.propTypes = {

        /**
         * The CSS class name of the root element.
         */
        className: PropTypes.string,

        /**
         * Override the styles of the root element.
         */
        style: PropTypes.object,

        /**
         * The button theme.Can be primary,highlight,success,warning,error.
         */
        theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

        /**
         * If true,the button will have rounded corners.
         */
        isRounded: PropTypes.bool,

        /**
         * If true,the button will be round.
         */
        isCircular: PropTypes.bool,

        /**
         * The text of the button.
         */
        value: PropTypes.any,

        /**
         * The type of button.Can be reset,submit or button.
         */
        type: PropTypes.string,

        /**
         * Disables the button if set to true.
         */
        disabled: PropTypes.bool,

        /**
         * If true,the button will be have loading effect.
         */
        isLoading: PropTypes.bool,

        /**
         * If true,the element's ripple effect will be disabled.
         */
        disableTouchRipple: PropTypes.bool,

        /**
         * Use this property to display an icon.It will display on the left.
         */
        iconCls: PropTypes.string,

        /**
         * Use this property to display an icon.It will display on the right.
         */
        rightIconCls: PropTypes.string,

        /**
         * If true,the ripple effect will be display centered.
         */
        rippleDisplayCenter: PropTypes.bool,

        tip: PropTypes.string,
        tipPosition: PropTypes.oneOf(Util.enumerateValue(TipProvider.Position)),

        /**
         * You can create a complicated renderer callback instead of value prop.
         */
        renderer: PropTypes.func,

        /**
         * Callback function fired when the button is touch-tapped.
         */
        onTouchTap: PropTypes.func

    };

}

RaisedButton.defaultProps = {

    className: '',
    style: null,
    theme: Theme.DEFAULT,

    isRounded: false,
    isCircular: false,

    value: '',
    disabled: false,
    type: 'button',
    isLoading: false,
    disableTouchRipple: false,

    rippleDisplayCenter: false,

    iconCls: '',
    rightIconCls: '',

    tip: null,
    tipPosition: TipProvider.Position.BOTTOM

};

export default RaisedButton;<|endoftext|>


<commit_before>/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2013 Red Hat, Inc.
 *
 * Cockpit is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Cockpit is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
 */

var cockpit_pages = [];
var cockpit_visited_pages = {};

var cockpit_loc_trail;

var cockpit_current_hash;
var cockpit_content_is_shown = false;

function cockpit_content_init ()
{
    var pages = $('#content > div');
    pages.each (function (i, p) {
        $(p).hide();
    });
    cockpit_loc_trail = [ ];

    $('div[role="dialog"]').on('show.bs.modal', function() {
	cockpit_page_enter($(this).attr("id"));
    });
    $('div[role="dialog"]').on('shown.bs.modal', function() {
	cockpit_page_show($(this).attr("id"));
    });
    $('div[role="dialog"]').on('hidden.bs.modal', function() {
	cockpit_page_leave($(this).attr("id"));
    });

    $(window).on('hashchange', function () {
        if (window.location.hash != cockpit_current_hash)
            cockpit_go_hash (window.location.hash);
    });

    $(window).on('resize', function () {
        cockpit_content_header_changed ();
    });

    cockpit_search_init ($('#content-search'));

    cockpit_content_refresh ();
}

function cockpit_content_show ()
{
    $('#content-user-name').text($cockpit.connection_config.name || $cockpit.connection_config.user || "???");

    $('.page').hide();
    $('#content').show();
    cockpit_content_is_shown = true;
    cockpit_go_hash (window.location.hash);
}

function cockpit_content_leave ()
{
    if (cockpit_loc_trail.length > 0)
        cockpit_page_leave (cockpit_loc_trail[cockpit_loc_trail.length-1].page);
    cockpit_content_is_shown = false;
}

function cockpit_content_refresh ()
{
    $('#content-search').attr('placeholder', _("Search"));
    if (cockpit_loc_trail.length > 0)
        cockpit_go (cockpit_loc_trail);
}

function cockpit_content_header_changed ()
{
    $('body').css('padding-top', $('#content nav').height());
}

function cockpit_content_update_loc_trail ()
{
    function go(t) {
        return function () {
            cockpit_go (t);
        };
    }

    var i;
    var box = $('#content-loc-trail');
    box.empty();
    for (i = 0; i < cockpit_loc_trail.length; i++) {
        var p = cockpit_page_from_id (cockpit_loc_trail[i].page);
        var title = p? (p.getTitleHtml? p.getTitleHtml() : cockpit_esc(p.getTitle())) : "??";
        var btn = $('<button>', { 'class': 'btn btn-default' }).html(title);
        box.append(btn);
        btn.on('click', go(cockpit_loc_trail.slice(0, i+1)));
    }

    var doc_title = "";
    if (cockpit_loc_trail.length == 1)
        doc_title = cockpit_get_page_title (cockpit_loc_trail[0].page);
    else if (cockpit_loc_trail.length > 1) {
        doc_title = cockpit_get_page_title (cockpit_loc_trail[1].page);
        if (cockpit_loc_trail.length > 2)
            doc_title = doc_title + " — " + cockpit_get_page_title (cockpit_loc_trail[cockpit_loc_trail.length-1].page);
    }
    document.title = doc_title;
}

var cockpit_dbus_client_selected_machine;

function cockpit_select_legacy_client ()
{
    cockpit_dbus_client_selected_machine = "localhost";
    cockpit_dbus_client = cockpit_dbus_local_client;

    var trail = cockpit_decode_trail (window.location.hash);

    if (trail.length >= 2 && trail[1].page == "server") {
        cockpit_dbus_client_selected_machine = trail[1].machine || "localhost";
        for (var j = 0; j < cockpit_machines.length; j++) {
            if (cockpit_machines[j].address == cockpit_dbus_client_selected_machine)
                cockpit_dbus_client = cockpit_machines[j].client;
        }
    }
}

function cockpit_go (trail)
{
    var new_loc = trail[trail.length-1];

    if (trail.length >= 2 && trail[1].page == "server") {
        // Check whether we are connected to the right machine, and if
        // not, reload the whole thing.

        var wanted_machine = trail[1].machine || "localhost";
        var selected_machine = cockpit_dbus_client_selected_machine;
        var connected_machine = cockpit_dbus_client.target;

        if (wanted_machine != selected_machine) {
            cockpit_loc_trail = trail;
            cockpit_show_hash ();
            cockpit_expecting_disconnect = true;
            window.location.reload(false);
            return;
        }

        if (selected_machine != connected_machine)
            window.alert("This is not the machine you are looking for.");
    }

    if ($('#' + new_loc.page).length === 0) {
        cockpit_go (trail.slice(0, trail.length-1));
        return;
    } else if (cockpit_loc_trail.length === 0) {
        cockpit_loc_trail = trail;
        $('#content-header-extra').empty();
        $('#content-search').val("");
        cockpit_page_enter (new_loc.page);
    } else {
        var cur_loc = cockpit_loc_trail[cockpit_loc_trail.length-1];
        cockpit_page_leave (cur_loc.page);
        cockpit_loc_trail = trail;
        $('#content-header-extra').empty();
        $('#content-search').val("");
        cockpit_page_enter (new_loc.page);
        $('#' + cur_loc.page).hide();
    }

    $('#' + new_loc.page).show();
    cockpit_show_hash ();
    cockpit_content_update_loc_trail ();
    cockpit_content_header_changed ();
    cockpit_page_show (new_loc.page);
}

function cockpit_go_down (loc)
{
    if (loc.substr)
        loc = { page: loc };
    cockpit_go (cockpit_loc_trail.concat([ loc ]));
}

function cockpit_go_top (page, params)
{
    var loc = $.extend({ page: page }, params);
    cockpit_go ([ cockpit_loc_trail[0], loc ]);
}

function cockpit_go_down_cmd (page, params)
{
    var loc = $.extend({ page: page }, params);
    return "cockpit_go_down(" + JSON.stringify(loc) + ");";
}

function cockpit_go_up ()
{
    if (cockpit_loc_trail.length > 1)
        cockpit_go (cockpit_loc_trail.slice(0, cockpit_loc_trail.length-1));
}

function cockpit_encode_trail (trail)
{
    function encode (p)
    {
        var res = encodeURIComponent(p.page);
        var param;
        for (param in p) {
            if (param != "page" && p.hasOwnProperty(param))
                res += "?" + encodeURIComponent(param) + "=" + encodeURIComponent(p[param]);
        }
        return res;
    }

    var hash;
    if (trail.length == 1 && trail[0].page == "dashboard")
        hash = encode(trail[0]);
    else {
        hash = '';
        for (var i = 1; i < trail.length; i++) {
            hash += encode(trail[i]);
            if (i < trail.length-1)
                hash += '&';
        }
    }

    return '#' + hash;
}

function cockpit_decode_trail (hash)
{
    var locs, params, vals, trail, p, i, j;

    if (hash === "") {
        if (cockpit_machines.length == 1)
            return [ { page: "dashboard"
                     },
                     { page: "server",
                       machine: cockpit_machines[0].address
                     }
                   ];
        else
            return [ { page: "dashboard" } ];
    }

    if (hash[0] == '#')
        hash = hash.substr(1);

    locs = hash.split('&');
    trail = [ ];

    for (i = 0; i < locs.length; i++) {
        params = locs[i].split('?');
        p = { page: decodeURIComponent(params[0]) };
        for (j = 1; j < params.length; j++) {
            vals = params[j].split('=');
            p[decodeURIComponent(vals[0])] = decodeURIComponent(vals[1]);
        }
        trail.push(p);
    }
    if (trail[0].page != "dashboard")
        trail = [ { page: 'dashboard' } ].concat(trail);

    return trail;
}

function cockpit_show_hash ()
{
    cockpit_current_hash = cockpit_encode_trail (cockpit_loc_trail);
    window.location.hash = cockpit_current_hash;
}

function cockpit_go_hash (hash)
{
    cockpit_go (cockpit_decode_trail (hash));
}

function cockpit_page_from_id (id)
{
    var page = null;
    for (var n = 0; n < cockpit_pages.length; n++) {
        if (cockpit_pages[n].id == id) {
            page = cockpit_pages[n];
            break;
        }
    }
    return page;
}

function cockpit_page_enter (id)
{
    var page = cockpit_page_from_id(id);
    var first_visit = true;
    if (cockpit_visited_pages[id])
        first_visit = false;

    if (page) {
        // cockpit_debug("enter() for page with id " + id);
        page.enter(first_visit);
    }
    cockpit_visited_pages[id] = true;
    phantom_checkpoint ();
}

function cockpit_page_leave (id)
{
    var page = cockpit_page_from_id(id);
    if (page) {
        // cockpit_debug("leave() for page with id " + id);
        page.leave();
    }
    phantom_checkpoint ();
}

function cockpit_page_show(id)
{
    var page = cockpit_page_from_id(id);
    if (page) {
        // cockpit_debug("show() for page with id " + id);
        if (cockpit_content_is_shown) {
            page.show();
        }
    }
    phantom_checkpoint ();
}

function cockpit_get_page_title(id)
{
    var page = cockpit_page_from_id(id);
    return page? page.getTitle() : _("Unknown Page");
}

function cockpit_get_page_param(key, page)
{
    var index = cockpit_loc_trail.length-1;
    if (page) {
        while (index >= 0 && cockpit_loc_trail[index].page != page)
            index--;
    }
    if (index >= 0)
        return cockpit_loc_trail[index][key];
    else
        return undefined;
}

function cockpit_set_page_param(key, val)
{
    if (val)
        cockpit_loc_trail[cockpit_loc_trail.length-1][key] = val;
    else
        delete cockpit_loc_trail[cockpit_loc_trail.length-1][key];

    cockpit_show_hash ();
}

function cockpit_show_error_dialog(title, message)
{
    if (message) {
        $("#error-popup-title").text(title);
        $("#error-popup-message").text(message);
    } else {
        $("#error-popup-title").text(_("Error"));
        $("#error-popup-message").text(title);
    }

    $('#error-popup').modal('show');
}

function cockpit_show_unexpected_error(error)
{
    cockpit_show_error_dialog(_("Unexpected error"), error.message || error);
}
<commit_msg>web: Tell pages when they appear in the breadcrumb.<commit_after>/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2013 Red Hat, Inc.
 *
 * Cockpit is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Cockpit is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
 */

var cockpit_pages = [];
var cockpit_visited_pages = {};

var cockpit_loc_trail;

var cockpit_current_hash;
var cockpit_content_is_shown = false;

function cockpit_content_init ()
{
    var pages = $('#content > div');
    pages.each (function (i, p) {
        $(p).hide();
    });
    cockpit_loc_trail = [ ];

    $('div[role="dialog"]').on('show.bs.modal', function() {
	cockpit_page_enter($(this).attr("id"));
    });
    $('div[role="dialog"]').on('shown.bs.modal', function() {
	cockpit_page_show($(this).attr("id"));
    });
    $('div[role="dialog"]').on('hidden.bs.modal', function() {
	cockpit_page_leave($(this).attr("id"));
    });

    $(window).on('hashchange', function () {
        if (window.location.hash != cockpit_current_hash)
            cockpit_go_hash (window.location.hash);
    });

    $(window).on('resize', function () {
        cockpit_content_header_changed ();
    });

    cockpit_search_init ($('#content-search'));

    cockpit_content_refresh ();
}

function cockpit_content_show ()
{
    $('#content-user-name').text($cockpit.connection_config.name || $cockpit.connection_config.user || "???");

    $('.page').hide();
    $('#content').show();
    cockpit_content_is_shown = true;
    cockpit_go_hash (window.location.hash);
}

function cockpit_content_leave ()
{
    for (var i = 0; i < cockpit_loc_trail.length; i++)
        cockpit_page_leave_breadcrumb(cockpit_loc_trail[i].page);
    if (cockpit_loc_trail.length > 0)
        cockpit_page_leave (cockpit_loc_trail[cockpit_loc_trail.length-1].page);
    cockpit_loc_trail = [ ];
    cockpit_content_is_shown = false;
}

function cockpit_content_refresh ()
{
    $('#content-search').attr('placeholder', _("Search"));
    if (cockpit_loc_trail.length > 0)
        cockpit_go (cockpit_loc_trail);
}

function cockpit_content_header_changed ()
{
    $('body').css('padding-top', $('#content nav').height());
}

function cockpit_content_update_loc_trail ()
{
    function go(t) {
        return function () {
            cockpit_go (t);
        };
    }

    var i;
    var box = $('#content-loc-trail');
    box.empty();
    for (i = 0; i < cockpit_loc_trail.length; i++) {
        var p = cockpit_page_from_id (cockpit_loc_trail[i].page);
        var title = p? (p.getTitleHtml? p.getTitleHtml() : cockpit_esc(p.getTitle())) : "??";
        var btn = $('<button>', { 'class': 'btn btn-default' }).html(title);
        box.append(btn);
        btn.on('click', go(cockpit_loc_trail.slice(0, i+1)));
    }

    var doc_title = "";
    if (cockpit_loc_trail.length == 1)
        doc_title = cockpit_get_page_title (cockpit_loc_trail[0].page);
    else if (cockpit_loc_trail.length > 1) {
        doc_title = cockpit_get_page_title (cockpit_loc_trail[1].page);
        if (cockpit_loc_trail.length > 2)
            doc_title = doc_title + " — " + cockpit_get_page_title (cockpit_loc_trail[cockpit_loc_trail.length-1].page);
    }
    document.title = doc_title;
}

var cockpit_dbus_client_selected_machine;

function cockpit_select_legacy_client ()
{
    cockpit_dbus_client_selected_machine = "localhost";
    cockpit_dbus_client = cockpit_dbus_local_client;

    var trail = cockpit_decode_trail (window.location.hash);

    if (trail.length >= 2 && trail[1].page == "server") {
        cockpit_dbus_client_selected_machine = trail[1].machine || "localhost";
        for (var j = 0; j < cockpit_machines.length; j++) {
            if (cockpit_machines[j].address == cockpit_dbus_client_selected_machine)
                cockpit_dbus_client = cockpit_machines[j].client;
        }
    }
}

function cockpit_go (trail)
{
    var new_loc = trail[trail.length-1];

    if (trail.length >= 2 && trail[1].page == "server") {
        // Check whether we are connected to the right machine, and if
        // not, reload the whole thing.

        var wanted_machine = trail[1].machine || "localhost";
        var selected_machine = cockpit_dbus_client_selected_machine;
        var connected_machine = cockpit_dbus_client.target;

        if (wanted_machine != selected_machine) {
            cockpit_loc_trail = trail;
            cockpit_show_hash ();
            cockpit_expecting_disconnect = true;
            window.location.reload(false);
            return;
        }

        if (selected_machine != connected_machine)
            window.alert("This is not the machine you are looking for.");
    }

    function leave_breadcrumb(trail) {
        for (var i = 0; i < trail.length; i++)
            cockpit_page_leave_breadcrumb(trail[i].page);
    }

    function enter_breadcrumb(trail) {
        for (var i = 0; i < trail.length; i++)
            cockpit_page_enter_breadcrumb(trail[i].page);
    }

    if ($('#' + new_loc.page).length === 0) {
        cockpit_go (trail.slice(0, trail.length-1));
        return;
    } else if (cockpit_loc_trail.length === 0) {
        leave_breadcrumb(cockpit_loc_trail);
        cockpit_loc_trail = trail;
        enter_breadcrumb(cockpit_loc_trail);
        $('#content-header-extra').empty();
        $('#content-search').val("");
        cockpit_page_enter (new_loc.page);
    } else {
        var cur_loc = cockpit_loc_trail[cockpit_loc_trail.length-1];
        cockpit_page_leave (cur_loc.page);
        leave_breadcrumb(cockpit_loc_trail);
        cockpit_loc_trail = trail;
        enter_breadcrumb(cockpit_loc_trail);
        $('#content-header-extra').empty();
        $('#content-search').val("");
        cockpit_page_enter (new_loc.page);
        $('#' + cur_loc.page).hide();
    }

    $('#' + new_loc.page).show();
    cockpit_show_hash ();
    cockpit_content_update_loc_trail ();
    cockpit_content_header_changed ();
    cockpit_page_show (new_loc.page);
}

function cockpit_go_down (loc)
{
    if (loc.substr)
        loc = { page: loc };
    cockpit_go (cockpit_loc_trail.concat([ loc ]));
}

function cockpit_go_top (page, params)
{
    var loc = $.extend({ page: page }, params);
    cockpit_go ([ cockpit_loc_trail[0], loc ]);
}

function cockpit_go_down_cmd (page, params)
{
    var loc = $.extend({ page: page }, params);
    return "cockpit_go_down(" + JSON.stringify(loc) + ");";
}

function cockpit_go_up ()
{
    if (cockpit_loc_trail.length > 1)
        cockpit_go (cockpit_loc_trail.slice(0, cockpit_loc_trail.length-1));
}

function cockpit_encode_trail (trail)
{
    function encode (p)
    {
        var res = encodeURIComponent(p.page);
        var param;
        for (param in p) {
            if (param != "page" && p.hasOwnProperty(param))
                res += "?" + encodeURIComponent(param) + "=" + encodeURIComponent(p[param]);
        }
        return res;
    }

    var hash;
    if (trail.length == 1 && trail[0].page == "dashboard")
        hash = encode(trail[0]);
    else {
        hash = '';
        for (var i = 1; i < trail.length; i++) {
            hash += encode(trail[i]);
            if (i < trail.length-1)
                hash += '&';
        }
    }

    return '#' + hash;
}

function cockpit_decode_trail (hash)
{
    var locs, params, vals, trail, p, i, j;

    if (hash === "") {
        if (cockpit_machines.length == 1)
            return [ { page: "dashboard"
                     },
                     { page: "server",
                       machine: cockpit_machines[0].address
                     }
                   ];
        else
            return [ { page: "dashboard" } ];
    }

    if (hash[0] == '#')
        hash = hash.substr(1);

    locs = hash.split('&');
    trail = [ ];

    for (i = 0; i < locs.length; i++) {
        params = locs[i].split('?');
        p = { page: decodeURIComponent(params[0]) };
        for (j = 1; j < params.length; j++) {
            vals = params[j].split('=');
            p[decodeURIComponent(vals[0])] = decodeURIComponent(vals[1]);
        }
        trail.push(p);
    }
    if (trail[0].page != "dashboard")
        trail = [ { page: 'dashboard' } ].concat(trail);

    return trail;
}

function cockpit_show_hash ()
{
    cockpit_current_hash = cockpit_encode_trail (cockpit_loc_trail);
    window.location.hash = cockpit_current_hash;
}

function cockpit_go_hash (hash)
{
    cockpit_go (cockpit_decode_trail (hash));
}

function cockpit_page_from_id (id)
{
    var page = null;
    for (var n = 0; n < cockpit_pages.length; n++) {
        if (cockpit_pages[n].id == id) {
            page = cockpit_pages[n];
            break;
        }
    }
    return page;
}

function cockpit_page_enter (id)
{
    var page = cockpit_page_from_id(id);
    var first_visit = true;
    if (cockpit_visited_pages[id])
        first_visit = false;

    if (page) {
        // cockpit_debug("enter() for page with id " + id);
        page.enter(first_visit);
    }
    cockpit_visited_pages[id] = true;
    phantom_checkpoint ();
}

function cockpit_page_leave (id)
{
    var page = cockpit_page_from_id(id);
    if (page) {
        // cockpit_debug("leave() for page with id " + id);
        page.leave();
    }
    phantom_checkpoint ();
}

function cockpit_page_show(id)
{
    var page = cockpit_page_from_id(id);
    if (page) {
        // cockpit_debug("show() for page with id " + id);
        if (cockpit_content_is_shown) {
            page.show();
        }
    }
    phantom_checkpoint ();
}

function cockpit_page_enter_breadcrumb (id)
{
    var page = cockpit_page_from_id(id);
    if (page && page.enter_breadcrumb)
        page.enter_breadcrumb();
}

function cockpit_page_leave_breadcrumb (id)
{
    var page = cockpit_page_from_id(id);
    if (page && page.leave_breadcrumb)
        page.leave_breadcrumb();
}

function cockpit_get_page_title(id)
{
    var page = cockpit_page_from_id(id);
    return page? page.getTitle() : _("Unknown Page");
}

function cockpit_get_page_param(key, page)
{
    var index = cockpit_loc_trail.length-1;
    if (page) {
        while (index >= 0 && cockpit_loc_trail[index].page != page)
            index--;
    }
    if (index >= 0)
        return cockpit_loc_trail[index][key];
    else
        return undefined;
}

function cockpit_set_page_param(key, val)
{
    if (val)
        cockpit_loc_trail[cockpit_loc_trail.length-1][key] = val;
    else
        delete cockpit_loc_trail[cockpit_loc_trail.length-1][key];

    cockpit_show_hash ();
}

function cockpit_show_error_dialog(title, message)
{
    if (message) {
        $("#error-popup-title").text(title);
        $("#error-popup-message").text(message);
    } else {
        $("#error-popup-title").text(_("Error"));
        $("#error-popup-message").text(title);
    }

    $('#error-popup').modal('show');
}

function cockpit_show_unexpected_error(error)
{
    cockpit_show_error_dialog(_("Unexpected error"), error.message || error);
}
<|endoftext|>


<commit_before>var DummyAgent = function () {

};

DummyAgent.prototype.introduce = function () {
    return {
        "name"   : "Very Dummy",
        "author" : "Andrew Sidorenko",
        "email"  : "sidorenkobw@gmail.com"
    };
};

DummyAgent.prototype.onNewTick = function (status) {

};

DummyAgent.prototype.decision = function () {
    return null;
};

DummyAgent.prototype.onNotification = function (notificationCode) {

};

exports.agentClass = DummyAgent;
<commit_msg>VeryDummy now always idling<commit_after>var DummyAgent = function () {
    this.Constants = {};
};

DummyAgent.prototype.introduce = function () {
    return {
        "name"   : "Very Dummy",
        "author" : "Andrew Sidorenko",
        "email"  : "sidorenkobw@gmail.com"
    };
};

DummyAgent.prototype.init = function (constants) {
    this.Constants = constants;
};

DummyAgent.prototype.onNewTick = function (status) {

};

DummyAgent.prototype.decision = function () {
    return {
        action: this.Constants.ACTION_IDLE
    };
};

DummyAgent.prototype.onNotification = function (notificationCode) {

};

exports.agentClass = DummyAgent;
<|endoftext|>