<commit_before>module.exports = jsFlowTracker

var debug = require('debug')('bittorrent-tracker:websocket-tracker')
var EventEmitter = require('events').EventEmitter
var hat = require('hat')
var inherits = require('inherits')
var jsFlow = require('./jsFlow')
var Peer = require('simple-peer')
//var Peer = require('jsflow-peer') // No changes needed.. :)

function jsFlowTracker (client, announceUrl, opts) {
  console.log('jsFlowTracker constructor is now running')
	var self=this
	EventEmitter.call(self)

  self.client = client
  self.storeIce = []
  self._opts = opts
  self._announceUrl = announceUrl
  self._peers = {} // peers (peer id -> peer)
  self._intervalMs = self.client._intervalMs // use client interval initially
  self._interval = null

  console.log(self);

  jsFlow.onRecievedUserId = function(userId) {
    console.log('userId', userId);
    self._peerID = userId;
  };

  // Starts jsFlow and handles the user ID
  jsFlow.run("31bc728296d8da7e14e132k",{sessionAuthURL: 'http://corslabs.com/jsflow',
                  debugMsg: true});

  jsFlow.addHandler('list', self._onData.bind(self)) // These messages are only sent by the TRACKER

  //Handlers for peer-2-peer WebRTC signalling using jsFlow
  jsFlow.addHandler('webrtc_offer', self._onWebRtcOffer.bind(self)) // only sent from other PEERS
  jsFlow.addHandler('webrtc_answer', self._onWebRtcAnswer.bind(self)) // only sent from other PEERS
  jsFlow.addHandler('webrtc_ice', self._onWebRtcIce.bind(self)) // only sent from other PEERS
}

jsFlowTracker.prototype.announce = function(opts){
  var self=this
  opts.info_hash = self.client._infoHash.toString('base64')
  opts.peer_id = self._peerId
  opts.trackerid = 'tracker'

  self._sendAnnounce(opts)
}

jsFlowTracker.prototype.setInterval= function (intervalMs) {
  var self = this
  clearInterval(self._interval)

  self._intervalMs = intervalMs
  if (intervalMs) {
    // HACK
    var update = self.announce.bind(self, self.client._defaultAnnounceOpts())
    self._interval = setInterval(update, self._intervalMs)
  }
}

jsFlowTracker.prototype._onData = function (payload, from) {
  console.log('Got Data from Tracker!!', payload, from);

  var self = this
  if (!(typeof payload === 'object' && payload !== null)) {
    return self.client.emit('warning', new Error('Invalid tracker response'))
  }

  if (payload.info_hash !== self.client._infoHash.toString('base64')) {
    return self.client.emit('warning', new Error('Invalid tracker response'))
  }

  var failure = payload['failure reason']
  if (failure) return self.client.emit('warning', new Error(failure))

  var warning = payload['warning message']
  if (warning) self.client.emit('warning', new Error(warning))

  var interval = payload.interval || payload['min interval']
  if (interval && !self._opts.interval && self._intervalMs !== 0) { //TODO: Fix so tracker can set interval (now defaults to 30 min)
  //if(interval) { // ALWAYS use the interval recommended by tracker
    self.setInterval(interval)
  }

  var trackerId = payload['tracker id']
  if (trackerId) {
    // If absent, do not discard previous trackerId value
    self._trackerId = trackerId
  }

  if (payload.complete) {
    self.client.emit('update', {
    announce: self._announceUrl,
    complete: payload.complete,
    incomplete: payload.incomplete
    })
  }

  // Handle received peers
  if (Array.isArray(payload.peers)) {
    payload.peers.forEach(function (peerInfo) { //For all peers we receive - Open a WebRTC peer connection
      var peerId = peerInfo.peerId;

      console.log('Will handle connection for peer: ', peerId);

      var peer = self._peers[peerId] = new Peer({
        initiator: true,
        trickle: true,
        config: self.client._rtcConfig,
        wrtc: self.client._wrtc
      })
      peer.id = peerId
      peer.on('close', function () {
        console.info('Peer connection destroyed, will remove from list of peers!')
        delete self._peers[from]
      })

      peer.on('signal', function (signalObject) {
        console.log('GOT SIGNAL, expecting offer/ice', signalObject);
        if(signalObject.candidate) { // The signalling is for ICE
          var params = {
              info_hash: self.client._infoHash.toString('base64'),
              peer_id: self._peerID,
              candidate: signalObject.candidate
            }

            jsFlow.messageUser(peerId, params, 'webrtc_ice');
        }
        else { // The signalling is for an OFFER
          offer = signalObject;
          if (peer.readyForIce) {
            console.log('self: ', self)
            processStoredCandidates(self)
          }
          var params = {
            info_hash: self.client._infoHash.toString('base64'),
            peer_id: self._peerID,
            offer: offer
          }

          jsFlow.messageUser(peerId, params, 'webrtc_offer');
        }
      })
    })
  };
}

jsFlowTracker.prototype._onWebRtcOffer = function (payload, from) {
  var self = this
  if(!self._peers[from]) { //Verify we haven't already received an offer from this user
    var peer = self._peers[from] = new Peer({
      trickle: true,
      config: self.client._rtcConfig,
      wrtc: self.client._wrtc
    })
    peer.id = from
    peer.on('close', function () {
      console.info('Peer connection destroyed, will remove from list of peers!')
      delete self._peers[from]
    })

    peer.on('signal', function (signalObject) {
      console.log('GOT SIGNAL, expecting answer/ice', signalObject);
      if(signalObject.candidate) { // The signalling is for ICE
        var params = {
          info_hash: self.client._infoHash.toString('base64'),
          peer_id: self._peerID,
          candidate: signalObject.candidate
        }

        jsFlow.messageUser(from, params, 'webrtc_ice');
      }
      else { // The signalling is for an ANSWER
        answer = signalObject;
        var params = {
          info_hash: self.client._infoHash.toString('base64'),
          peer_id: self._peerID,
          answer: answer
        }

        jsFlow.messageUser(from, params, 'webrtc_answer');
      }
    })

    peer.signal(payload.offer)

    self.client.emit('peer', peer)
  }
  else {
    console.error('Already have peer connection with user: ' + from);
  }
}

jsFlowTracker.prototype._onWebRtcAnswer = function (payload, from) {
  var self = this
  var peer = self._peers[from]
  if(peer) {
    peer.id = from
    peer.signal(payload.answer)
    self.client.emit('peer', peer)
  }
  else {
    console.error('Got answer on peer connection that does not exist, from user: ' + from);
  }
}

jsFlowTracker.prototype._onWebRtcIce = function (payload, from) {
  var self = this

  if (from.readyForIce) {

    console.log('WILL ADD ICE CANDIDATE!!!');

    var peer = self._peers[from]
    if(peer) {
      if(payload.candidate) {
        peer.signal(payload)
      }
    else {
      console.error('Candidate did not exist as expected in payload', payload);
    }
  }
  else {
    console.error('Candidate refers to a peer not yet created. Buffering ICE!', payload);
    self.storeIce.push(payload.candidate)
    }
  }
}
jsFlowTracker.prototype._sendAnnounce = function (params) {
  var self = this
  jsFlow.messageUser(params.trackerid, params, 'announce')
}

function processStoredCandidates (tracker, from) {
  console.log("Processing stored candidates.")
  tracker.storeIce.forEach(function(candidate){
    var peer = tracker._peers[from]
    if(peer) {
      peer.signal(candidate)
    }
    else {
    console.error('Candidate did not exist as expected in payload', payload);
    }
  })
}
<commit_msg>Minor fixes<commit_after>module.exports = jsFlowTracker

var debug = require('debug')('bittorrent-tracker:websocket-tracker')
var EventEmitter = require('events').EventEmitter
var hat = require('hat')
var inherits = require('inherits')
var jsFlow = require('./jsFlow')
var Peer = require('simple-peer')
//var Peer = require('jsflow-peer') // No changes needed.. :)

function jsFlowTracker (client, announceUrl, opts) {
  console.log('jsFlowTracker constructor is now running')
	var self=this
	EventEmitter.call(self)

  self.client = client
  self.storeIce = []
  self._opts = opts
  self._announceUrl = announceUrl
  self._peers = {} // peers (peer id -> peer)
  self._intervalMs = self.client._intervalMs // use client interval initially
  self._interval = null

  console.log(self);

  jsFlow.onRecievedUserId = function(userId) {
    console.log('userId', userId);
    self._peerID = userId;
  };

  // Starts jsFlow and handles the user ID
  jsFlow.run("31bc728296d8da7e14e132k",{sessionAuthURL: 'http://corslabs.com/jsflow',
                  debugMsg: true});

  jsFlow.addHandler('list', self._onData.bind(self)) // These messages are only sent by the TRACKER

  //Handlers for peer-2-peer WebRTC signalling using jsFlow
  jsFlow.addHandler('webrtc_offer', self._onWebRtcOffer.bind(self)) // only sent from other PEERS
  jsFlow.addHandler('webrtc_answer', self._onWebRtcAnswer.bind(self)) // only sent from other PEERS
  jsFlow.addHandler('webrtc_ice', self._onWebRtcIce.bind(self)) // only sent from other PEERS
}

jsFlowTracker.prototype.announce = function(opts){
  var self=this
  opts.info_hash = self.client._infoHash.toString('base64')
  opts.peer_id = self._peerId
  opts.trackerid = 'tracker'

  self._sendAnnounce(opts)
}

jsFlowTracker.prototype.setInterval= function (intervalMs) {
  var self = this
  clearInterval(self._interval)

  self._intervalMs = intervalMs
  if (intervalMs) {
    // HACK
    var update = self.announce.bind(self, self.client._defaultAnnounceOpts())
    self._interval = setInterval(update, self._intervalMs)
  }
}

jsFlowTracker.prototype._onData = function (payload, from) {
  console.log('Got Data from Tracker!!', payload, from);

  var self = this
  if (!(typeof payload === 'object' && payload !== null)) {
    return self.client.emit('warning', new Error('Invalid tracker response'))
  }

  if (payload.info_hash !== self.client._infoHash.toString('base64')) {
    return self.client.emit('warning', new Error('Invalid tracker response'))
  }

  var failure = payload['failure reason']
  if (failure) return self.client.emit('warning', new Error(failure))

  var warning = payload['warning message']
  if (warning) self.client.emit('warning', new Error(warning))

  var interval = payload.interval || payload['min interval']
  if (interval && !self._opts.interval && self._intervalMs !== 0) { //TODO: Fix so tracker can set interval (now defaults to 30 min)
  //if(interval) { // ALWAYS use the interval recommended by tracker
    self.setInterval(interval)
  }

  var trackerId = payload['tracker id']
  if (trackerId) {
    // If absent, do not discard previous trackerId value
    self._trackerId = trackerId
  }

  if (payload.complete) {
    self.client.emit('update', {
    announce: self._announceUrl,
    complete: payload.complete,
    incomplete: payload.incomplete
    })
  }

  // Handle received peers
  if (Array.isArray(payload.peers)) {
    payload.peers.forEach(function (peerInfo) { //For all peers we receive - Open a WebRTC peer connection
      var peerId = peerInfo.peerId;

      console.log('Will handle connection for peer: ', peerId);

      var peer = self._peers[peerId] = new Peer({
        initiator: true,
        trickle: true,
        config: self.client._rtcConfig,
        wrtc: self.client._wrtc
      })
      peer.id = peerId
      peer.on('close', function () {
        console.info('Peer connection destroyed, will remove from list of peers!')
        delete self._peers[from]
      })

      peer.on('signal', function (signalObject) {
        console.log('GOT SIGNAL, expecting offer/ice', signalObject);
        if(signalObject.candidate) { // The signalling is for ICE
          var params = {
              info_hash: self.client._infoHash.toString('base64'),
              peer_id: self._peerID,
              candidate: signalObject.candidate
            }

            jsFlow.messageUser(peerId, params, 'webrtc_ice');
        }
        else { // The signalling is for an OFFER
          offer = signalObject;
          if (peer.readyForIce) {
            console.log('self: ', self)
            processStoredCandidates(self)
          }
          var params = {
            info_hash: self.client._infoHash.toString('base64'),
            peer_id: self._peerID,
            offer: offer
          }

          jsFlow.messageUser(peerId, params, 'webrtc_offer');
        }
      })
    })
  };
}

jsFlowTracker.prototype._onWebRtcOffer = function (payload, from) {
  var self = this
  if(!self._peers[from]) { //Verify we haven't already received an offer from this user
    var peer = self._peers[from] = new Peer({
      trickle: true,
      config: self.client._rtcConfig,
      wrtc: self.client._wrtc
    })
    peer.id = from
    peer.on('close', function () {
      console.info('Peer connection destroyed, will remove from list of peers!')
      delete self._peers[from]
    })

    peer.on('signal', function (signalObject) {
      console.log('GOT SIGNAL, expecting answer/ice', signalObject);
      if(signalObject.candidate) { // The signalling is for ICE
        var params = {
          info_hash: self.client._infoHash.toString('base64'),
          peer_id: self._peerID,
          candidate: signalObject.candidate
        }

        jsFlow.messageUser(from, params, 'webrtc_ice');
      }
      else { // The signalling is for an ANSWER
        answer = signalObject;
        var params = {
          info_hash: self.client._infoHash.toString('base64'),
          peer_id: self._peerID,
          answer: answer
        }

        jsFlow.messageUser(from, params, 'webrtc_answer');
      }
    })

    peer.signal(payload.offer)

    self.client.emit('peer', peer)
  }
  else {
    console.error('Already have peer connection with user: ' + from);
  }
}

jsFlowTracker.prototype._onWebRtcAnswer = function (payload, from) {
  var self = this
  var peer = self._peers[from]
  if(peer) {
    peer.id = from
    peer.signal(payload.answer)
    self.client.emit('peer', peer)
  }
  else {
    console.error('Got answer on peer connection that does not exist, from user: ' + from);
  }
}

jsFlowTracker.prototype._onWebRtcIce = function (payload, from) {
  var self = this

  if (from.readyForIce) {

    console.log('WILL ADD ICE CANDIDATE!!!');

    var peer = self._peers[from]
    if(peer) {
      if(payload.candidate) {
        peer.signal(payload)
      }
    else {
      console.error('Candidate did not exist as expected in payload', payload);
    }
  }
  else {
    console.error('Candidate refers to a peer not yet created. Buffering ICE!', payload);
    self.storeIce.push(payload.candidate)
    }
  }
}
jsFlowTracker.prototype._sendAnnounce = function (params) {
  var self = this
  jsFlow.messageUser(params.trackerid, params, 'announce')
}

function processStoredCandidates (tracker, from) {
  console.log("Processing stored candidates.")
  var peer = tracker._peers[from]
  tracker.storeIce.forEach(function(candidate){
    if(peer) {
      peer.signal(candidate)
    }
    else {
      console.error('Candidate did not exist as expected in payload', payload);
    }
  })
}
<|endoftext|>


<commit_before>/* global console, io */

var Cheese = {
  routes: {},
  db: {},
  events: {},
  messageHandlers: {},
  socket: {}
};

(function ($, DOM, diff) {
  var applyDiff = diff.applyDiff;
  var createDiff = diff.createDiff;
  var copyObject = diff.copyObject;
  var DOMUtils = DOM;

  $.ajax('/socket.io/socket.io.js', { async: true }).done(handleMessages);

  var serverDB = {};
  var initialized = false;
  var socket;
  var startup = function () {};
  var domLoaded = false;

  function routeMatchesPattern (pattern, route) {
    pattern = pattern.split('/').filter(function (i) { return i !== ''; });
    route = route.split('/').filter(function (i) { return i !== ''; });

    if (pattern.length !== route.length) return false;

    var args = {};

    for (var i = 0; i < pattern.length; i++)
      if (pattern[i].charAt(0) === ':') args[i] = pattern[i];

    for (var j = 0; j < route.length; j++) {
      if (args[j] === undefined && route[j] !== pattern[j]) return false;
    }

    return true;
  }

  function argsForRoute (pattern, route) {
    if (! routeMatchesPattern(pattern, route)) return [];

    pattern = pattern.split('/').filter(function (i) { return i !== ''; });
    route = route.split('/').filter(function (i) { return i !== ''; });
    var args = [];

    for (var i = 0; i < pattern.length; i++)
      if (pattern[i].charAt(0) === ':') args.push(route[i]);

    return args;
  }

  function updateClient () {
    Cheese.db = copyObject(serverDB);
    Cheese.reload();
  }

  function updateServer () {
    if (! initialized) return;

    if (JSON.stringify(createDiff(serverDB, Cheese.db)) !== '{}') {
      socket.emit('msg', createDiff(serverDB, Cheese.db));
      serverDB = copyObject(Cheese.db);
    }
  };

  function initializeEvents () {
    for (var selector in Cheese.events)
      $(selector).off(Cheese.events[selector]).on(Cheese.events[selector]);
  }

  function handleMessages () {
    socket = io('http://' + window.location.hostname);

    socket.on('msg', function (diff) {
      applyDiff(diff, serverDB);
      updateClient();
    });

    socket.on('init', function (db) {
      serverDB = copyObject(db);
      Cheese.db = copyObject(db);
      initialized = true;
      startup();
      Cheese.reload();
      domLoaded = true;
    });

    socket.on('custom', function (msg) {
      if (Cheese.messageHandlers[msg.msg]) Cheese.messageHandlers[msg.msg](msg.args);
      Cheese.reload();
    });

    socket.on('reload', function () {
      window.location.reload(true);
    });
  };

  Cheese.socket.emit = function (m, d) {
    if (initialized) socket.emit('custom', { msg: m, args: d });
    else console.error('The client can only send messages after it has connected to the server.');
  };

  Cheese.socket.on = function (m, f) {
    Cheese.messageHandlers[m] = f;
  };

  Cheese.reload = function () {
    updateServer();

    var route = window.location.pathname;
    var args = [];
    for (var i in this.routes)
      if (routeMatchesPattern(i, window.location.pathname)) {
        args = argsForRoute(i, window.location.pathname);
        route = i;
        break;
      }

    var html = document.createElement('html');
    html.innerHTML = this.routes[route].apply(window, args);
    DOMUtils.updateDOMElement($('html'), $(html));
    initializeEvents();
  };

  Cheese.request = function (name, callback) {
    if (typeof callback === 'function') {
      $.ajax('/__static/' + name, { async: true }).done(callback);
      return undefined;
    }

    var content;
    $.ajax('/__static/' + name, { async: false }).done(function (data) { content = data; });
    return content;
  };

  Cheese.route = function (r, f) {
    this.routes[r] = f;
    if (routeMatchesPattern(r, window.location.pathname)) Cheese.reload();
  };

  Cheese.startup = function (f) {
    startup = f;
  };

  Cheese.event = function (event, selector, handler) {
    if (! this.events[selector]) this.events[selector] = {};
    this.events[selector][event] = function (e) {
      handler(e);
      Cheese.reload();
    };
    if (domLoaded) initializeEvents();
  };
})(jQuery, DOMUtils, diffUtils);

delete window.diffUtils;
delete window.DOMUtils;
delete window.jQuery;
delete window.$;
delete window.io;
<commit_msg>Make client requireable with browserify.<commit_after>
/* global console, io, require, module */

var Cheese = {
  routes: {},
  db: {},
  events: {},
  messageHandlers: {},
  socket: {}
};

if (typeof require === 'function' && typeof module === 'object') {
  var DOMUtils = require('./dom.js');
  var diffUtils = require('./diff.js');
  var jQuery = require('./jquery-1.10.2.min.js');
  var io = require('socket.io-client');

  module.exports = Cheese;

}

(function ($, DOM, diff) {
  var applyDiff = diff.applyDiff;
  var createDiff = diff.createDiff;
  var copyObject = diff.copyObject;
  var DOMUtils = DOM;

  $.ajax('/socket.io/socket.io.js', { async: true }).done(handleMessages);

  var serverDB = {};
  var initialized = false;
  var socket;
  var startup = function () {};
  var domLoaded = false;

  function routeMatchesPattern (pattern, route) {
    pattern = pattern.split('/').filter(function (i) { return i !== ''; });
    route = route.split('/').filter(function (i) { return i !== ''; });

    if (pattern.length !== route.length) return false;

    var args = {};

    for (var i = 0; i < pattern.length; i++)
      if (pattern[i].charAt(0) === ':') args[i] = pattern[i];

    for (var j = 0; j < route.length; j++) {
      if (args[j] === undefined && route[j] !== pattern[j]) return false;
    }

    return true;
  }

  function argsForRoute (pattern, route) {
    if (! routeMatchesPattern(pattern, route)) return [];

    pattern = pattern.split('/').filter(function (i) { return i !== ''; });
    route = route.split('/').filter(function (i) { return i !== ''; });
    var args = [];

    for (var i = 0; i < pattern.length; i++)
      if (pattern[i].charAt(0) === ':') args.push(route[i]);

    return args;
  }

  function updateClient () {
    Cheese.db = copyObject(serverDB);
    Cheese.reload();
  }

  function updateServer () {
    if (! initialized) return;

    if (JSON.stringify(createDiff(serverDB, Cheese.db)) !== '{}') {
      socket.emit('msg', createDiff(serverDB, Cheese.db));
      serverDB = copyObject(Cheese.db);
    }
  };

  function initializeEvents () {
    for (var selector in Cheese.events)
      $(selector).off(Cheese.events[selector]).on(Cheese.events[selector]);
  }

  function handleMessages () {
    socket = io('http://' + window.location.hostname);

    socket.on('msg', function (diff) {
      applyDiff(diff, serverDB);
      updateClient();
    });

    socket.on('init', function (db) {
      serverDB = copyObject(db);
      Cheese.db = copyObject(db);
      initialized = true;
      startup();
      Cheese.reload();
      domLoaded = true;
    });

    socket.on('custom', function (msg) {
      if (Cheese.messageHandlers[msg.msg]) Cheese.messageHandlers[msg.msg](msg.args);
      Cheese.reload();
    });

    socket.on('reload', function () {
      window.location.reload(true);
    });
  };

  Cheese.socket.emit = function (m, d) {
    if (initialized) socket.emit('custom', { msg: m, args: d });
    else console.error('The client can only send messages after it has connected to the server.');
  };

  Cheese.socket.on = function (m, f) {
    Cheese.messageHandlers[m] = f;
  };

  Cheese.reload = function () {
    updateServer();

    var route = window.location.pathname;
    var args = [];
    for (var i in this.routes)
      if (routeMatchesPattern(i, window.location.pathname)) {
        args = argsForRoute(i, window.location.pathname);
        route = i;
        break;
      }

    var html = document.createElement('html');
    html.innerHTML = this.routes[route].apply(window, args);
    DOMUtils.updateDOMElement($('html'), $(html));
    initializeEvents();
  };

  Cheese.request = function (name, callback) {
    if (typeof callback === 'function') {
      $.ajax('/__static/' + name, { async: true }).done(callback);
      return undefined;
    }

    var content;
    $.ajax('/__static/' + name, { async: false }).done(function (data) { content = data; });
    return content;
  };

  Cheese.route = function (r, f) {
    this.routes[r] = f;
    if (routeMatchesPattern(r, window.location.pathname)) Cheese.reload();
  };

  Cheese.startup = function (f) {
    startup = f;
  };

  Cheese.event = function (event, selector, handler) {
    if (! this.events[selector]) this.events[selector] = {};
    this.events[selector][event] = function (e) {
      handler(e);
      Cheese.reload();
    };
    if (domLoaded) initializeEvents();
  };
})(jQuery, DOMUtils, diffUtils);

delete window.diffUtils;
delete window.DOMUtils;
delete window.jQuery;
delete window.$;
delete window.io;
<|endoftext|>


<commit_before>const EventEmitter = require('events');

const moment = require('moment');
const CouchbaseChannelDisney = require('./couchbaseChannelDisney');
const Facilities = require('./disneyFacilityChannel');
const DebugLog = require('../debugPrint.js');
const Cache = require('../cache');

const DisneyUtil = require('./disneyUtil');

const sParkID = Symbol('Park ID');
const sChannel = Symbol('Channel');
const sFacilities = Symbol('Facilities');
const sBusVersion = Symbol('Bus Version');
const sPollInterval = Symbol('Polling Interval');

class DisneyLiveBusTimes extends EventEmitter {
  constructor(options = {}) {
    super();

    this[sParkID] = options.park_id === undefined ? 'wdw' : options.park_id;
    this[sBusVersion] = options.version === undefined ? '1_0' : options.version;

    // how often to poll for bus time updates (in seconds)
    this[sPollInterval] = options.pollinterval === undefined ? 10 : options.pollinterval;

    // setup our facilities channel, we need this to sync before we get our bus times
    this[sFacilities] = new Facilities({
      resort_id: this[sParkID],
    });

    // NOTE - whilst there is a live channel for "Outpost Depot at Disney's Fort Wilderness Resort & Campground"
    //  our list of resorts doesn't return the ID 144943 needed to subscribe
    //  However, channel 80010408 has exactly the same data, so we're getting it via the "The Cabins at Disney's Fort Wilderness Resort" resort instead
  }

  /**
   * Callback for "updated" event from our couchbase channel class
   * @param {Object} doc
   */
  OnDocUpdated(doc) {
    // console.log(JSON.stringify(doc, null, 2));
    const id = DisneyUtil.CleanID(doc.id);

    this[sFacilities].GetFacilityName(id).then((name) => {
      if (doc.destinations === undefined) {
        return;
      }

      // add human-readable times and names to each destination
      const destinationPromises = [];
      doc.destinations.forEach((dest) => {
        destinationPromises.push(this.ParseDestination(dest));
      });
      Promise.all(destinationPromises).then((destinations) => {
        // emit events for each updated bus stop
        // console.log(JSON.stringify(destinations, null, 2));

        destinations.forEach((dest) => {
          if (dest.arrivals !== undefined) {
            dest.arrivals.forEach((arrival) => {
              this.emit('busupdate', {
                from: name,
                from_id: id,
                to: dest.name,
                to_id: DisneyUtil.CleanID(dest.id),
                atStop: arrival.atStop,
                atStopHuman: arrival.atStop_human,
                atDestination: arrival.atDestination,
                atDestinationHuman: arrival.atDestination_human,
                frequency: dest.frequency,
                frequencyHuman: dest.frequency_human,
              });
            });
          } else {
            // emit event with boring "every 20 minutes" update
            this.emit('busupdate', {
              from: name,
              from_id: id,
              to: dest.name,
              to_id: DisneyUtil.CleanID(dest.id),
              frequency: dest.frequency,
              frequencyHuman: dest.frequency_human,
            });
          }
        });
      });
    });
  }

  ParseDestination(destination) {
    return this[sFacilities].GetFacilityName(destination.id).then((name) => {
      destination.name = name;

      // use moment to make some human-readable times
      const now = moment();

      if (destination.frequency !== undefined) {
        const busFrequency = now.clone().add(destination.frequency, 'seconds');
        destination.frequency_human = `every ${busFrequency.from(now, true)}`;
      }

      // add human-readable strings to each arrival
      if (destination.arrivals !== undefined) {
        destination.arrivals.forEach((arrival) => {
          arrival.atStop_human = moment(arrival.atStop).from(now);
          arrival.atDestination_human = moment(arrival.atDestination).from(now);
        });
      }

      return Promise.resolve(destination);
    });
  }

  /**
   * Start fetching live bus time updates
   */
  Start() {
    if (!this[sFacilities].Synced) {
      // start facilities syncing,
      return this[sFacilities].Start().then(() => {
        if (this[sChannel] === undefined) {
          // set this to something in case multiple callbacks arrive before we set it up
          this[sChannel] = 1;

          // find all our resorts and build channel names
          return this.GetChannelNames().then((channels) => {
            // create our channel for fetching bus times
            this[sChannel] = new CouchbaseChannelDisney({
              dbName: `${this[sParkID]}_buses`,
              channel: channels.join(','),
              longpollDelay: 10, // delay each longpoll by 10 seconds (to avoid constant flooding of updates)
            });

            // setup puller hooks for document updates
            this[sChannel].on('updated', this.OnDocUpdated.bind(this));
            this[sChannel].on('error', this.Log.bind(this));

            // start the channel fetching documents
            this[sChannel].Start();
          });
        }

        return Promise.resolve();
      });
    }

    return Promise.resolve();
  }

  GetChannelNames() {
    return Cache.DB().then(db => new Promise((resolve, reject) => {
      db.all('SELECT id, name FROM disneyFacilities WHERE entityType = ? AND resort_code = ?', ['resort', this[sParkID]], (err, rows) => {
        if (err) {
          return reject(err);
        }

        const channels = [];
        rows.forEach((row) => {
          channels.push(`${this[sParkID]}.arrivals.${row.id};entityType=resort.${this[sBusVersion]}`);
        });

        return resolve(channels);
      });
    }));
  }

  /**
   * Debug print a message (when NODE_DEBUG=themeparks is set in environment)
   * @param {...*} ToPrint Objects/strings to print
   * */
  Log(...args) {
    return DebugLog.apply(null, [`${this.constructor.name}:`, ...args]);
  }
}

module.exports = DisneyLiveBusTimes;
<commit_msg>[~] Report IDs as numbers from the disneyBus test script<commit_after>const EventEmitter = require('events');

const moment = require('moment');
const CouchbaseChannelDisney = require('./couchbaseChannelDisney');
const Facilities = require('./disneyFacilityChannel');
const DebugLog = require('../debugPrint.js');
const Cache = require('../cache');

const DisneyUtil = require('./disneyUtil');

const sParkID = Symbol('Park ID');
const sChannel = Symbol('Channel');
const sFacilities = Symbol('Facilities');
const sBusVersion = Symbol('Bus Version');
const sPollInterval = Symbol('Polling Interval');

class DisneyLiveBusTimes extends EventEmitter {
  constructor(options = {}) {
    super();

    this[sParkID] = options.park_id === undefined ? 'wdw' : options.park_id;
    this[sBusVersion] = options.version === undefined ? '1_0' : options.version;

    // how often to poll for bus time updates (in seconds)
    this[sPollInterval] = options.pollinterval === undefined ? 10 : options.pollinterval;

    // setup our facilities channel, we need this to sync before we get our bus times
    this[sFacilities] = new Facilities({
      resort_id: this[sParkID],
    });

    // NOTE - whilst there is a live channel for "Outpost Depot at Disney's Fort Wilderness Resort & Campground"
    //  our list of resorts doesn't return the ID 144943 needed to subscribe
    //  However, channel 80010408 has exactly the same data, so we're getting it via the "The Cabins at Disney's Fort Wilderness Resort" resort instead
  }

  /**
   * Callback for "updated" event from our couchbase channel class
   * @param {Object} doc
   */
  OnDocUpdated(doc) {
    // console.log(JSON.stringify(doc, null, 2));
    const id = DisneyUtil.CleanID(doc.id);

    this[sFacilities].GetFacilityName(id).then((name) => {
      if (doc.destinations === undefined) {
        return;
      }

      // add human-readable times and names to each destination
      const destinationPromises = [];
      doc.destinations.forEach((dest) => {
        destinationPromises.push(this.ParseDestination(dest));
      });
      Promise.all(destinationPromises).then((destinations) => {
        // emit events for each updated bus stop
        // console.log(JSON.stringify(destinations, null, 2));

        destinations.forEach((dest) => {
          if (dest.arrivals !== undefined) {
            dest.arrivals.forEach((arrival) => {
              this.emit('busupdate', {
                from: name,
                from_id: Number(id),
                to: dest.name,
                to_id: Number(DisneyUtil.CleanID(dest.id)),
                atStop: arrival.atStop,
                atStopHuman: arrival.atStop_human,
                atDestination: arrival.atDestination,
                atDestinationHuman: arrival.atDestination_human,
                frequency: dest.frequency,
                frequencyHuman: dest.frequency_human,
              });
            });
          } else {
            // emit event with boring "every 20 minutes" update
            this.emit('busupdate', {
              from: name,
              from_id: Number(id),
              to: dest.name,
              to_id: Number(DisneyUtil.CleanID(dest.id)),
              frequency: dest.frequency,
              frequencyHuman: dest.frequency_human,
            });
          }
        });
      });
    });
  }

  ParseDestination(destination) {
    return this[sFacilities].GetFacilityName(destination.id).then((name) => {
      destination.name = name;

      // use moment to make some human-readable times
      const now = moment();

      if (destination.frequency !== undefined) {
        const busFrequency = now.clone().add(destination.frequency, 'seconds');
        destination.frequency_human = `every ${busFrequency.from(now, true)}`;
      }

      // add human-readable strings to each arrival
      if (destination.arrivals !== undefined) {
        destination.arrivals.forEach((arrival) => {
          arrival.atStop_human = moment(arrival.atStop).from(now);
          arrival.atDestination_human = moment(arrival.atDestination).from(now);
        });
      }

      return Promise.resolve(destination);
    });
  }

  /**
   * Start fetching live bus time updates
   */
  Start() {
    if (!this[sFacilities].Synced) {
      // start facilities syncing,
      return this[sFacilities].Start().then(() => {
        if (this[sChannel] === undefined) {
          // set this to something in case multiple callbacks arrive before we set it up
          this[sChannel] = 1;

          // find all our resorts and build channel names
          return this.GetChannelNames().then((channels) => {
            // create our channel for fetching bus times
            this[sChannel] = new CouchbaseChannelDisney({
              dbName: `${this[sParkID]}_buses`,
              channel: channels.join(','),
              longpollDelay: 10, // delay each longpoll by 10 seconds (to avoid constant flooding of updates)
            });

            // setup puller hooks for document updates
            this[sChannel].on('updated', this.OnDocUpdated.bind(this));
            this[sChannel].on('error', this.Log.bind(this));

            // start the channel fetching documents
            this[sChannel].Start();
          });
        }

        return Promise.resolve();
      });
    }

    return Promise.resolve();
  }

  GetChannelNames() {
    return Cache.DB().then(db => new Promise((resolve, reject) => {
      db.all('SELECT id, name FROM disneyFacilities WHERE entityType = ? AND resort_code = ?', ['resort', this[sParkID]], (err, rows) => {
        if (err) {
          return reject(err);
        }

        const channels = [];
        rows.forEach((row) => {
          channels.push(`${this[sParkID]}.arrivals.${row.id};entityType=resort.${this[sBusVersion]}`);
        });

        return resolve(channels);
      });
    }));
  }

  /**
   * Debug print a message (when NODE_DEBUG=themeparks is set in environment)
   * @param {...*} ToPrint Objects/strings to print
   * */
  Log(...args) {
    return DebugLog.apply(null, [`${this.constructor.name}:`, ...args]);
  }
}

module.exports = DisneyLiveBusTimes;
<|endoftext|>


<commit_before>$(main);

function main() {
    $.getJSON('./data.json', function(d) {
        var data = attachColors(groupData(d));
        var state = initializeState(data.providers);
        var update = updateAll.bind(undefined, $('.content.row'), data, state);
        var updateWithRoute, router;

        $(window).on('resize', update);

        router = initializeRouter(state, update);

        updateWithRoute = union(update, updateRoute.bind(undefined, state, router));

        createControls($('.allControlsContainer'), state, updateWithRoute);
        createLegend($('.legendContainer'), data, state, updateWithRoute);

        update();
    });

    function union() {
        var args = arguments;

        return function() {
            for(var i = 0; i < args.length; i++) {
                args[i]();
            }
        };
    }

    function initializeRouter(state, update) {
        var Router = Backbone.Router.extend({
            routes: {
                ':providers': 'providers',
                ':providers/:type': 'providersWithType',
                ':providers/:type/:amount': 'all'
            },
            providers: function(providers) {
                state.providers = providers.split(',');

                update();
            },
            providersWithType: function(providers, type) {
                state.type = type;

                this.providers(providers);
            },
            all: function(providers, type, amount) {
                state.amount = amount;

                this.providersWithType(providers, type);
            }
        });
        var router = new Router();

        Backbone.history.start();

        return router;
    }

    function updateRoute(state, router) {
        var providers = state.providers.toString();
        var type = state.type;
        var amount = state.amount;
        var url = '/' + providers + '/' + type + '/' + amount;

        router.navigate(url, {replace: true});

        localStorage.setItem('index', '/#' + url);
    }

    function initializeState(data) {
        return {
            type: '',
            amount: '',
            providers: Object.keys(data).map(idfy)
        };
    }

    function groupData(data) {
        var providers = {};
        var wholeDayInMs = 1000 * 60 * 60 * 24;

        data.providers.forEach(function(v) {
            var name = getProviderName(v.name);

            if(!(name in providers)) providers[name] = {
                types: {},
                host: getHost(v.host)
            };
            if(!(v.type in providers[name].types)) providers[name].types[v.type] = {
                latency: v.latency,
                uptime: v.downtime.map(function(v) {
                    if(v) return parseFloat(((1 - (v / wholeDayInMs)) * 100).toFixed(3));

                    return 100;
                })
            };
        });

        return {
            providers: providers,
            firstDate: new XDate(data.firstDate * 1000),
            lastDate: new XDate(data.lastDate * 1000)
        };
    }

    function getHost(name) {
        var hosts = {
            'ajax.aspnetcdn.com': 'www.asp.net/ajaxlibrary/cdn.ashx',
            'ajax.googleapis.com': 'developers.google.com/speed/libraries/'
        };

        if(name in hosts) return hosts[name];

        return name;
    }

    function toFixed(a) {
        return function(v) {
            return parseFloat(v.toFixed(a), 10);
        };
    }

    function attachColors(data) {
        var i = 0;

        for(var name in data.providers) {
            data.providers[name].color = getColor(i);

            i++;
        }

        return data;
    }

    function createControls($p, state, update) {
        createTypes($p, state, update);
        createAmounts($p, state, update);
    }

    function createTypes($p, state, update) {
        $controls($p, state, update, 'type', ['ping', 'http', 'https'], 'ping');
    }

    function createAmounts($p, state, update) {
        $controls($p, state, update, 'amount', [7, 14, 30, 90], 30);
    }

    function $controls($p, state, update, type, items, selected) {
        var $e = $('<div>',
            {'class': 'controlsContainer ' + (type + 's')}).appendTo($p);
        items.forEach($control.bind(undefined, $e, state, type, update));

        if(!state[type]) {
            $('.' + selected, $e).addClass('selected');
            state[type] = selected;
        }
    }

    function $control($p, state, type, update, name) {
        var $e = $('<a>', {'class': 'control ' + type + ' ' + name, href: '#'}).
                text(name).on('click', function(e) {
            e.preventDefault();

            $e.siblings().removeClass('selected').removeClass('label');
            $e.addClass('selected label');

            state[type] = name;

            update();
        }).appendTo($p);

        if(state[type] == name) $e.addClass('selected');
    }

    function updateAll($p, data, state) {
        updateCharts($p, data, state);
        updateLegend($p, data, state);
    }

    function updateCharts($p, data, state) {
        updateChart($('.uptimeContainer'), data, state, 'uptime', 100, function(v) {
            var val = ((100 - v.value) / 100 * 60 * 24).toFixed(2);

            v.value += ' %, ' + val + ' min downtime';

            return v;
        });
        updateChart($('.latencyContainer'), data, state, 'latency', 300, function(v) {
            v.value += ' ms';

            return v;
        });
    }

    function updateChart($p, data, state, category, height, tooltipCb) {
        var $canvas = $('.chart:first', $p);
        var ctx, width, $help;

        if(!$canvas.length) {
            $help = $('.help', $p);
            $help.qtip({content: $help.text()}).text('?');
            $canvas = $('<canvas>', {'class': 'chart'}).appendTo($p);
        }

        // dynamic width (parent width might change)
        width = $canvas.parent().width();

        $canvas.attr({width: width, height: height});

        ctx = $canvas[0].getContext('2d');

        chart(ctx, getData(data, state, category), tooltipCb);
    }

    function updateLegend($p, data, state) {
        updateType(data, state, 'latency', function(state, values) {
            return average(values.slice(-state.amount)).toFixed(1) + ' ms';
        });

        updateType(data, state, 'uptime', function(state, values) {
            return average(values.slice(-state.amount)).toFixed(3) + ' %';
        });
    }

    function updateType(data, state, type, calculateValue) {
        var providers = data.providers;
        var tdClass, value, name;

        for(name in providers) {
            value = 'NA';
            provider = providers[name];
            tdClass = idfy(name);

            if(state.type in provider.types) {
                values = provider.types[state.type][type];

                value = values && calculateValue(state, values);
            }

            $('.' + type + '.' + tdClass).text(value);
        }
    }

    function chart(ctx, data, tooltipCb) {
        // https://github.com/nnnick/Chart.js/issues/76
        var min = minimum(data.datasets.map(op(minimum, prop('data'))));
        var max = maximum(data.datasets.map(op(maximum, prop('data'))));
        var opts;

        if(max == min) {
            opts = {
                animation: true,
                animationSteps: 10,
                datasetFill: false,
                scaleOverride : true,
                scaleSteps : 3,
                scaleStepWidth : 1,
                scaleStartValue : max - 3
            };
        }
        else {
            opts = {
                animation: true,
                animationSteps: 10,
                datasetFill: false,
                pointDot: true,
                scaleShowGridLines: true,
                scaleGridLineColor: 'rgba(224,224,224,0.5)',
                scaleGridLineWidth: 1,
                pointDotRadius: 3,
                bezierCurve: false
            };
        }

        new Chart(ctx, {}, tooltipCb).Line(data, opts);
    }

    function op(fn, accessor) {
        return function(v) {
            return fn(accessor(v));
        };
    }

    function minimum(a) {
        return a.slice().sort()[0];
    }

    function maximum(a) {
        return a.slice().sort()[a.length - 1];
    }

    function createLegend($container, data, state, update) {
        var $table = $('<table>', {'class': 'legend'}).appendTo($container);
        var $header = $('<tr>').appendTo($table);
        var providers = data.providers;
        var provider, color, name, host;

        $('<th>', {'class': 'names'}).html(' ').appendTo($header);

        for(name in providers) {
            provider = providers[name];
            host = provider.host;
            color = provider.color;

            createTh(state, name, host, color, update).appendTo($header);
        }

        createRow(state, providers, 'latency', 'ms').appendTo($table);
        createRow(state, providers, 'uptime', '%').appendTo($table);
    }

    function createTh(state, name, host, color, update) {
        var thClass = idfy(name);
        var $e = $('<th>', {'class': 'cdn ' + thClass}).css({
            'background-color': colorToHex(color)
        });
        $('<a>', {href: 'http://' + host}).css({
            'color': colorToHex(flipColor(color))
        }).text(name).appendTo($e);

        var $icon = $('<i>', {'class': 'visibility foundicon-eyeball'}).on('click', function() {
            $icon.toggleClass('selected');

            toggleItem(state.providers, idfy(name), $icon.hasClass('selected'));

            update();
        }).appendTo($e);

        if(within(state.providers, thClass)) $icon.addClass('selected');

        return $e;
    }

    function toggleItem(arr, k, v) {
        var i;

        if(v) {
            if(!within(arr, k)) arr.push(k);
        }
        else {
            i = arr.indexOf(k);

            if(i >= 0) arr.splice(i, 1);
        }
    }

    function idfy(val) {
        return val.toLowerCase().replace(/[ \-\(\)]+/g, '_').replace(/\.+/g, '');
    }

    function createRow(state, providers, type, unit) {
        var $row = $('<tr>');
        var name, provider, values, value, tdClass;

        $('<td>', {'class': type}).text(type).appendTo($row);

        for(name in providers) {
            provider = providers[name];
            tdClass = idfy(name);

            $('<td>', {'class': type + ' ' + tdClass}).appendTo($row);
        }

        return $row;
    }

    function average(arr) {
        if(!arr) return;

        return sum(arr) / arr.length;
    }

    function sum(arr) {
        if(!arr) return;

        return arr.reduce(function(a, b) {
            return a + b;
        });
    }

    function getData(data, state, category) {
        return {
            labels: getLabels(data.lastDate, state.amount),
            datasets: getDatasets(data.providers, state, category)
        };
    }

    function getLabels(lastDate, amount) {
        var ret = [];
        var len = amount;
        var i, d;

        // TODO: move these rules elsewhere
        if(amount == 90) len = 30;

        for(i = 0; i < len; i++) {
            d = lastDate.clone();

            // TODO: move these rules elsewhere
            if(amount == 90) d.addDays(-(90 - i * 3 - 1));
            else d.addDays(-(amount - i - 1));

            ret.push(d.toString('dd MMM'));
        }

        return ret;
    }

    function getDatasets(providers, state, category) {
        var ret = [];
        var provider, color, data, offset;

        for(var name in providers) {
            provider = providers[name];
            color = colorToHex(provider.color);

            if(!within(state.providers, idfy(name))) continue;
            if(!(state.type in provider.types)) continue;
            if(!(category in provider.types[state.type])) continue;

            data = provider.types[state.type][category];

            offset = state.amount - data.length;
            if(offset > 0) data = zeroes(offset).concat(data);
            else data = data.slice(-state.amount);

            ret.push({
                strokeColor: color,
                pointColor: color,
                pointStrokeColor: color,
                data: pickPoints(state.amount, data)
            });
        }

        return ret;
    }

    function pickPoints(amount, data) {
        // TODO: maybe there's a better way? now it skips the contribution of
        // the neighboring days
        if(amount == 90) return everyNth(3, data);

        return data;
    }

    function everyNth(n, data) {
        return data.filter(function(v, i) {
            return !(i % n);
        });
    }

    function zeroes(amount) {
        var ret = [];
        var i;

        for(i = 0; i < amount; i++) ret.push(0);

        return ret;
    }

    function within(arr, v) {
        return arr.indexOf(v) >= 0;
    }

    function getProviderName(fullname) {
        return fullname.split(' ')[0];
    }

    function flipColor(color) {
        /* flips color to black or white based on brightness */
        /* http://javascriptrules.com/2009/08/05/css-color-brightness-contrast-using-javascript/ */
        var brightness = (color[0] * 299 + color[1] * 587 + color[2] * 114) / 1000;

        if(brightness > 125) return [0, 0, 0];

        return [255, 255, 255];
    }

    function colorToHex(color) {
        return '#' + color.map(function(v) {
            return v.toString(16);
        }).join('');
    }

    function getColor(index) {
        return [
            [216, 79, 68],
            [174, 227, 214],
            [243, 213, 162],
            [93, 150, 215],
            [68, 68, 68],
            [227, 136, 235],
            [170, 221, 94]
        ][index];
    }

    function unique(arr) {
        var ret = {};

        arr.forEach(function(v) {
            ret[v] = true;
        });

        return Object.keys(ret);
    }

    function prop(name) {
        return function(v) {
            return v[name];
        };
    }
}
<commit_msg>Invert color of visibility toggles too<commit_after>$(main);

function main() {
    $.getJSON('./data.json', function(d) {
        var data = attachColors(groupData(d));
        var state = initializeState(data.providers);
        var update = updateAll.bind(undefined, $('.content.row'), data, state);
        var updateWithRoute, router;

        $(window).on('resize', update);

        router = initializeRouter(state, update);

        updateWithRoute = union(update, updateRoute.bind(undefined, state, router));

        createControls($('.allControlsContainer'), state, updateWithRoute);
        createLegend($('.legendContainer'), data, state, updateWithRoute);

        update();
    });

    function union() {
        var args = arguments;

        return function() {
            for(var i = 0; i < args.length; i++) {
                args[i]();
            }
        };
    }

    function initializeRouter(state, update) {
        var Router = Backbone.Router.extend({
            routes: {
                ':providers': 'providers',
                ':providers/:type': 'providersWithType',
                ':providers/:type/:amount': 'all'
            },
            providers: function(providers) {
                state.providers = providers.split(',');

                update();
            },
            providersWithType: function(providers, type) {
                state.type = type;

                this.providers(providers);
            },
            all: function(providers, type, amount) {
                state.amount = amount;

                this.providersWithType(providers, type);
            }
        });
        var router = new Router();

        Backbone.history.start();

        return router;
    }

    function updateRoute(state, router) {
        var providers = state.providers.toString();
        var type = state.type;
        var amount = state.amount;
        var url = '/' + providers + '/' + type + '/' + amount;

        router.navigate(url, {replace: true});

        localStorage.setItem('index', '/#' + url);
    }

    function initializeState(data) {
        return {
            type: '',
            amount: '',
            providers: Object.keys(data).map(idfy)
        };
    }

    function groupData(data) {
        var providers = {};
        var wholeDayInMs = 1000 * 60 * 60 * 24;

        data.providers.forEach(function(v) {
            var name = getProviderName(v.name);

            if(!(name in providers)) providers[name] = {
                types: {},
                host: getHost(v.host)
            };
            if(!(v.type in providers[name].types)) providers[name].types[v.type] = {
                latency: v.latency,
                uptime: v.downtime.map(function(v) {
                    if(v) return parseFloat(((1 - (v / wholeDayInMs)) * 100).toFixed(3));

                    return 100;
                })
            };
        });

        return {
            providers: providers,
            firstDate: new XDate(data.firstDate * 1000),
            lastDate: new XDate(data.lastDate * 1000)
        };
    }

    function getHost(name) {
        var hosts = {
            'ajax.aspnetcdn.com': 'www.asp.net/ajaxlibrary/cdn.ashx',
            'ajax.googleapis.com': 'developers.google.com/speed/libraries/'
        };

        if(name in hosts) return hosts[name];

        return name;
    }

    function toFixed(a) {
        return function(v) {
            return parseFloat(v.toFixed(a), 10);
        };
    }

    function attachColors(data) {
        var i = 0;

        for(var name in data.providers) {
            data.providers[name].color = getColor(i);

            i++;
        }

        return data;
    }

    function createControls($p, state, update) {
        createTypes($p, state, update);
        createAmounts($p, state, update);
    }

    function createTypes($p, state, update) {
        $controls($p, state, update, 'type', ['ping', 'http', 'https'], 'ping');
    }

    function createAmounts($p, state, update) {
        $controls($p, state, update, 'amount', [7, 14, 30, 90], 30);
    }

    function $controls($p, state, update, type, items, selected) {
        var $e = $('<div>',
            {'class': 'controlsContainer ' + (type + 's')}).appendTo($p);
        items.forEach($control.bind(undefined, $e, state, type, update));

        if(!state[type]) {
            $('.' + selected, $e).addClass('selected');
            state[type] = selected;
        }
    }

    function $control($p, state, type, update, name) {
        var $e = $('<a>', {'class': 'control ' + type + ' ' + name, href: '#'}).
                text(name).on('click', function(e) {
            e.preventDefault();

            $e.siblings().removeClass('selected').removeClass('label');
            $e.addClass('selected label');

            state[type] = name;

            update();
        }).appendTo($p);

        if(state[type] == name) $e.addClass('selected');
    }

    function updateAll($p, data, state) {
        updateCharts($p, data, state);
        updateLegend($p, data, state);
    }

    function updateCharts($p, data, state) {
        updateChart($('.uptimeContainer'), data, state, 'uptime', 100, function(v) {
            var val = ((100 - v.value) / 100 * 60 * 24).toFixed(2);

            v.value += ' %, ' + val + ' min downtime';

            return v;
        });
        updateChart($('.latencyContainer'), data, state, 'latency', 300, function(v) {
            v.value += ' ms';

            return v;
        });
    }

    function updateChart($p, data, state, category, height, tooltipCb) {
        var $canvas = $('.chart:first', $p);
        var ctx, width, $help;

        if(!$canvas.length) {
            $help = $('.help', $p);
            $help.qtip({content: $help.text()}).text('?');
            $canvas = $('<canvas>', {'class': 'chart'}).appendTo($p);
        }

        // dynamic width (parent width might change)
        width = $canvas.parent().width();

        $canvas.attr({width: width, height: height});

        ctx = $canvas[0].getContext('2d');

        chart(ctx, getData(data, state, category), tooltipCb);
    }

    function updateLegend($p, data, state) {
        updateType(data, state, 'latency', function(state, values) {
            return average(values.slice(-state.amount)).toFixed(1) + ' ms';
        });

        updateType(data, state, 'uptime', function(state, values) {
            return average(values.slice(-state.amount)).toFixed(3) + ' %';
        });
    }

    function updateType(data, state, type, calculateValue) {
        var providers = data.providers;
        var tdClass, value, name;

        for(name in providers) {
            value = 'NA';
            provider = providers[name];
            tdClass = idfy(name);

            if(state.type in provider.types) {
                values = provider.types[state.type][type];

                value = values && calculateValue(state, values);
            }

            $('.' + type + '.' + tdClass).text(value);
        }
    }

    function chart(ctx, data, tooltipCb) {
        // https://github.com/nnnick/Chart.js/issues/76
        var min = minimum(data.datasets.map(op(minimum, prop('data'))));
        var max = maximum(data.datasets.map(op(maximum, prop('data'))));
        var opts;

        if(max == min) {
            opts = {
                animation: true,
                animationSteps: 10,
                datasetFill: false,
                scaleOverride : true,
                scaleSteps : 3,
                scaleStepWidth : 1,
                scaleStartValue : max - 3
            };
        }
        else {
            opts = {
                animation: true,
                animationSteps: 10,
                datasetFill: false,
                pointDot: true,
                scaleShowGridLines: true,
                scaleGridLineColor: 'rgba(224,224,224,0.5)',
                scaleGridLineWidth: 1,
                pointDotRadius: 3,
                bezierCurve: false
            };
        }

        new Chart(ctx, {}, tooltipCb).Line(data, opts);
    }

    function op(fn, accessor) {
        return function(v) {
            return fn(accessor(v));
        };
    }

    function minimum(a) {
        return a.slice().sort()[0];
    }

    function maximum(a) {
        return a.slice().sort()[a.length - 1];
    }

    function createLegend($container, data, state, update) {
        var $table = $('<table>', {'class': 'legend'}).appendTo($container);
        var $header = $('<tr>').appendTo($table);
        var providers = data.providers;
        var provider, color, name, host;

        $('<th>', {'class': 'names'}).html(' ').appendTo($header);

        for(name in providers) {
            provider = providers[name];
            host = provider.host;
            color = provider.color;

            createTh(state, name, host, color, update).appendTo($header);
        }

        createRow(state, providers, 'latency', 'ms').appendTo($table);
        createRow(state, providers, 'uptime', '%').appendTo($table);
    }

    function createTh(state, name, host, color, update) {
        var thClass = idfy(name);
        var $e = $('<th>', {'class': 'cdn ' + thClass}).css({
            'background-color': colorToHex(color)
        });
        var inverseColor = colorToHex(flipColor(color));
        $('<a>', {href: 'http://' + host}).css({
            'color': inverseColor
        }).text(name).appendTo($e);

        var $icon = $('<i>', {'class': 'visibility foundicon-eyeball'}).css({
            'color': inverseColor
        }).on('click', function() {
            $icon.toggleClass('selected');

            toggleItem(state.providers, idfy(name), $icon.hasClass('selected'));

            update();
        }).appendTo($e);

        if(within(state.providers, thClass)) $icon.addClass('selected');

        return $e;
    }

    function toggleItem(arr, k, v) {
        var i;

        if(v) {
            if(!within(arr, k)) arr.push(k);
        }
        else {
            i = arr.indexOf(k);

            if(i >= 0) arr.splice(i, 1);
        }
    }

    function idfy(val) {
        return val.toLowerCase().replace(/[ \-\(\)]+/g, '_').replace(/\.+/g, '');
    }

    function createRow(state, providers, type, unit) {
        var $row = $('<tr>');
        var name, provider, values, value, tdClass;

        $('<td>', {'class': type}).text(type).appendTo($row);

        for(name in providers) {
            provider = providers[name];
            tdClass = idfy(name);

            $('<td>', {'class': type + ' ' + tdClass}).appendTo($row);
        }

        return $row;
    }

    function average(arr) {
        if(!arr) return;

        return sum(arr) / arr.length;
    }

    function sum(arr) {
        if(!arr) return;

        return arr.reduce(function(a, b) {
            return a + b;
        });
    }

    function getData(data, state, category) {
        return {
            labels: getLabels(data.lastDate, state.amount),
            datasets: getDatasets(data.providers, state, category)
        };
    }

    function getLabels(lastDate, amount) {
        var ret = [];
        var len = amount;
        var i, d;

        // TODO: move these rules elsewhere
        if(amount == 90) len = 30;

        for(i = 0; i < len; i++) {
            d = lastDate.clone();

            // TODO: move these rules elsewhere
            if(amount == 90) d.addDays(-(90 - i * 3 - 1));
            else d.addDays(-(amount - i - 1));

            ret.push(d.toString('dd MMM'));
        }

        return ret;
    }

    function getDatasets(providers, state, category) {
        var ret = [];
        var provider, color, data, offset;

        for(var name in providers) {
            provider = providers[name];
            color = colorToHex(provider.color);

            if(!within(state.providers, idfy(name))) continue;
            if(!(state.type in provider.types)) continue;
            if(!(category in provider.types[state.type])) continue;

            data = provider.types[state.type][category];

            offset = state.amount - data.length;
            if(offset > 0) data = zeroes(offset).concat(data);
            else data = data.slice(-state.amount);

            ret.push({
                strokeColor: color,
                pointColor: color,
                pointStrokeColor: color,
                data: pickPoints(state.amount, data)
            });
        }

        return ret;
    }

    function pickPoints(amount, data) {
        // TODO: maybe there's a better way? now it skips the contribution of
        // the neighboring days
        if(amount == 90) return everyNth(3, data);

        return data;
    }

    function everyNth(n, data) {
        return data.filter(function(v, i) {
            return !(i % n);
        });
    }

    function zeroes(amount) {
        var ret = [];
        var i;

        for(i = 0; i < amount; i++) ret.push(0);

        return ret;
    }

    function within(arr, v) {
        return arr.indexOf(v) >= 0;
    }

    function getProviderName(fullname) {
        return fullname.split(' ')[0];
    }

    function flipColor(color) {
        /* flips color to black or white based on brightness */
        /* http://javascriptrules.com/2009/08/05/css-color-brightness-contrast-using-javascript/ */
        var brightness = (color[0] * 299 + color[1] * 587 + color[2] * 114) / 1000;

        if(brightness > 125) return [0, 0, 0];

        return [255, 255, 255];
    }

    function colorToHex(color) {
        return '#' + color.map(function(v) {
            return v.toString(16);
        }).join('');
    }

    function getColor(index) {
        return [
            [216, 79, 68],
            [174, 227, 214],
            [243, 213, 162],
            [93, 150, 215],
            [68, 68, 68],
            [227, 136, 235],
            [170, 221, 94]
        ][index];
    }

    function unique(arr) {
        var ret = {};

        arr.forEach(function(v) {
            ret[v] = true;
        });

        return Object.keys(ret);
    }

    function prop(name) {
        return function(v) {
            return v[name];
        };
    }
}
<|endoftext|>


<commit_before>/**
 * Render text on <canvas> with some formatting options.
 *
 */

/*global require,define,window,console*/

define("krusovice/tools/text2canvas", ["krusovice/thirdparty/jquery"], function($) {

    /**
     * Renderer provides some basic text rendering tools for <canvas>
     *
     * Set <target> canvas as a paramter.
     *
     * WARNING: This operation taint the <canvas> on Firefox 8 and Chrome 13.
     * Not usable yet.
     *
     * https://bugzilla.mozilla.org/show_bug.cgi?id=672013
     */
    function Renderer(cfg) {
        $.extend(this, cfg);
    }

    Renderer.prototype = {

        /**
         * Target canvas
         */
        canvas : null,

        fontHeight : 0,

        /**
         * Text split to lines
         */
        lines : [],

        /**
         * Text bounding box relative to the back buffer
         */
        box : {
            x : 0,
            y : 0,
            w : 1,
            h : 1
        },

        /**
         * Some pseudo-CSS styling
         *
         */
        css : {

            "color" : "#000000",

            // Add some shadow / border to make text stand
            // out of various backgrounds
            "border-color" : "#000000",

            // top, middle, bottom
            "vertical-align" : "top",

            // left, right, center
            "text-align"  : "left",

            // Font height of <canvas> height
            "font-size-percents" : 10,

            // 0...1 multiplier for font size
            "font-size-adjust" : 1,

            "padding-percents" : 10,

            "border-size-percents" : 1,

            "font-family" : "'Gloria Hallelujah'"

        },

        getWidthAsPixels : function(percents) {
            return this.canvas.width * percents/100;
        },

        getHeightAsPixels : function(percents) {
            return this.canvas.height * percents / 100;
        },

        // Default 2% line padding
        calculateLineHeight : function() {
            return this.fontHeight + this.getHeightAsPixels(2);
        },

        measureText : function() {

            var maxWidth = 0;
            var self = this;

            this.lines.forEach(function(line) {
                var width = self.ctx.measureText(line);
                if(width > maxWidth) {
                    maxWidth = width;
                }
            });

            var lineHeight = this.calculateLineHeight();

            return {
                width : maxWidth,
                height : lineHeight * this.lines.length
            };
        },

        split : function(text) {
            this.lines = text.split("\n");
        },

        prepareFont : function() {

            var pixels;

            if(this.css["font-size-percents"]) {
                pixels = this.getHeightAsPixels(this.css["font-size-percents"]);
            }

            this.fontHeight =  pixels * this.css["font-size-adjust"];

            var font = this.fontHeight + "px " + this.css["font-family"];
            console.log("Using font definition:" + font);
            this.ctx.font = font;
        },

        /**
         * Return x and y which is the start of the text drawing position
         */
        positionText : function(dim) {
            var box = this.box;
            var w = this.canvas.width * box.w;
            var h = this.canvas.height * box.h;

            var paddingX = this.getWidthAsPixels(this.css["padding-percents"]);
            var paddingY = this.getHeightAsPixels(this.css["padding-percents"]);

            var x = this.canvas.width * box.x, y=this.canvas.height*box.y;
            var v = this.css["vertical-align"];
            var a = this.css["text-align"];

            if(v == "top") {
                y = y+paddingY;
            }

            if(v == "middle") {
                y = box.y + (h / 2 - dim.height / 2);
            }

            if(v == "bottom") {
                y = box.y + (h - paddingY - dim.height);
            }

            // Use native canvas text align API for real X
            x = x+paddingX;

            if(a == "center") {
                x = w /2;
            }

            if(a == "right") {
                x = w - paddingX;
            }

            var data = { x : x, y : y, width : w - paddingX * 2, height : h - paddingY * 2};

            if(data.width < 0) {
                console.error(data);
                console.error(box);
                throw "Width calculation failed big time";
            }

            return data;

        },

        /**
         * Render text using current formatting paramaters.
         *
         * Cleverly delegate heavy lifting to jQuery.
         */
        renderText : function(text) {

            console.log("renderText()");
            console.log(this.css);

            var ctx;
            this.ctx = ctx = this.canvas.getContext("2d");

            this.split(text);

            this.prepareFont();

            var dimensions = this.measureText(text);

            var position = this.positionText(dimensions);

            var x = position.x;
            var y = position.y;
            var self = this;
            var h = this.calculateLineHeight();

            ctx.textAlign = this.css["text-align"];
            ctx.textBaseline = "top";
            ctx.fillStyle = this.css.color;

            if(this.css["border-color"] !== null) {
                ctx.strokeStyle = this.css["border-color"];
                ctx.lineWidth = this.getHeightAsPixels(this.css["border-size-percents"]);
            } else {
                ctx.lineWidth = 0;
            }

            ctx.shadowBlur = 3;
            ctx.shadowColor = this.css["border-color"];
            /*
            ctx.fillStyle = "#ff0000";
            ctx.fillRect(0, 0, position.width, position.height);
            ctx.font = "bold 12px sans-serif";
            ctx.fillText("Foobar", 20, 20);
            */

            this.lines.forEach(function(line) {
                console.log("Drawing line:" + x +  " " + y + " line:" + line + " maxWidth:" + position.width);
                if(self.ctx.lineWidth > 0) {
                    ctx.strokeText(line, x, y, position.width);
                }
                ctx.fillText(line, x, y, position.width);

                y += h;
            });

        }

    };

    //
    // public API
    //

    return {
        Renderer : Renderer
    };

});

<commit_msg>Found ugly issues<commit_after>/**
 * Render text on <canvas> with some formatting options.
 *
 */

/*global require,define,window,console*/

define("krusovice/tools/text2canvas", ["krusovice/thirdparty/jquery"], function($) {

    /**
     * Renderer provides some basic text rendering tools for <canvas>
     *
     * Set <target> canvas as a paramter.
     *
     * WARNING: This operation taint the <canvas> on Firefox 8 and Chrome 13.
     * Not usable yet.
     *
     * https://bugzilla.mozilla.org/show_bug.cgi?id=672013
     */
    function Renderer(cfg) {
        $.extend(this, cfg);
    }

    Renderer.prototype = {

        /**
         * Target canvas
         */
        canvas : null,

        fontHeight : 0,

        /**
         * Text split to lines
         */
        lines : [],

        /**
         * Text bounding box relative to the back buffer
         */
        box : {
            x : 0,
            y : 0,
            w : 1,
            h : 1
        },

        /**
         * Some pseudo-CSS styling.
         *
         * Note that used font-family MUST be preloaded somehow,
         * or the first call of draw string will draw nothing
         * on Chrome 15.
         *
         */
        css : {

            "color" : "#000000",

            // Add some shadow / border to make text stand
            // out of various backgrounds
            "border-color" : "#000000",

            // top, middle, bottom
            "vertical-align" : "top",

            // left, right, center
            "text-align"  : "left",

            // Font height of <canvas> height
            "font-size-percents" : 10,

            // 0...1 multiplier for font size
            "font-size-adjust" : 1,

            "padding-percents" : 10,

            "border-size-percents" : 1,

            "font-family" : "'Gloria Hallelujah'"

        },

        getWidthAsPixels : function(percents) {
            return this.canvas.width * percents/100;
        },

        getHeightAsPixels : function(percents) {
            return this.canvas.height * percents / 100;
        },

        // Default 2% line padding
        calculateLineHeight : function() {
            return this.fontHeight + this.getHeightAsPixels(2);
        },

        measureText : function() {

            var maxWidth = 0;
            var self = this;

            this.lines.forEach(function(line) {
                var width = self.ctx.measureText(line);
                if(width > maxWidth) {
                    maxWidth = width;
                }
            });

            var lineHeight = this.calculateLineHeight();

            return {
                width : maxWidth,
                height : lineHeight * this.lines.length
            };
        },

        split : function(text) {
            this.lines = text.split("\n");
        },

        prepareFont : function() {

            var pixels;

            if(this.css["font-size-percents"]) {
                pixels = this.getHeightAsPixels(this.css["font-size-percents"]);
            }

            this.fontHeight =  pixels * this.css["font-size-adjust"];

            var font = this.fontHeight + "px " + this.css["font-family"];
            console.log("Using font definition:" + font);
            this.ctx.font = font;
        },

        /**
         * Return x and y which is the start of the text drawing position
         */
        positionText : function(dim) {
            var box = this.box;
            var w = this.canvas.width * box.w;
            var h = this.canvas.height * box.h;

            var paddingX = this.getWidthAsPixels(this.css["padding-percents"]);
            var paddingY = this.getHeightAsPixels(this.css["padding-percents"]);

            var x = this.canvas.width * box.x, y=this.canvas.height*box.y;
            var v = this.css["vertical-align"];
            var a = this.css["text-align"];

            if(v == "top") {
                y = y+paddingY;
            }

            if(v == "middle") {
                y = box.y + (h / 2 - dim.height / 2);
            }

            if(v == "bottom") {
                y = box.y + (h - paddingY - dim.height);
            }

            // Use native canvas text align API for real X
            x = x+paddingX;

            if(a == "center") {
                x = w /2;
            }

            if(a == "right") {
                x = w - paddingX;
            }

            var data = { x : x, y : y, width : w - paddingX * 2, height : h - paddingY * 2};

            if(data.width < 0) {
                console.error(data);
                console.error(box);
                throw "Width calculation failed big time";
            }

            return data;

        },

        /**
         * Render text using current formatting paramaters.
         *
         * Cleverly delegate heavy lifting to jQuery.
         */
        renderText : function(text) {

            console.log("renderText()");
            console.log(this.css);

            var ctx;
            this.ctx = ctx = this.canvas.getContext("2d");

            this.split(text);

            this.prepareFont();

            var dimensions = this.measureText(text);

            var position = this.positionText(dimensions);

            var x = position.x;
            var y = position.y;
            var self = this;
            var h = this.calculateLineHeight();

            ctx.textAlign = this.css["text-align"];
            ctx.textBaseline = "top";
            ctx.fillStyle = this.css.color;

            if(this.css["border-color"] !== null) {
                ctx.strokeStyle = this.css["border-color"];
                ctx.lineWidth = this.getHeightAsPixels(this.css["border-size-percents"]);
            } else {
                ctx.lineWidth = 0;
            }

            ctx.shadowBlur = 3;
            ctx.shadowColor = this.css["border-color"];
            /*
            ctx.fillStyle = "#ff0000";
            ctx.fillRect(0, 0, position.width, position.height);
            ctx.font = "bold 12px sans-serif";
            ctx.fillText("Foobar", 20, 20);
            */

            this.lines.forEach(function(line) {
                console.log("Drawing line:" + x +  " " + y + " line:" + line + " maxWidth:" + position.width);
                if(self.ctx.lineWidth > 0) {
                    ctx.strokeText(line, x, y, position.width);
                }
                ctx.fillText(line, x, y, position.width);

                y += h;
            });

        }

    };

    //
    // public API
    //

    return {
        Renderer : Renderer
    };

});

<|endoftext|>


<commit_before>import Map from './map'
import Wad from './wad'
import Replay from './replay'
import Fullscreen from './fullscreen'

let ui_play_btn =
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='currentcolor'>
    <path d='M0 0 L0 64 L64 32 Z' />
</svg>`

let ui_pause_btn =
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='currentcolor'>
    <path d='M0 0 L0 64 L20 64 L20 0 M44 0 L64 0 L64 64 L44 64 Z' />
</svg>`

let ui_stop_btn =
`<svg viewBox="0 0 1 1" fill="currentcolor">
    <rect width="1" height="1" />
</svg>`

let ui_fullscreen_btn =
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='currentcolor'>
    <path d='M0 22 L8 22 L8 8 L22 8 L22 0 L0 0 M42 0 L42 8 L56 8 L56 22 L64 22 L64 0 M0 64 L0 42 L8 42 L8 56 L22 56 L22 64 M64 64 L42 64 L42 56 L56 56 L56 42 L64 42 Z' />
</svg> `

let ui_loading_anim =
`<svg width='72px' height='72px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-squares"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><rect x="15" y="15" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.0s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="40" y="15" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.125s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="65" y="15" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.25s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="15" y="40" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.875s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="65" y="40" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.375" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="15" y="65" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.75s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="40" y="65" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.625s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="65" y="65" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.5s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect></svg>`

let ui_template =
`

${ui_loading_anim}

${ui_play_btn}

${ui_stop_btn}

                    ${ui_fullscreen_btn}

`

let ui_style =
`.hlv {
    position:relative;
    width:100%;
    height:100%;
    font-family:"Calibri",sans-serif,arial;
    color:#fff
}
.hlv-loading-animation {
    position:absolute;
    top:0;
    left:0;
    right:0;
    bottom:0;
    z-index:30;
}
.hlv-loading-animation > svg {
    position:absolute;
    left:50%;
    top:50%;
    transform:translate(-50%, -50%);
}
.hlv-controls {
    position:absolute;
    bottom:0;
    left:0;
    right:0;
    height:40px;
    background:#333;
    user-select:none;
    z-index:20;
}
.hlv-controls > div > div {
    display:inline-block;
    line-height:36px;
    width:40px;
    text-align:center;
    font-size:16pt;
}
.hlv-controls > div > div:hover {
    color:#fc0
}
.hlv-controls > .hlv-controls-left {
    float:left
}
.hlv-controls-play {
    visibility:hidden
}
.hlv-controls-stop {
    visibility:hidden
}
.hlv-controls > .hlv-controls-right {
    float:right;
}
.hlv-controls .button {
    cursor:pointer;
    width:20px;
    margin:5px 8px 0
}

.hlv-ui > .hlv-loading {
    position:absolute;
    top:16px;
    right:16px;
    background:#333;
    display:none;
    list-style:none;
    padding:5px;
    font-family:monospace;
    z-index:20;
}

.hlv-screen {
    position:absolute;
    top:0;
    left:0;
    width:100%;
    height:100%;
    z-index:10;
}`

let createDomFromHtml = (html) => {
    let tempNode = document.createElement('div')
    tempNode.innerHTML = html.trim()
    if (tempNode.children.length === 1) {
        return tempNode.firstChild
    } else {
        return tempNode.children
    }
}

let addStyleToDom = (style) => {
    let element = document.createElement('style')
    let textNode = document.createTextNode(style)
    element.appendChild(textNode)
    document.head.appendChild(element)
}

export default class UI {
    constructor(target, game) {
        target.appendChild(createDomFromHtml(ui_template))

        this.buttons = {
            play:       createDomFromHtml(ui_play_btn),
            pause:      createDomFromHtml(ui_pause_btn),
            stop:       createDomFromHtml(ui_stop_btn),
            fullscreen: createDomFromHtml(ui_fullscreen_btn)
        }

        this.dom = {}
        this.dom.root       = target.querySelector('.hlv')
        this.dom.ui         = this.dom.root.querySelector('.hlv-ui')
        this.dom.screen     = this.dom.root.querySelector('.hlv-screen')
        this.dom.loading    = this.dom.root.querySelector('.hlv-loading')
        this.dom.loadAnim   = this.dom.root.querySelector('.hlv-loading-animation')
        this.dom.controls   = this.dom.root.querySelector('.hlv-controls')
        this.dom.play       = this.dom.root.querySelector('.hlv-controls-play')
        this.dom.stop       = this.dom.root.querySelector('.hlv-controls-stop')
        this.dom.fullscreen = this.dom.root.querySelector('.hlv-controls-fullscreen')
        this.dom.screen.appendChild(game.getCanvas())

        addStyleToDom(ui_style)

        this.game = game
        this.currentReplay = null

        this.dom.play.addEventListener('click', () => {
            if (!this.game.player) {
                return
            }

            this.game.player.play()
            this.dom.play.removeChild(this.dom.play.children[0])
            if (this.game.player.isPlaying) {
                if (this.game.player.isPaused) {
                    this.dom.play.appendChild(this.buttons.play)
                } else {
                    this.dom.play.appendChild(this.buttons.pause)
                }
            }
        })
        this.dom.stop.addEventListener('click', () => {
            if (!this.game.player) {
                return
            }

            this.game.player.stop()
            this.dom.play.removeChild(this.dom.play.children[0])
            this.dom.play.appendChild(this.buttons.play)
        })
        document.addEventListener('fullscreenchange', () => {
            this.isFullscreen = document.fullscreen
        }, false)
        document.addEventListener('mozfullscreenchange', () => {
            this.isFullscreen = document.mozFullScreen
        }, false)
        document.addEventListener('webkitfullscreenchange', () => {
            this.isFullscreen = document.webkitIsFullScreen
        }, false)
        document.addEventListener('msfullscreenchange', () => {
            this.isFullscreen = document.msFullscreenElement
        }, false)
        this.dom.fullscreen.addEventListener('click', () => {
            if (Fullscreen.element() === this.dom.root) {
                Fullscreen.exit()
            } else {
                Fullscreen.enter(this.dom.root)
            }
        })
    }

    showReplayControls() {
        this.dom.play.style.visibility = 'visible'
        this.dom.stop.style.visibility = 'visible'
    }

    hideReplayControls() {
        this.dom.play.style.visibility = 'hidden'
        this.dom.stop.style.visibility = 'hidden'
    }

    showLoadingBox() {
        this.dom.loading.style.display = 'initial'
    }

    hideLoadingBox() {
        this.dom.loading.style.display = 'none'
    }

    clearLoadingBox() {
        this.dom.loading.innerHTML = ''
    }

    showLoadingAnimation() {
        this.dom.loadAnim.style.display = 'initial'
    }

    hideLoadingAnimation() {
        this.dom.loadAnim.style.display = 'none'
    }

    show() {
        this.dom.ui.style.display = 'initial'
    }

    hide() {
        this.dom.ui.style.display = 'none'
    }
}<commit_msg>Removed unused fullscreen code<commit_after>import Map from './map'
import Wad from './wad'
import Replay from './replay'
import Fullscreen from './fullscreen'

let ui_play_btn =
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='currentcolor'>
    <path d='M0 0 L0 64 L64 32 Z' />
</svg>`

let ui_pause_btn =
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='currentcolor'>
    <path d='M0 0 L0 64 L20 64 L20 0 M44 0 L64 0 L64 64 L44 64 Z' />
</svg>`

let ui_stop_btn =
`<svg viewBox="0 0 1 1" fill="currentcolor">
    <rect width="1" height="1" />
</svg>`

let ui_fullscreen_btn =
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='currentcolor'>
    <path d='M0 22 L8 22 L8 8 L22 8 L22 0 L0 0 M42 0 L42 8 L56 8 L56 22 L64 22 L64 0 M0 64 L0 42 L8 42 L8 56 L22 56 L22 64 M64 64 L42 64 L42 56 L56 56 L56 42 L64 42 Z' />
</svg> `

let ui_loading_anim =
`<svg width='72px' height='72px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-squares"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><rect x="15" y="15" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.0s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="40" y="15" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.125s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="65" y="15" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.25s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="15" y="40" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.875s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="65" y="40" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.375" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="15" y="65" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.75s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="40" y="65" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.625s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect><rect x="65" y="65" width="20" height="20" fill="#f5f5f5" class="sq"><animate attributeName="fill" from="#f5f5f5" to="#303030" repeatCount="indefinite" dur="1s" begin="0.5s" values="#303030;#303030;#f5f5f5;#f5f5f5" keyTimes="0;0.1;0.2;1"></animate></rect></svg>`

let ui_template =
`

${ui_loading_anim}

${ui_play_btn}

${ui_stop_btn}

                    ${ui_fullscreen_btn}

`

let ui_style =
`.hlv {
    position:relative;
    width:100%;
    height:100%;
    font-family:"Calibri",sans-serif,arial;
    color:#fff
}
.hlv-loading-animation {
    position:absolute;
    top:0;
    left:0;
    right:0;
    bottom:0;
    z-index:30;
}
.hlv-loading-animation > svg {
    position:absolute;
    left:50%;
    top:50%;
    transform:translate(-50%, -50%);
}
.hlv-controls {
    position:absolute;
    bottom:0;
    left:0;
    right:0;
    height:40px;
    background:#333;
    user-select:none;
    z-index:20;
}
.hlv-controls > div > div {
    display:inline-block;
    line-height:36px;
    width:40px;
    text-align:center;
    font-size:16pt;
}
.hlv-controls > div > div:hover {
    color:#fc0
}
.hlv-controls > .hlv-controls-left {
    float:left
}
.hlv-controls-play {
    visibility:hidden
}
.hlv-controls-stop {
    visibility:hidden
}
.hlv-controls > .hlv-controls-right {
    float:right;
}
.hlv-controls .button {
    cursor:pointer;
    width:20px;
    margin:5px 8px 0
}

.hlv-ui > .hlv-loading {
    position:absolute;
    top:16px;
    right:16px;
    background:#333;
    display:none;
    list-style:none;
    padding:5px;
    font-family:monospace;
    z-index:20;
}

.hlv-screen {
    position:absolute;
    top:0;
    left:0;
    width:100%;
    height:100%;
    z-index:10;
}`

let createDomFromHtml = (html) => {
    let tempNode = document.createElement('div')
    tempNode.innerHTML = html.trim()
    if (tempNode.children.length === 1) {
        return tempNode.firstChild
    } else {
        return tempNode.children
    }
}

let addStyleToDom = (style) => {
    let element = document.createElement('style')
    let textNode = document.createTextNode(style)
    element.appendChild(textNode)
    document.head.appendChild(element)
}

export default class UI {
    constructor(target, game) {
        target.appendChild(createDomFromHtml(ui_template))

        this.buttons = {
            play:       createDomFromHtml(ui_play_btn),
            pause:      createDomFromHtml(ui_pause_btn),
            stop:       createDomFromHtml(ui_stop_btn),
            fullscreen: createDomFromHtml(ui_fullscreen_btn)
        }

        this.dom = {}
        this.dom.root       = target.querySelector('.hlv')
        this.dom.ui         = this.dom.root.querySelector('.hlv-ui')
        this.dom.screen     = this.dom.root.querySelector('.hlv-screen')
        this.dom.loading    = this.dom.root.querySelector('.hlv-loading')
        this.dom.loadAnim   = this.dom.root.querySelector('.hlv-loading-animation')
        this.dom.controls   = this.dom.root.querySelector('.hlv-controls')
        this.dom.play       = this.dom.root.querySelector('.hlv-controls-play')
        this.dom.stop       = this.dom.root.querySelector('.hlv-controls-stop')
        this.dom.fullscreen = this.dom.root.querySelector('.hlv-controls-fullscreen')
        this.dom.screen.appendChild(game.getCanvas())

        addStyleToDom(ui_style)

        this.game = game
        this.currentReplay = null

        this.dom.play.addEventListener('click', () => {
            if (!this.game.player) {
                return
            }

            this.game.player.play()
            this.dom.play.removeChild(this.dom.play.children[0])
            if (this.game.player.isPlaying) {
                if (this.game.player.isPaused) {
                    this.dom.play.appendChild(this.buttons.play)
                } else {
                    this.dom.play.appendChild(this.buttons.pause)
                }
            }
        })
        this.dom.stop.addEventListener('click', () => {
            if (!this.game.player) {
                return
            }

            this.game.player.stop()
            this.dom.play.removeChild(this.dom.play.children[0])
            this.dom.play.appendChild(this.buttons.play)
        })
        this.dom.fullscreen.addEventListener('click', () => {
            if (Fullscreen.element() === this.dom.root) {
                Fullscreen.exit()
            } else {
                Fullscreen.enter(this.dom.root)
            }
        })
    }

    showReplayControls() {
        this.dom.play.style.visibility = 'visible'
        this.dom.stop.style.visibility = 'visible'
    }

    hideReplayControls() {
        this.dom.play.style.visibility = 'hidden'
        this.dom.stop.style.visibility = 'hidden'
    }

    showLoadingBox() {
        this.dom.loading.style.display = 'initial'
    }

    hideLoadingBox() {
        this.dom.loading.style.display = 'none'
    }

    clearLoadingBox() {
        this.dom.loading.innerHTML = ''
    }

    showLoadingAnimation() {
        this.dom.loadAnim.style.display = 'initial'
    }

    hideLoadingAnimation() {
        this.dom.loadAnim.style.display = 'none'
    }

    show() {
        this.dom.ui.style.display = 'initial'
    }

    hide() {
        this.dom.ui.style.display = 'none'
    }
}<|endoftext|>


<commit_before>mini.Module(
		"basic/constants"
)
.requires(

)
.defines(function() {
	var Constants = {
		"Json": {
			"spawnPoints": "spawnPoints",
			"shapes": "shapes",
			"bluePrints": "bluePrints",
			"bodies": "bodies"
		}
	};

	Bloob.Constants = Constants;

	return Constants;
});
<commit_msg>removed Constants from Bloob namespace<commit_after>mini.Module(
	"basic/constants"
)
.requires(

)
.defines(function() {
	var Constants = {
		"Json": {
			"spawnPoints": "spawnPoints",
			"shapes": "shapes",
			"bluePrints": "bluePrints",
			"bodies": "bodies"
		}
	};

	return Constants;
});
<|endoftext|>


<commit_before>const config = require('~/config')
const repos = require('~/src/apps/companies/apps/add-company/repos')
const countriesFixture = require('../../../../data/metadata/country')
const { transformObjectToOption } = require('~/src/apps/transformers')

const token = 'abcd'

describe('Add company form repos', () => {
  describe('when fetchForeignCountries() is called with default "domesticCountry"', () => {
    const foreignCountriesFixture = countriesFixture
      .filter(c => c.name !== 'United Kingdom')
      .map(transformObjectToOption)

    beforeEach(async () => {
      nock(config.apiRoot)
        .get('/metadata/country/')
        .reply(200, countriesFixture)

      this.results = await repos.fetchForeignCountries({ token })
    })

    it('should return countries excluding "United Kingdom"', () => {
      expect(this.results).to.be.deep.equal(foreignCountriesFixture)
    })
  })

  describe('when fetchForeignCountries() is called with "domesticCountry" equal to "Italy"', () => {
    const foreignCountriesFixture = countriesFixture
      .filter(c => c.name !== 'Italy')
      .map(transformObjectToOption)

    beforeEach(async () => {
      nock(config.apiRoot)
        .get('/metadata/country/')
        .reply(200, countriesFixture)

      this.results = await repos.fetchForeignCountries({ token, domesticCountry: 'Italy' })
    })

    it('should return countries excluding "Italy"', () => {
      expect(this.results).to.be.deep.equal(foreignCountriesFixture)
    })
  })
})
<commit_msg>Refactor test to support more tests<commit_after>const config = require('~/config')
const { fetchForeignCountries } = require('~/src/apps/companies/apps/add-company/repos')
const countriesFixture = require('../../../../data/metadata/country')
const { transformObjectToOption } = require('~/src/apps/transformers')

const token = 'abcd'

describe('Add company form repos', () => {
  describe('#fetchForeignCountries', () => {
    context('when "domesticCountry" is not set', () => {
      const foreignCountriesFixture = countriesFixture
        .filter(c => c.name !== 'United Kingdom')
        .map(transformObjectToOption)

      beforeEach(async () => {
        nock(config.apiRoot)
          .get('/metadata/country/')
          .reply(200, countriesFixture)

        this.results = await fetchForeignCountries({ token })
      })

      it('should return countries excluding "United Kingdom"', () => {
        expect(this.results).to.be.deep.equal(foreignCountriesFixture)
      })
    })

    context('when "domesticCountry" is Italy', () => {
      const foreignCountriesFixture = countriesFixture
        .filter(c => c.name !== 'Italy')
        .map(transformObjectToOption)

      beforeEach(async () => {
        nock(config.apiRoot)
          .get('/metadata/country/')
          .reply(200, countriesFixture)

        this.results = await fetchForeignCountries({ token, domesticCountry: 'Italy' })
      })

      it('should return countries excluding "Italy"', () => {
        expect(this.results).to.be.deep.equal(foreignCountriesFixture)
      })
    })
  })
})
<|endoftext|>