<commit_before>import LiveEvents from './LiveEvents';

let WebSocket = typeof window !== 'undefined' && window.WebSocket;

const apiUrl = 'wss://www.binary.com/websockets/v2';

const noSubscriptions = () => ({
    ticks: {},
    priceProposal: null,
});

export default class LiveApi {

    static Status = {
        Unknown: 'unknown',
        Connected: 'connected'
    };

    constructor(replacementForWebSocket) {
        this.status = LiveApi.Status.Unknown;
        this.subscriptions = noSubscriptions();

        this.bufferedSends = [];
        this.bufferedExecutes = [];
        this.unresolvedPromises = {};

        this.events = new LiveEvents();

        if (replacementForWebSocket) {
            WebSocket = replacementForWebSocket;
        }

        this.connect();
    }

    connect() {
        this.socket = new WebSocket(apiUrl);
        this.socket.onopen = ::this.onOpen;
        this.socket.onclose = ::this.onClose;
        this.socket.onerror = ::this.onError;
        this.socket.onmessage = ::this.onMessage;
    }

    isReady() {
        return this.socket && this.socket.readyState === 1;
    }

    sendBufferedSends() {
        while (this.bufferedSends.length > 0) {
            this.socket.send(JSON.stringify(this.bufferedSends.shift()));
        }
    }

    executeBufferedExecutes() {
        while (this.bufferedExecutes.length > 0) {
            this.bufferedExecutes.shift()();
        }
    }

    onOpen() {
        this.sendBufferedSends();
        this.executeBufferedExecutes();
    }

    onClose() {
        setTimeout(function() {
            this.connect();
            this.resubscribe();
        }.bind(this), 1000);
    }

    onError(error) {
        console.log(error);
    }

    onMessage(message) {
        const json = JSON.parse(message.data);

        this.events.emit(json.msg_type, json);

        if (!json.echo_req.passthrough || !json.echo_req.passthrough.uid) {
            return;
        }

        const promise = this.unresolvedPromises[json.echo_req.passthrough.uid];
        if (promise) {
            delete this.unresolvedPromises[json.echo_req.passthrough.uid];
            if (!json.error) {
                promise.resolve(json);
            } else {
                promise.reject(json.error);
            }
        }
    }

    sendRaw(data) {
        if (this.isReady()) {
            this.socket.send(JSON.stringify(data));
        } else {
            this.bufferedSends.push(data);
        }
        var promise = new Promise((resolve, reject) => {
            this.unresolvedPromises[data.passthrough.uid] = { resolve, reject };
        });
        return promise;
    }

    send(data) {
        data.passthrough = data.passthrough || { };
        data.passthrough.uid =  (Math.random() * 1e17).toString();
        return this.sendRaw(data);
    }

    execute(func) {
        if (this.isReady()) {
            func();
        } else {
            this.bufferedExecutes.push(func);
        }
    }

    resubscribe() {
        const { ticks, priceProposal } = this.subscriptions;

        this.subscribeToTicks(Object.keys(ticks));

        if (priceProposal) {
            this.getPriceForContractProposal(priceProposal);
        }
    }

    /////

    getTickHistory(symbol, tickHistoryOptions = {}) {
        return this.send({
            ticks: symbol,
            ...tickHistoryOptions
        });
    }

    getActiveSymbolsBrief() {
        return this.send({
            active_symbols: 'brief'
        });
    }

    getActiveSymbolsFull() {
        return this.send({
            active_symbols: 'full'
        });
    }

    getContractsForSymbol(symbol) {
        return this.send({
            contracts_for: symbol
        });
    }

    getPayoutCurrencies() {
        return this.send({
            payout_currencies: 1
        });
    }

    getTradingTimes(date = new Date()) {
        const dateStr = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
        return this.send({
            trading_times: dateStr
        });
    }

    getAssetIndex() {
        return this.send({
            asset_index: 1
        });
    }

    ping() {
        return this.send({
            ping: 1
        });
    }

    getServerTime() {
        return this.send({
            time: 1
        });
    }

    /////

    subscribeToTick(symbol) {
        this.subscriptions.ticks[symbol] = true;

        this.send({
            ticks: symbol
        });
    }

    subscribeToTicks(symbols) {
        symbols.forEach(this.subscribeToTick.bind(this));
    }

    subscribeToPriceForContractProposal(contractProposal) {
        return this.send({
            proposal: 1,
            ...contractProposal
        });
    }

    subscribeToOpenContract(contractId) {
        return this.send({
            proposal_open_contract: 1,
            fmd_id: contractId
        });
    }

    subscribeToAllOpenContracts() {
        return this.send({
            proposal_open_contract: 1
        });
    }

    unsubscribeFromTick(symbol) {
        delete this.subscriptions.ticks[symbol];

        return this.send({
            forget: symbol
        });
    }

    unsubscribeFromTicks(symbols) {
        symbols.forEach(this.unsubscribeFromTick);
    }

    unsubscribeFromAllTicks() {
        this.subscriptions.ticks = {};

        return this.send({
            forget_all: "ticks"
        });
    }

    unsubscribeFromAllProposals() {
        this.subscriptions.priceProposal = null;

        return this.send({
            forget_all: "proposal"
        });
    }

    unsubscribeFromAllPortfolios() {
        this.subscriptions.portfolio = false;

        return this.send({
            forget_all: "portfolio"
        });
    }

    unsubscribeFromAlProposals() {
        this.subscriptions = noSubscriptions();

        return this.send({
            forget_all: "proposal_open_contract"
        });
    }

    /////

    authorize(token) {
        return this.send({
            authorize: token
        });
    }

    getBalance() {
        return this.send({
            balance: 1
        });
    }

    getStatement(statementOptions = {}) {
        return this.send({
            statement: 1,
            ...statementOptions
        });
    }

    getProfitTable(profitTableOptions = {}) {
        return this.send({
            profit_table: 1,
            ...profitTableOptions
        });
    }

    getPortfolio() {
        return this.send({
            portfolio: 1
        });
    }

    buyContract(contractId, price) {
        return this.send({
            buy: contractId,
            price: price
        });
    }

    sellContract(contractId, price) {
        return this.send({
            sell: contractId,
            price: price
        });
    }
}
<commit_msg>Rename getBalance in accordance with api changes<commit_after>import LiveEvents from './LiveEvents';

let WebSocket = typeof window !== 'undefined' && window.WebSocket;

const apiUrl = 'wss://www.binary.com/websockets/v2';

const noSubscriptions = () => ({
    ticks: {},
    priceProposal: null,
});

export default class LiveApi {

    static Status = {
        Unknown: 'unknown',
        Connected: 'connected'
    };

    constructor(replacementForWebSocket) {
        this.status = LiveApi.Status.Unknown;
        this.subscriptions = noSubscriptions();

        this.bufferedSends = [];
        this.bufferedExecutes = [];
        this.unresolvedPromises = {};

        this.events = new LiveEvents();

        if (replacementForWebSocket) {
            WebSocket = replacementForWebSocket;
        }

        this.connect();
    }

    connect() {
        this.socket = new WebSocket(apiUrl);
        this.socket.onopen = ::this.onOpen;
        this.socket.onclose = ::this.onClose;
        this.socket.onerror = ::this.onError;
        this.socket.onmessage = ::this.onMessage;
    }

    isReady() {
        return this.socket && this.socket.readyState === 1;
    }

    sendBufferedSends() {
        while (this.bufferedSends.length > 0) {
            this.socket.send(JSON.stringify(this.bufferedSends.shift()));
        }
    }

    executeBufferedExecutes() {
        while (this.bufferedExecutes.length > 0) {
            this.bufferedExecutes.shift()();
        }
    }

    onOpen() {
        this.sendBufferedSends();
        this.executeBufferedExecutes();
    }

    onClose() {
        setTimeout(function() {
            this.connect();
            this.resubscribe();
        }.bind(this), 1000);
    }

    onError(error) {
        console.log(error);
    }

    onMessage(message) {
        const json = JSON.parse(message.data);

        this.events.emit(json.msg_type, json);

        if (!json.echo_req.passthrough || !json.echo_req.passthrough.uid) {
            return;
        }

        const promise = this.unresolvedPromises[json.echo_req.passthrough.uid];
        if (promise) {
            delete this.unresolvedPromises[json.echo_req.passthrough.uid];
            if (!json.error) {
                promise.resolve(json);
            } else {
                promise.reject(json.error);
            }
        }
    }

    sendRaw(data) {
        if (this.isReady()) {
            this.socket.send(JSON.stringify(data));
        } else {
            this.bufferedSends.push(data);
        }
        var promise = new Promise((resolve, reject) => {
            this.unresolvedPromises[data.passthrough.uid] = { resolve, reject };
        });
        return promise;
    }

    send(data) {
        data.passthrough = data.passthrough || { };
        data.passthrough.uid =  (Math.random() * 1e17).toString();
        return this.sendRaw(data);
    }

    execute(func) {
        if (this.isReady()) {
            func();
        } else {
            this.bufferedExecutes.push(func);
        }
    }

    resubscribe() {
        const { ticks, priceProposal } = this.subscriptions;

        this.subscribeToTicks(Object.keys(ticks));

        if (priceProposal) {
            this.getPriceForContractProposal(priceProposal);
        }
    }

    /////

    getTickHistory(symbol, tickHistoryOptions = {}) {
        return this.send({
            ticks: symbol,
            ...tickHistoryOptions
        });
    }

    getActiveSymbolsBrief() {
        return this.send({
            active_symbols: 'brief'
        });
    }

    getActiveSymbolsFull() {
        return this.send({
            active_symbols: 'full'
        });
    }

    getContractsForSymbol(symbol) {
        return this.send({
            contracts_for: symbol
        });
    }

    getPayoutCurrencies() {
        return this.send({
            payout_currencies: 1
        });
    }

    getTradingTimes(date = new Date()) {
        const dateStr = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
        return this.send({
            trading_times: dateStr
        });
    }

    getAssetIndex() {
        return this.send({
            asset_index: 1
        });
    }

    ping() {
        return this.send({
            ping: 1
        });
    }

    getServerTime() {
        return this.send({
            time: 1
        });
    }

    /////

    subscribeToBalance() {
        return this.send({
            balance: 1
        });
    }

    subscribeToTick(symbol) {
        this.subscriptions.ticks[symbol] = true;

        this.send({
            ticks: symbol
        });
    }

    subscribeToTicks(symbols) {
        symbols.forEach(this.subscribeToTick.bind(this));
    }

    subscribeToPriceForContractProposal(contractProposal) {
        return this.send({
            proposal: 1,
            ...contractProposal
        });
    }

    subscribeToOpenContract(contractId) {
        return this.send({
            proposal_open_contract: 1,
            fmd_id: contractId
        });
    }

    subscribeToAllOpenContracts() {
        return this.send({
            proposal_open_contract: 1
        });
    }

    unsubscribeFromTick(symbol) {
        delete this.subscriptions.ticks[symbol];

        return this.send({
            forget: symbol
        });
    }

    unsubscribeFromTicks(symbols) {
        symbols.forEach(this.unsubscribeFromTick);
    }

    unsubscribeFromAllTicks() {
        this.subscriptions.ticks = {};

        return this.send({
            forget_all: "ticks"
        });
    }

    unsubscribeFromAllProposals() {
        this.subscriptions.priceProposal = null;

        return this.send({
            forget_all: "proposal"
        });
    }

    unsubscribeFromAllPortfolios() {
        this.subscriptions.portfolio = false;

        return this.send({
            forget_all: "portfolio"
        });
    }

    unsubscribeFromAlProposals() {
        this.subscriptions = noSubscriptions();

        return this.send({
            forget_all: "proposal_open_contract"
        });
    }

    /////

    authorize(token) {
        return this.send({
            authorize: token
        });
    }

    getStatement(statementOptions = {}) {
        return this.send({
            statement: 1,
            ...statementOptions
        });
    }

    getProfitTable(profitTableOptions = {}) {
        return this.send({
            profit_table: 1,
            ...profitTableOptions
        });
    }

    getPortfolio() {
        return this.send({
            portfolio: 1
        });
    }

    buyContract(contractId, price) {
        return this.send({
            buy: contractId,
            price: price
        });
    }

    sellContract(contractId, price) {
        return this.send({
            sell: contractId,
            price: price
        });
    }
}
<|endoftext|>


<commit_before>/* Module for Game Closure Devkit to handle loading screen
 * This module makes use of StackView signals.
 *
 * Authors: Jishnu Mohan <jishnu7@gmail.com>, Bijosh T J <bijoshtj@gmail.com>
 *
 * Copyright: 2014, Hashcube (http://hashcube.com)
 *
 */

/* global GC, console, Emitter, loader, _, setTimeout, test, history */
/* jshint ignore:start */
import event.Emitter as Emitter;

import ui.resource.loader as loader;

import util.underscore as _;

import .history as history;
import .test as test;
/* jshint ignore:end */

exports = new (Class(Emitter, function () { // jshint ignore:line
  'use strict';

  var debug = false,
    loading = false,
    log = function (msg) {
      if (debug) {
        console.log('loading:', msg);
      }
    },

    // folders to pre-load
    folders,
    view;

  this.initialize = function (loading_view, cache) {
    folders = cache ? cache : {};
    view = loading_view;

    _.bindAll(this, 'show', 'hide');

    test.prepare(this, {
      view: view,
      folders: folders
    });
  };

  // method to show loading screen
  this.show = function (preload, callback) {
    // disable back button
    history.setBusy();
    this.emit('show');
    view.updateOpts({
      superview: GC.app,
      zIndex: 10,
      visible: true
    });

    if (preload && _.has(folders, preload)) {
      log('preload ' + preload);
      loading = true;
      loader.preload(folders[preload], function () {
        loading = false;
      });
    } else {
      log('no preload');
      loading = false;
    }

    // method to hide loading screen
    GC.app.on('StackChanged', this.hide);

    if (callback) {
      _.defer(callback);
    }
  };

  this.update = function (cache) {
    folders = cache;
  };

  // No need to call this, unless you want to hide manually.
  this.hide = function () {
    // if images are still loading, call this function again
    if (loading === true) {
      log('flag is ' + loading);
      setTimeout(this.hide, 1);
      return;
    }
    log('flag is ' + loading);
    GC.app.removeListener('StackChanged', this.hide);
    view.removeFromSuperview();
    view.updateOpts({
      visible: false
    });

    // reset backbutton
    history.resetBusy();
    this.emit('hide');
  };
}))();
<commit_msg>fixed loading error<commit_after>/* Module for Game Closure Devkit to handle loading screen
 * This module makes use of StackView signals.
 *
 * Authors: Jishnu Mohan <jishnu7@gmail.com>, Bijosh T J <bijoshtj@gmail.com>
 *
 * Copyright: 2014, Hashcube (http://hashcube.com)
 *
 */

/* global GC, console, Emitter, loader, _, setTimeout, test, history */
/* jshint ignore:start */
import event.Emitter as Emitter;

import ui.resource.loader as loader;

import util.underscore as _;

import .history as history;
import .test as test;
/* jshint ignore:end */

exports = new (Class(Emitter, function () { // jshint ignore:line
  'use strict';

  var debug = false,
    loading = false,
    log = function (msg) {
      if (debug) {
        console.log('loading:', msg);
      }
    },

    // folders to pre-load
    folders,
    view;

  this.initialize = function (loading_view, cache) {
    folders = cache ? cache : {};
    view = loading_view;

    _.bindAll(this, 'show', 'hide');

    test.prepare(this, {
      view: view,
      folders: folders
    });
  };

  // method to show loading screen
  this.show = function (preload, callback) {
    // disable back button
    history.setBusy();
    this.emit('show');
    view.updateOpts({
      superview: GC.app,
      zIndex: 10,
      visible: true
    });

    if (preload && _.has(folders, preload)) {
      log('preload ' + preload);
      loading = true;
      loader.preload(folders[preload], function () {
        loading = false;
      });
    } else {
      log('no preload');
      loading = false;
    }

    // method to hide loading screen
    GC.app.on('StackChanged', this.hide);

    if (callback) {
      _.defer(callback);
    }
  };

  this.update = function (cache) {
    folders = cache;
  };

  // No need to call this, unless you want to hide manually.
  this.hide = function () {
    // if images are still loading, call this function again
    if (loading === true) {
      log('flag is ' + loading);
      window.setTimeout(this.hide, 1);
      return;
    }
    log('flag is ' + loading);
    GC.app.removeListener('StackChanged', this.hide);
    view.removeFromSuperview();
    view.updateOpts({
      visible: false
    });

    // reset backbutton
    history.resetBusy();
    this.emit('hide');
  };
}))();
<|endoftext|>


<commit_before>/* Module for Game Closure Devkit to handle loading screen
 * This module makes use of StackView signals.
 *
 * Authors: Jishnu Mohan <jishnu7@gmail.com>, Bijosh T J <bijoshtj@gmail.com>
 *
 * Copyright: 2014, Hashcube (http://hashcube.com)
 *
 */

/* global console, loader, _, setTimeout, test */
/* jshint ignore:start */
import ui.resource.Image as Image;
import ui.ImageView as ImageView;
import ui.resource.loader as loader;

import util.underscore as _;

import .history as history;
import .test as test;
/* jshint ignore:end */

exports = (function() {
  'use strict';

  var obj = {},
    debug = false,
    loading = false,
    log = function(msg) {
      if(debug) {
        console.log('loading:', msg);
      }
    },
    // folders to pre-load
    folders,
    view;

  obj.initialize = function (view_obj, folders_obj) {
    view = view_obj;
    folders = folders_obj ? folders_obj : {};

    test.prepare(this, {
      view: view,
      folders: folders
    });
  };

  // method to show loading screen
  obj.show = function(parent, preload, callback) {
    if (!view) {
      return false;
    }

    // disable back button
    history.setBusy();

    view.emit('loading:show');
    view.updateOpts({
      superview: parent,
      visible: true
    });

    if(preload && _.has(folders, preload)) {
      log('preload ' + preload);
      loading = true;
      loader.preload(folders[preload], function() {
        loading = false;
      });
    } else {
      log('no preload');
      loading = false;
    }

    // method to hide loading screen
    parent.once('ViewDidDisappear', this.hide);

    if(callback) {
      _.defer(_.bind(callback, parent));
    }
  };

  // No need to call this, unless you want to hide manually.
  obj.hide = function() {
    if (!view) {
      return false;
    }
    // if images are still loading, call this function again
    if(loading === true) {
      log('flag is ' + loading);
      setTimeout(obj.hide, 1);
      return;
    }

    log('flag is ' + loading);
    view.removeFromSuperview();
    view.updateOpts({
      visible: false
    });

    // reset backbutton
    history.resetBusy();
    view.emit('loading:hide');
  };

  return obj;
})();
<commit_msg>emit events on and after loading screen<commit_after>/* Module for Game Closure Devkit to handle loading screen
 * This module makes use of StackView signals.
 *
 * Authors: Jishnu Mohan <jishnu7@gmail.com>, Bijosh T J <bijoshtj@gmail.com>
 *
 * Copyright: 2014, Hashcube (http://hashcube.com)
 *
 */

/* global console, Emitter, loader, _, setTimeout, test, history */
/* jshint ignore:start */
import event.Emitter as Emitter;

import ui.resource.Image as Image;
import ui.ImageView as ImageView;
import ui.resource.loader as loader;

import util.underscore as _;

import .history as history;
import .test as test;
/* jshint ignore:end */

exports = new (Class(Emitter, function () {
  'use strict';

  var debug = false,
    loading = false,
    log = function(msg) {
      if(debug) {
        console.log('loading:', msg);
      }
    },
    // folders to pre-load
    folders,
    view;

  this.initialize = function (loading_view, cache) {
    folders = cache ? cache : {};
    view = loading_view;
    loading_view.build();

    _.bindAll(this, 'show', 'hide');

    test.prepare(this, {
      view: view,
      folders: folders
    });
  };

  // method to show loading screen
  this.show = function(parent, preload, callback) {
    // disable back button
    history.setBusy();

    this.emit('show');
    view.updateOpts({
      superview: parent,
      visible: true
    });

    if(preload && _.has(folders, preload)) {
      log('preload ' + preload);
      loading = true;
      loader.preload(folders[preload], function() {
        loading = false;
      });
    } else {
      log('no preload');
      loading = false;
    }

    // method to hide loading screen
    parent.once('ViewDidDisappear', this.hide);

    if(callback) {
      _.defer(_.bind(callback, parent));
    }
  };

  // No need to call this, unless you want to hide manually.
  this.hide = function() {
    // if images are still loading, call this function again
    if(loading === true) {
      log('flag is ' + loading);
      setTimeout(this.hide, 1);
      return;
    }

    log('flag is ' + loading);
    view.removeFromSuperview();
    view.updateOpts({
      visible: false
    });

    // reset backbutton
    history.resetBusy();
    this.emit('hide');
  };
}))();
<|endoftext|>


<commit_before>/**
 * Created by euans on 16/02/2017.
 */

const prop = require('prop-d');
const {makeState, isAbstract} = require('./state');

class Machine extends Function{
  _hasChild(state){
    return this._children.has(state);
  }
  _hasDescendant(state){
    return this._descendants.has(state);
  }
  _addChild(state){
    this._children.add(state);
    this._addDescendant(state);
  }
  _addDescendant(state){
    this._descendants.add(state);
    if (this._parent) this._parent._addDescendant(state);
  }
  _findChildWith(state){
    for(let sub of this._children){
      if (exports.isMachine(sub) && sub._hasDescendant(state)){
        return sub;
      }
    }
  }
  //noinspection JSMethodCanBeStatic
  $createContext(...args){
    return {arguments:args};
  }
}

const machineProps = {
  _children: prop(()=>new Set()).hidden.constant,
  _descendants: prop(()=>new Set()).hidden.constant,
  _parent: prop().hidden
};

const copyProperty = [
  "$createContext"
];

exports.makeMachine = function(obj, parent, machine){
  if (parent != null && !exports.isMachine(parent)) throw new TypeError('second parameter must be undefined or a machine');
  if (typeof obj !== 'object') throw new TypeError('first parameter must be an object');

  if (!machine){
    machine = Object.create(Machine.prototype);
  } else {
    Object.setPrototypeOf(machine, Machine.prototype);
  }
  Object.defineProperties(machine, machineProps);
  machine._parent = parent;

  //go through all ennumerable properties of the machine definition
  //Any functions => states, any objects => machines
  for (let k of Object.keys(obj)){
    if (copyProperty.indexOf(k)!==-1){
      machine[k]=obj[k];
    }
    //todo: ideally add to a list of own abstract states so that
    //they will not be passed out of an attached machine
    //and cause the parent machine to exit
    else if (isAbstract(obj[k])){
      machine[k]=obj[k];
    }
    else if (typeof obj[k] === 'function'){
      let state = makeState(obj[k]);
      machine[k]=state;
      machine._addChild(state);
    }
    else if (typeof obj[k] === 'object'){
      let submachine = exports.makeMachine(obj[k], machine);
      machine[k] = submachine;
      machine._addChild(submachine);
    } else throw new TypeError('Machine definition properties must be functions (states) or objects (sub-machines)');
  }
  return machine;
};

exports.isMachine = func => func instanceof Machine;<commit_msg>added $triggerTypes to the machine definition<commit_after>/**
 * Created by euans on 16/02/2017.
 */

const prop = require('prop-d');
const {makeState, isAbstract} = require('./state');

class Machine extends Function{
  _hasChild(state){
    return this._children.has(state);
  }
  _hasDescendant(state){
    return this._descendants.has(state);
  }
  _addChild(state){
    this._children.add(state);
    this._addDescendant(state);
  }
  _addDescendant(state){
    this._descendants.add(state);
    if (this._parent) this._parent._addDescendant(state);
  }
  _findChildWith(state){
    for(let sub of this._children){
      if (exports.isMachine(sub) && sub._hasDescendant(state)){
        return sub;
      }
    }
  }
  //noinspection JSMethodCanBeStatic
  $createContext(...args){
    return {arguments:args};
  }
}

const machineProps = {
  _children: prop(()=>new Set()).hidden.constant,
  _descendants: prop(()=>new Set()).hidden.constant,
  _parent: prop().hidden,
  $triggerTypes: prop(()=>[]).hidden
};

const copyProperty = [
  "$createContext",
  "$triggerTypes"
];

exports.makeMachine = function(obj, parent, machine){
  if (parent != null && !exports.isMachine(parent)) throw new TypeError('second parameter must be undefined or a machine');
  if (typeof obj !== 'object') throw new TypeError('first parameter must be an object');

  if (!machine){
    machine = Object.create(Machine.prototype);
  } else {
    Object.setPrototypeOf(machine, Machine.prototype);
  }
  Object.defineProperties(machine, machineProps);
  machine._parent = parent;

  //go through all ennumerable properties of the machine definition
  //Any functions => states, any objects => machines
  for (let k of Object.keys(obj)){
    if (copyProperty.indexOf(k)!==-1){
      machine[k]=obj[k];
    }
    //todo: ideally add to a list of own abstract states so that
    //they will not be passed out of an attached machine
    //and cause the parent machine to exit
    else if (isAbstract(obj[k])){
      machine[k]=obj[k];
    }
    else if (typeof obj[k] === 'function'){
      let state = makeState(obj[k]);
      machine[k]=state;
      machine._addChild(state);
    }
    else if (typeof obj[k] === 'object'){
      let submachine = exports.makeMachine(obj[k], machine);
      machine[k] = submachine;
      machine._addChild(submachine);
    } else throw new TypeError('Machine definition properties must be functions (states) or objects (sub-machines)');
  }
  return machine;
};

exports.isMachine = func => func instanceof Machine;<|endoftext|>


<commit_before>class Context {
  constructor(name, value, depth, parent) {
    this.name = name
    this.value = value
    this.depth = depth
    this.parent = parent
  }
}

let charsTaken = 0
let nullMatch = /(?=.)/.exec(" ")

function matchEdge(node, str) {
  for (let i = 1; i < node.length; i++) {
    let edge = node[i]
    if (edge.lookahead) throw new Error("FIXME")
    let match = edge.match ? edge.match.exec(str) : nullMatch
    if (match) {
      charsTaken = match[0].length
      return edge
    }
  }
}

let tokenValue = null

class State {
  constructor(stack, context) {
    this.stack = stack
    this.context = context
  }

  forward(str) {
    for (;;) {
      let edge = matchEdge(this.stack[this.stack.length - 1], str)
      if (!edge) return false
      this.stack.pop()
      this.popContext()
      edge.apply(this)
      if (charsTaken > 0) return charsTaken
    }
    return -1
  }

  forwardAndUnwind(str, tokenNode) {
    for (let depth = this.stack.length - 1;;) {
      let edge = matchEdge(this.stack[depth], str)
      matched: if (edge) {
        let taken = charsTaken
        if (depth == this.stack.length - 1) {
          // Regular continuation of the current state
          this.stack.pop()
        } else if (taken > 0) {
          // Unwinding some of the stack to continue after a mismatch.
          // Can use this state object because we already know there's
          // progress and we'll commit to this unwinding
          this.stack.length = depth
        } else {
          // Speculatively forward with a copy of the state when
          // encountering a null match during unwinding, since we only
          // want to commit to it when it matches something
          let copy = new State(this.stack.slice(0, depth + 1), this.context)
          let forwarded = copy.forward(str)
          if (forwarded > 0) {
            this.stack = copy.stack
            this.context = copy.context
            return forwarded
          } else {
            break matched
          }
        }
        this.popContext()
        tokenValue = edge.apply(this)
        if (taken > 0) return taken
        depth = this.stack.length - 1
        continue
      }
      // No matching edge, unwind if possible, match a generic token and try again otherwise
      if (depth) depth--
      else depth = this.stack.push(tokenNode) - 1
    }
  }

  push(node) {
    this.stack[this.stack.length] = node
  }

  pushContext(name, value) {
    this.context = new Context(name, value, this.stack.length, this.context)
  }

  popContext() {
    while (this.context && this.stack.length <= this.context.depth)
      this.context = this.context.parent
  }

  copy() {
    return new State(this.stack.slice(), this.context)
  }
}

(typeof exports == "object" ? exports : CodeMirror).GrammarMode = class GrammarMode {
  constructor(config) {
    this.startNode = config.start
    this.tokenNode = config.token
  }

  startState() { return new State([this.startNode], null) }

  copyState(state) { return state.copy() }

  token(stream, state) {
    let str = stream.string.slice(stream.pos)
    tokenValue = null
    stream.pos += state.forwardAndUnwind(str, this.tokenNode)
    let tokenType = tokenValue
    if (stream.eol()) state.forwardAndUnwind("\n", this.tokenNode)
    return tokenType
  }

  blankLine(state) {
    state.forwardAndUnwind("\n", this.tokenNode)
  }
}
<commit_msg>Use token values from speculative matches<commit_after>class Context {
  constructor(name, value, depth, parent) {
    this.name = name
    this.value = value
    this.depth = depth
    this.parent = parent
  }
}

let charsTaken = 0
let nullMatch = /(?=.)/.exec(" ")

function matchEdge(node, str) {
  for (let i = 1; i < node.length; i++) {
    let edge = node[i]
    if (edge.lookahead) throw new Error("FIXME")
    let match = edge.match ? edge.match.exec(str) : nullMatch
    if (match) {
      charsTaken = match[0].length
      return edge
    }
  }
}

let tokenValue = null

class State {
  constructor(stack, context) {
    this.stack = stack
    this.context = context
  }

  forward(str) {
    for (;;) {
      let edge = matchEdge(this.stack[this.stack.length - 1], str)
      if (!edge) return -1
      this.stack.pop()
      this.popContext()
      tokenValue = edge.apply(this)
      if (charsTaken > 0) return charsTaken
    }
  }

  forwardAndUnwind(str, tokenNode) {
    for (let depth = this.stack.length - 1;;) {
      let edge = matchEdge(this.stack[depth], str)
      matched: if (edge) {
        let taken = charsTaken
        if (depth == this.stack.length - 1) {
          // Regular continuation of the current state
          this.stack.pop()
        } else if (taken > 0) {
          // Unwinding some of the stack to continue after a mismatch.
          // Can use this state object because we already know there's
          // progress and we'll commit to this unwinding
          this.stack.length = depth
        } else {
          // Speculatively forward with a copy of the state when
          // encountering a null match during unwinding, since we only
          // want to commit to it when it matches something
          let copy = new State(this.stack.slice(0, depth + 1), this.context)
          let forwarded = copy.forward(str)
          if (forwarded > 0) {
            this.stack = copy.stack
            this.context = copy.context
            return forwarded
          } else {
            break matched
          }
        }
        this.popContext()
        tokenValue = edge.apply(this)
        if (taken > 0) return taken
        depth = this.stack.length - 1
        continue
      }
      // No matching edge, unwind if possible, match a generic token and try again otherwise
      if (depth) depth--
      else depth = this.stack.push(tokenNode) - 1
    }
  }

  push(node) {
    this.stack[this.stack.length] = node
  }

  pushContext(name, value) {
    this.context = new Context(name, value, this.stack.length, this.context)
  }

  popContext() {
    while (this.context && this.stack.length <= this.context.depth)
      this.context = this.context.parent
  }

  copy() {
    return new State(this.stack.slice(), this.context)
  }
}

(typeof exports == "object" ? exports : CodeMirror).GrammarMode = class GrammarMode {
  constructor(config) {
    this.startNode = config.start
    this.tokenNode = config.token
  }

  startState() { return new State([this.startNode], null) }

  copyState(state) { return state.copy() }

  token(stream, state) {
    let str = stream.string.slice(stream.pos)
    stream.pos += state.forwardAndUnwind(str, this.tokenNode)
    let tokenType = tokenValue
    if (stream.eol()) state.forwardAndUnwind("\n", this.tokenNode)
    return tokenType
  }

  blankLine(state) {
    state.forwardAndUnwind("\n", this.tokenNode)
  }
}
<|endoftext|>


<commit_before>import React, { PropTypes as pt } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import { Table, Thead, Th, Tr, Td } from 'reactable';
import classNames from 'classnames';
import moment from 'moment';
import { makeApiRequest, recipesReceived, setSelectedRecipe } from '../actions/ControlActions.js';

const BooleanIcon = props => {
  if (props.value) {
    return  ;
  }
  return  ;
};
BooleanIcon.propTypes = {
  value: pt.bool.isRequired,
};

function FilterBar({ searchText, selectedFilter, updateSearch, updateFilter }) {
  return (

          <input type="text" placeholder="Search" value={searchText} onChange={updateSearch} />

Filter By:

        <SwitchFilter
          options={['All', 'Enabled', 'Disabled']}
          selectedFilter={selectedFilter}
          updateFilter={updateFilter}
        />

  );
}
FilterBar.propTypes = {
  searchText: pt.string.isRequired,
  selectedFilter: pt.any.isRequired,
  updateSearch: pt.func.isRequired,
  updateFilter: pt.func.isRequired,
};

function SwitchFilter({ options, selectedFilter, updateFilter }) {
  return (

      <div className={`switch-selection position-${options.indexOf(selectedFilter)}`}>

      {options.map(option =>
         updateFilter(option)}
        >{option}

      )}

  );
}
SwitchFilter.propTypes = {
  options: pt.object.isRequired,
  selectedFilter: pt.any.isRequired,
  updateFilter: pt.func.isRequired,
};

class DisconnectedRecipeList extends React.Component {
  /**
   * Recipe metadata properties and associated labels to display
   * @type {Object}
   */
  static ActionMetaData = {
    'show-heartbeat': {
      // prop name : displayed label
      surveyId: 'Survey ID',
      thanksMessage: 'Thanks Message',
    },
    'console-log': {
      message: 'Message',
    },
  };

  /**
   * Given a recipe object, determines what type of recipe it is (based on its `action`),
   * and then compiles a hash of 'displayed metadata props' and their values. This hash
   * is saved on the recipe as `metaData`, and displayed in the 'Metadata'
   *
   * Beyond that, a string of metaData values is created, and attached to the
   * recipe as the `searchData` property. This is used by the `Table` component
   * to search/filter/sort the metaData.
   *
   * @param  {Object} Original recipe object
   * @return {Object} Original recipe but with `metaData` and `searchData` properties added
   */
  static applyRecipeMetadata(recipe) {
    const { action: recipeAction } = recipe;
    const newRecipe = { ...recipe };

    // recipes should have empty metaData/searchData props,
    // regardless if we set the values or not
    newRecipe.metaData = {};
    newRecipe.searchData = '';

    // check if there are specific properties/labels we want to grub
    const requestedMetaProps = DisconnectedRecipeList.ActionMetaData[recipeAction];

    // if there are metadata props we want to pull out,
    // loop through them and extract the data from the recipe
    for (const propName in requestedMetaProps) {
      if (requestedMetaProps.hasOwnProperty(propName)) {
        // get the displayed label value
        const label = requestedMetaProps[propName];
        // get the actual property value
        const value = newRecipe.arguments[propName];
        // save the label:property into the metadata
        newRecipe.metaData[label] = value;

        // add the value to the existing search data
        newRecipe.searchData = `${newRecipe.searchData} ${value}`;
      }
    }

    return newRecipe;
  }

  constructor(props) {
    super(props);
    this.state = {
      searchText: '',
      filteredRecipes: null,
      selectedFilter: 'All',
    };

    this.updateFilter = ::this.updateFilter;
    this.updateSearch = ::this.updateSearch;
  }

  componentWillMount() {
    const { dispatch, isFetching, recipeListNeedsFetch } = this.props;
    dispatch(setSelectedRecipe(null));

    if (recipeListNeedsFetch && !isFetching) {
      dispatch(makeApiRequest('fetchAllRecipes', {}))
      .then(recipes => dispatch(recipesReceived(recipes)));
    }
  }

  title = 'Recipes';

  viewRecipe(recipe) {
    const { dispatch } = this.props;
    dispatch(setSelectedRecipe(recipe.id));
    dispatch(push(`/control/recipe/${recipe.id}/`));
  }

  updateSearch(event) {
    this.setState({
      searchText: event.target.value,
    });
  }

  updateFilter(filterStatus) {
    const { recipes } = this.props;

    if (filterStatus === 'All') {
      this.setState({
        filteredRecipes: null,
        selectedFilter: filterStatus,
      });
    } else {
      const enabledState = filterStatus === 'Enabled';
      this.setState({
        filteredRecipes: recipes.filter(recipe => recipe.enabled === enabledState),
        selectedFilter: filterStatus,
      });
    }
  }

  render() {
    const { recipes } = this.props;
    let filteredRecipes = this.state.filteredRecipes || recipes;
    filteredRecipes = filteredRecipes.map(DisconnectedRecipeList.applyRecipeMetadata);

    return (

        <FilterBar
          {...this.state}
          updateFilter={this.updateFilter}
          updateSearch={this.updateSearch}
        />

              Name
              Action Name
              Enabled
              Last Updated
              Metadata

            {filteredRecipes.map(recipe =>

 { this.viewRecipe(recipe); }}>
                {recipe.name}
                {recipe.action}

                  <BooleanIcon value={recipe.enabled} />

                  {moment(recipe.last_updated).fromNow()}

                    {
                      /* Display each metadata property in a list */
                      Object
                        .keys(recipe.metaData)
                        .map(metaProp => (

                            {metaProp}: {recipe.metaData[metaProp]}

                        ))
                    }

              )
            }

    );
  }
}

DisconnectedRecipeList.propTypes = {
  dispatch: pt.func.isRequired,
  isFetching: pt.bool.isRequired,
  recipeListNeedsFetch: pt.bool.isRequired,
  recipe: pt.object.isRequired,
  recipes: pt.array.isRequired,
};

const mapStateToProps = (state, ownProps) => ({
  recipes: state.controlApp.recipes || [],
  dispatch: ownProps.dispatch,
  recipeListNeedsFetch: state.controlApp.recipeListNeedsFetch,
  isFetching: state.controlApp.isFetching,
});

export default connect(
  mapStateToProps
)(DisconnectedRecipeList);
<commit_msg>Remove test props to match ticket reqs<commit_after>import React, { PropTypes as pt } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import { Table, Thead, Th, Tr, Td } from 'reactable';
import classNames from 'classnames';
import moment from 'moment';
import { makeApiRequest, recipesReceived, setSelectedRecipe } from '../actions/ControlActions.js';

const BooleanIcon = props => {
  if (props.value) {
    return  ;
  }
  return  ;
};
BooleanIcon.propTypes = {
  value: pt.bool.isRequired,
};

function FilterBar({ searchText, selectedFilter, updateSearch, updateFilter }) {
  return (

          <input type="text" placeholder="Search" value={searchText} onChange={updateSearch} />

Filter By:

        <SwitchFilter
          options={['All', 'Enabled', 'Disabled']}
          selectedFilter={selectedFilter}
          updateFilter={updateFilter}
        />

  );
}
FilterBar.propTypes = {
  searchText: pt.string.isRequired,
  selectedFilter: pt.any.isRequired,
  updateSearch: pt.func.isRequired,
  updateFilter: pt.func.isRequired,
};

function SwitchFilter({ options, selectedFilter, updateFilter }) {
  return (

      <div className={`switch-selection position-${options.indexOf(selectedFilter)}`}>

      {options.map(option =>
         updateFilter(option)}
        >{option}

      )}

  );
}
SwitchFilter.propTypes = {
  options: pt.object.isRequired,
  selectedFilter: pt.any.isRequired,
  updateFilter: pt.func.isRequired,
};

class DisconnectedRecipeList extends React.Component {
  /**
   * Recipe metadata properties and associated labels to display
   * @type {Object}
   */
  static ActionMetaData = {
    'show-heartbeat': {
      // prop name : displayed label
      surveyId: 'Survey ID',
    },
  };

  /**
   * Given a recipe object, determines what type of recipe it is (based on its `action`),
   * and then compiles a hash of 'displayed metadata props' and their values. This hash
   * is saved on the recipe as `metaData`, and displayed in the 'Metadata'
   *
   * Beyond that, a string of metaData values is created, and attached to the
   * recipe as the `searchData` property. This is used by the `Table` component
   * to search/filter/sort the metaData.
   *
   * @param  {Object} Original recipe object
   * @return {Object} Original recipe but with `metaData` and `searchData` properties added
   */
  static applyRecipeMetadata(recipe) {
    const { action: recipeAction } = recipe;
    const newRecipe = { ...recipe };

    // recipes should have empty metaData/searchData props,
    // regardless if we set the values or not
    newRecipe.metaData = {};
    newRecipe.searchData = '';

    // check if there are specific properties/labels we want to grub
    const requestedMetaProps = DisconnectedRecipeList.ActionMetaData[recipeAction];

    // if there are metadata props we want to pull out,
    // loop through them and extract the data from the recipe
    for (const propName in requestedMetaProps) {
      if (requestedMetaProps.hasOwnProperty(propName)) {
        // get the displayed label value
        const label = requestedMetaProps[propName];
        // get the actual property value
        const value = newRecipe.arguments[propName];
        // save the label:property into the metadata
        newRecipe.metaData[label] = value;

        // add the value to the existing search data
        newRecipe.searchData = `${newRecipe.searchData} ${value}`;
      }
    }

    return newRecipe;
  }

  constructor(props) {
    super(props);
    this.state = {
      searchText: '',
      filteredRecipes: null,
      selectedFilter: 'All',
    };

    this.updateFilter = ::this.updateFilter;
    this.updateSearch = ::this.updateSearch;
  }

  componentWillMount() {
    const { dispatch, isFetching, recipeListNeedsFetch } = this.props;
    dispatch(setSelectedRecipe(null));

    if (recipeListNeedsFetch && !isFetching) {
      dispatch(makeApiRequest('fetchAllRecipes', {}))
      .then(recipes => dispatch(recipesReceived(recipes)));
    }
  }

  title = 'Recipes';

  viewRecipe(recipe) {
    const { dispatch } = this.props;
    dispatch(setSelectedRecipe(recipe.id));
    dispatch(push(`/control/recipe/${recipe.id}/`));
  }

  updateSearch(event) {
    this.setState({
      searchText: event.target.value,
    });
  }

  updateFilter(filterStatus) {
    const { recipes } = this.props;

    if (filterStatus === 'All') {
      this.setState({
        filteredRecipes: null,
        selectedFilter: filterStatus,
      });
    } else {
      const enabledState = filterStatus === 'Enabled';
      this.setState({
        filteredRecipes: recipes.filter(recipe => recipe.enabled === enabledState),
        selectedFilter: filterStatus,
      });
    }
  }

  render() {
    const { recipes } = this.props;
    let filteredRecipes = this.state.filteredRecipes || recipes;
    filteredRecipes = filteredRecipes.map(DisconnectedRecipeList.applyRecipeMetadata);

    return (

        <FilterBar
          {...this.state}
          updateFilter={this.updateFilter}
          updateSearch={this.updateSearch}
        />

              Name
              Action Name
              Enabled
              Last Updated
              Metadata

            {filteredRecipes.map(recipe =>

 { this.viewRecipe(recipe); }}>
                {recipe.name}
                {recipe.action}

                  <BooleanIcon value={recipe.enabled} />

                  {moment(recipe.last_updated).fromNow()}

                    {
                      /* Display each metadata property in a list */
                      Object
                        .keys(recipe.metaData)
                        .map(metaProp => (

                            {metaProp}: {recipe.metaData[metaProp]}

                        ))
                    }

              )
            }

    );
  }
}

DisconnectedRecipeList.propTypes = {
  dispatch: pt.func.isRequired,
  isFetching: pt.bool.isRequired,
  recipeListNeedsFetch: pt.bool.isRequired,
  recipe: pt.object.isRequired,
  recipes: pt.array.isRequired,
};

const mapStateToProps = (state, ownProps) => ({
  recipes: state.controlApp.recipes || [],
  dispatch: ownProps.dispatch,
  recipeListNeedsFetch: state.controlApp.recipeListNeedsFetch,
  isFetching: state.controlApp.isFetching,
});

export default connect(
  mapStateToProps
)(DisconnectedRecipeList);
<|endoftext|>


<commit_before>'use strict'

var vm = require('vm'), fs = require('fs'), path = require('path')

var filename = require.resolve('../lib/ometa-js/all')
var mtime = Math.max(fs.statSync(filename).mtime, fs.statSync(__filename).mtime)

var context = vm.createContext()
var code = fs.readFileSync(filename).toString()
vm.runInContext(code, context, filename)

module.exports = context

require.extensions['.ometajs'] = function(module, filename) {
	var code, temp = filename.slice(0, -2) + '.js'
	if (fs.existsSync(temp) && fs.statSync(temp).mtime.getTime() + 1000 >= Math.max(fs.statSync(filename).mtime, mtime)) {
		code = fs.readFileSync(temp).toString()
	} else {
		console.log('recompile', filename)
		code = fs.readFileSync(filename).toString()
		code = context.translateCode(code)
		code = wrapModule(temp, code)
		fs.writeFileSync(temp, code)
	}
	//console.log(code)
	module._compile(code, filename)
}

function wrapModule(filename, code) {
	var dpProcs = [
		{
			re: /^module\s+(.*?)\s+at\s+(.*)/,
			tr: function(m) {
				return 'var ' + m[1] + ' = ' + 'require(' + m[2] + ')'
			}
		},
		{
			re: /^import\s+(.*?)\s+from\s+(.*)/,
			tr: function(m) {
				return 'void function(m){' +
					'Object.defineProperties(imports, {' +
						m[1].replace(/([^,]+)/g, '$1:{get:function(){return m.$1}}') +
					'})' +
				'}(require(' + m[2] + '))'
			}
		},
		{
			re: /^export\s+(.*)/,
			tr: function(m) {
				return 'Object.defineProperties(exports, {' +
					m[1].replace(/([^,]+)/g, '$1:{get:function(){return $1}}') +
				'})'
			}
		}
	]
	//TODO: refactor this file to ometajs lib
	var ometajsPath = './' + path.relative(filename + '/..', __filename)
	var targetCode = [
		'var ometajs = require(' + JSON.stringify(ometajsPath) + ')',
		'var OMeta = ometajs.OMeta',
		'var fail = ometajs.fail',
		'var objectThatDelegatesTo = ometajs.objectThatDelegatesTo',
		'var imports = Object.create(null)',
		'with (imports) {',
			'void function(){',
				'"use strict"',
	]
	//assert(code[0] === '{')
	var re = /^"(.*?)";/, offset = 1, m
	while (m = re.exec(code.slice(offset))) {
		var s = m[1].replace(/\\'/g, "'")
		for (var i = 0; i < dpProcs.length; i++) {
			var dpm = dpProcs[i].re.exec(s)
			if (dpm) {
				targetCode.push(dpProcs[i].tr(dpm))
				break
			}
		}
		offset += m[0].length
	}
	targetCode.push(
				code,
			'}()',
		'}'
	)
	return targetCode.join('\n')
}
<commit_msg>update ometa-js submodule<commit_after><|endoftext|>


<commit_before>import React              from 'react';
import ReactDOM           from 'react-dom';
import TestUtils          from 'react/lib/ReactTestUtils';
import Start              from './start';
import Rapper             from '../../../specs_support/rapper';
import configureStore     from '../../store/configure_store';

describe('start', function() {
  var result;

  beforeEach(()=>{
    settings = Immutable.fromJS({
      assessment_kind: "summative",
      src_url: "asdf",
      jwt: "asdfasdf",
      csrf: "asdfasfd",
      api_url: "www.example.com"
    });

    assessment = Immutable.fromJS({});

    var state = {
      settings,
      assessment
    };
    var store = configureStore(state);
    result = TestUtils.renderIntoDocument(<Rapper childProps={{store, params}} child={Start}/>);
  });

  it('renders the start page', function(){
    instance = result.refs.original.getWrappedInstance();
    expect(ReactDOM.findDOMNode(instance)).toBeDefined();
  });

});
<commit_msg>Adds Immutable to fix spec<commit_after>import React              from 'react';
import ReactDOM           from 'react-dom';
import Immutable          from 'immutable';
import TestUtils          from 'react/lib/ReactTestUtils';

import Start              from './start';
import Rapper             from '../../../specs_support/rapper';
import configureStore     from '../../store/configure_store';

describe('start', function() {
  var result;

  beforeEach(()=>{
    settings = Immutable.fromJS({
      assessment_kind: "summative",
      src_url: "asdf",
      jwt: "asdfasdf",
      csrf: "asdfasfd",
      api_url: "www.example.com"
    });

    assessment = Immutable.fromJS({});

    var state = {
      settings,
      assessment
    };
    var store = configureStore(state);
    result = TestUtils.renderIntoDocument(<Rapper childProps={{store, params}} child={Start}/>);
  });

  it('renders the start page', function(){
    instance = result.refs.original.getWrappedInstance();
    expect(ReactDOM.findDOMNode(instance)).toBeDefined();
  });

});
<|endoftext|>


<commit_before>"use strict";
var MapController = function($scope, $location, $timeout, analytics, config, mapTooltipFactory, navigation) {

	var MIN_TIME_BETWEEN_UPDATES = 100;

	var defaultMarker = null;
	var hoverMarker = null;
	var lastMarkerUpdateTime = null;
	var socket = null;

	$scope.mapOptions = null;
	$scope.markers = [];
	$scope.activeMarker = null;

	var doDeactivateMarker = function(marker) {
		var tooltip = marker.tooltips.pop();
		if (tooltip) {
			tooltip.destroy();
		}
		if (marker.tooltips.length === 0) {
			marker.setIcon(defaultMarker);
		}
	};

	$scope.markerActivate = function(marker) {
		if ($scope.activeMarker && $scope.activeMarker !== marker) {
			doDeactivateMarker($scope.activeMarker);
			$scope.activeMarker = null;
		}
		var tooltip = mapTooltipFactory.create(marker);
		marker.tooltips.push(tooltip);
		marker.setIcon(hoverMarker);
		$scope.activeMarker = marker;
	};

	$scope.markerDeactivate = function(marker) {
		$timeout(function() {
			doDeactivateMarker(marker);
		}, 300);
	};

	$scope.markerClicked = function(marker) {
		if (Modernizr.touch) {
			markerActivate(marker);
			$(marker.tooltips[0]).click(function() {
				navigation.toEntry(marker.hikeData.string_id);
			});
		} else {
			navigation.toEntry(marker.hikeData.string_id);
		}
	};

	$scope.updateMarkers = function(event) {
		if (lastMarkerUpdateTime &&
				event.type !== "map-idle" &&
				event.timestamp - lastMarkerUpdateTime < MIN_TIME_BETWEEN_UPDATES) {
			// Recently updated, ignore this update request
			return;
		}

		lastMarkerUpdateTime = event.timestamp;
		var mapBounds = $scope.map.getBounds();
		var northEast = mapBounds.getNorthEast();
		var northEastLatLng = {
			latitude: northEast.lat(),
			longitude: northEast.lng()
		};
		var southWest = mapBounds.getSouthWest();
		var southWestLatLng = {
			latitude: southWest.lat(),
			longitude: southWest.lng()
		};
		socket.emit("get-hikes-in-bounds", { ne: northEastLatLng, sw: southWestLatLng });
	};

	var initIcons = function() {
		defaultMarker = {
			path: google.maps.SymbolPath.CIRCLE,
			fillOpacity: 1,
			fillColor: "#FF6262",
			strokeWeight: 1.0,
			scale: 4
		};
		hoverMarker = {
			path: google.maps.SymbolPath.CIRCLE,
			fillOpacity: 1,
			fillColor: "#FFFF33",
			strokeWeight: 1.0,
			scale: 5
		};
	};

	var initMapOptions = function() {
		// Default to a central view of the US
		var centerLatLng = new google.maps.LatLng(39.833333, -98.583333);
		var zoomLevel = 4;

		// If the url parameters lat / lng are set, zoom in there
		var urlParams = $location.search();
		var latString = urlParams.lat;
		var lngString = urlParams.lng;
		if ($.isNumeric(latString) && $.isNumeric(lngString)) {
			var lat = Number(latString);
			var lng = Number(lngString);
			centerLatLng = new google.maps.LatLng(lat, lng);
			zoomLevel = 12;
		}

		// else if we can guess the user's location, zoom into this location
		else if (google.loader.ClientLocation) {
			var clientLocation = google.loader.ClientLocation;
			centerLatLng = new google.maps.LatLng(clientLocation.latitude, clientLocation.longitude);
			zoomLevel = 7;
		}

		$scope.mapOptions = {
			zoom: zoomLevel,
			center: centerLatLng,
			mapTypeId: google.maps.MapTypeId.TERRAIN
		};
	};

	var initSocketIo = function() {
		socket = io.connect(config.socketIoPath);
		socket.on("get-hikes-in-bounds", function (data) {
			mergeMarkers(data);
		});
	};

	var compareLatLng = function(a, b) {
		if		(a.lat() < b.lat())		{	return -1;	}
		else if (a.lat() > b.lat())		{	return 1;	}
		else if (a.lng() < b.lng())		{	return -1;	}
		else if (a.lng() > b.lng())		{	return 1;	}
		else							{	return 0;	}
	};

	var mergeMarkers = function(newMarkers) {
		var i = 0;
		var j = 0;
		var mergedMarkers = [];
		while (i !== newMarkers.length || j !== $scope.markers.length) {
			var newLatLng = null;
			var oldMarker = null;
			var oldLatLng = null;

			if (i < newMarkers.length) {
				newLatLng = new google.maps.LatLng(newMarkers[i].latitude, newMarkers[i].longitude);
			}

			if (j < $scope.markers.length) {
				oldMarker = $scope.markers[j];
				oldLatLng = oldMarker.getPosition();
			}

			if (!oldLatLng || (newLatLng && compareLatLng(newLatLng, oldLatLng) < 0)) {
				// This is a brand new marker, add it
				var marker = new google.maps.Marker({
					icon: defaultMarker,
					map: $scope.map,
					position: newLatLng,
					hikeData: newMarkers[i],
					tooltips: []
				});
				mergedMarkers.push(marker);
				i++;
			} else if (!newLatLng || (oldLatLng && compareLatLng(newLatLng, oldLatLng) > 0)) {
				// The old marker is no longer in use, remove it
				oldMarker.setMap(null);
				j++;
			} else {
				// Use the existing marker
				mergedMarkers.push(oldMarker);
				i++;
				j++;
			}
		}
		$scope.markers = mergedMarkers;
	};

	// Init
	initIcons();
	initMapOptions();
	initSocketIo();
	$scope.htmlReady();
};

MapController.$inject = ["$scope", "$location", "$timeout", "analytics", "config", "mapTooltipFactory", "navigation"];<commit_msg>Pull marker activation code into separate function.<commit_after>"use strict";
var MapController = function($scope, $location, $timeout, analytics, config, mapTooltipFactory, navigation) {

	var MIN_TIME_BETWEEN_UPDATES = 100;

	var defaultMarker = null;
	var hoverMarker = null;
	var lastMarkerUpdateTime = null;
	var socket = null;

	$scope.mapOptions = null;
	$scope.markers = [];
	$scope.activeMarker = null;

	var doDeactivateMarker = function(marker) {
		var tooltip = marker.tooltips.pop();
		if (tooltip) {
			tooltip.destroy();
		}
		if (marker.tooltips.length === 0) {
			marker.setIcon(defaultMarker);
		}
	};

	var doActivateMarker = function(marker) {
		if ($scope.activeMarker && $scope.activeMarker !== marker) {
			doDeactivateMarker($scope.activeMarker);
			$scope.activeMarker = null;
		}
		var tooltip = mapTooltipFactory.create(marker);
		marker.tooltips.push(tooltip);
		marker.setIcon(hoverMarker);
		$scope.activeMarker = marker;
	};

	$scope.markerActivate = function(marker) {
		doActivateMarker(marker);
	};

	$scope.markerDeactivate = function(marker) {
		$timeout(function() {
			doDeactivateMarker(marker);
		}, 300);
	};

	$scope.markerClicked = function(marker) {
		if (Modernizr.touch) {
			doActivateMarker(marker);
			$(marker.tooltips[0]).click(function() {
				navigation.toEntry(marker.hikeData.string_id);
			});
		} else {
			navigation.toEntry(marker.hikeData.string_id);
		}
	};

	$scope.updateMarkers = function(event) {
		if (lastMarkerUpdateTime &&
				event.type !== "map-idle" &&
				event.timestamp - lastMarkerUpdateTime < MIN_TIME_BETWEEN_UPDATES) {
			// Recently updated, ignore this update request
			return;
		}

		lastMarkerUpdateTime = event.timestamp;
		var mapBounds = $scope.map.getBounds();
		var northEast = mapBounds.getNorthEast();
		var northEastLatLng = {
			latitude: northEast.lat(),
			longitude: northEast.lng()
		};
		var southWest = mapBounds.getSouthWest();
		var southWestLatLng = {
			latitude: southWest.lat(),
			longitude: southWest.lng()
		};
		socket.emit("get-hikes-in-bounds", { ne: northEastLatLng, sw: southWestLatLng });
	};

	var initIcons = function() {
		defaultMarker = {
			path: google.maps.SymbolPath.CIRCLE,
			fillOpacity: 1,
			fillColor: "#FF6262",
			strokeWeight: 1.0,
			scale: 4
		};
		hoverMarker = {
			path: google.maps.SymbolPath.CIRCLE,
			fillOpacity: 1,
			fillColor: "#FFFF33",
			strokeWeight: 1.0,
			scale: 5
		};
	};

	var initMapOptions = function() {
		// Default to a central view of the US
		var centerLatLng = new google.maps.LatLng(39.833333, -98.583333);
		var zoomLevel = 4;

		// If the url parameters lat / lng are set, zoom in there
		var urlParams = $location.search();
		var latString = urlParams.lat;
		var lngString = urlParams.lng;
		if ($.isNumeric(latString) && $.isNumeric(lngString)) {
			var lat = Number(latString);
			var lng = Number(lngString);
			centerLatLng = new google.maps.LatLng(lat, lng);
			zoomLevel = 12;
		}

		// else if we can guess the user's location, zoom into this location
		else if (google.loader.ClientLocation) {
			var clientLocation = google.loader.ClientLocation;
			centerLatLng = new google.maps.LatLng(clientLocation.latitude, clientLocation.longitude);
			zoomLevel = 7;
		}

		$scope.mapOptions = {
			zoom: zoomLevel,
			center: centerLatLng,
			mapTypeId: google.maps.MapTypeId.TERRAIN
		};
	};

	var initSocketIo = function() {
		socket = io.connect(config.socketIoPath);
		socket.on("get-hikes-in-bounds", function (data) {
			mergeMarkers(data);
		});
	};

	var compareLatLng = function(a, b) {
		if		(a.lat() < b.lat())		{	return -1;	}
		else if (a.lat() > b.lat())		{	return 1;	}
		else if (a.lng() < b.lng())		{	return -1;	}
		else if (a.lng() > b.lng())		{	return 1;	}
		else							{	return 0;	}
	};

	var mergeMarkers = function(newMarkers) {
		var i = 0;
		var j = 0;
		var mergedMarkers = [];
		while (i !== newMarkers.length || j !== $scope.markers.length) {
			var newLatLng = null;
			var oldMarker = null;
			var oldLatLng = null;

			if (i < newMarkers.length) {
				newLatLng = new google.maps.LatLng(newMarkers[i].latitude, newMarkers[i].longitude);
			}

			if (j < $scope.markers.length) {
				oldMarker = $scope.markers[j];
				oldLatLng = oldMarker.getPosition();
			}

			if (!oldLatLng || (newLatLng && compareLatLng(newLatLng, oldLatLng) < 0)) {
				// This is a brand new marker, add it
				var marker = new google.maps.Marker({
					icon: defaultMarker,
					map: $scope.map,
					position: newLatLng,
					hikeData: newMarkers[i],
					tooltips: []
				});
				mergedMarkers.push(marker);
				i++;
			} else if (!newLatLng || (oldLatLng && compareLatLng(newLatLng, oldLatLng) > 0)) {
				// The old marker is no longer in use, remove it
				oldMarker.setMap(null);
				j++;
			} else {
				// Use the existing marker
				mergedMarkers.push(oldMarker);
				i++;
				j++;
			}
		}
		$scope.markers = mergedMarkers;
	};

	// Init
	initIcons();
	initMapOptions();
	initSocketIo();
	$scope.htmlReady();
};

MapController.$inject = ["$scope", "$location", "$timeout", "analytics", "config", "mapTooltipFactory", "navigation"];<|endoftext|>


<commit_before>"use strict";
var MapController = function($http, $location, $log, $scope, $timeout, analytics, config, mapTooltipFactory, navigation, resourceCache) {

	var MIN_TIME_BETWEEN_UPDATES = 100; // .1 seconds

	var socket = null;
	var defaultMarker = null;
	var hoverMarker = null;

	var activeMarker = null;
	var lastMarkerUpdateTime = null;
	var mapStabilized = false;
	var doneShowingBanner = false;
	var formattedLocationString = null;
	var searchQuery = null;
	var center = null;
	var zoomLevel = 0;

	$scope.mapOptions = null;
	$scope.markers = [];
	$scope.bannerString = null;
	$scope.showBanner = false;

	var initIcons = function() {
		defaultMarker = {
			url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAVFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADrWTwAAADpWDsnDwqEMiHFSzLkVjoGAgGIMyOrQSwIAwKLNCOsQSytQiw4iv+eAAAADnRSTlMA56+UiVsDVO4GreOoB+a9iu8AAACKSURBVBjTdZFZDsMgDEQdwmKalGHvdv97VqIoAom+zydZ9oypYc3JwHEaSxdmQ2czXWkJhJhyTjEAUjcp8Xx51/C1QLZZlLe7eBQYIsuobqCCLSkEP0ofoOiG6CYiBDHSLBOYgDzLDKzlsRhfLlqetDr+3mJ+5pj/CiG9j9Xtel1yxyrBAAv1e8cXV0kSmGSl8t0AAAAASUVORK5CYII=",
			anchor: new google.maps.Point(5, 5),
			scaledSize: new google.maps.Size(10, 10),
			size: new google.maps.Size(20, 20)
		};
		hoverMarker = {
			url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAS1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//zMAAAAFBQHh4S1TUxCqqiGJiRv6+jLa2itbWxLy8jBQUBCIiBswpy9yAAAADHRSTlMAV6dZ8alwHr53TuKCjDZsAAAApklEQVQoz3WSWRKDIBBER4OiiY/FNfc/aaJlASXwPuliBrpbbvQ4KPgMo5YU3RJoE+kNxnm77Kt3Bl5y08OxTjfrAb1cNJh5SpgNzTUfrvPIF849LW564OjOC8Y+BWvQMrJNGRujDPhc8AyisLlgUQJLLiwgsJcFxVocVV1efW79g9IVLSmaOIOu2R6CcjEoB30a7faPdrF+u6IN6I5AF8sQ66NifX4aJRcT7izi8QAAAABJRU5ErkJggg==",
			anchor: new google.maps.Point(5, 5),
			scaledSize: new google.maps.Size(10, 10),
			size: new google.maps.Size(20, 20)
		};
	};

	var initMapOptions = function() {
		updateViewport($location.search());
		$scope.mapOptions = {
			zoom: zoomLevel,
			center: center,
			scaleControl: true,
			mapTypeId: google.maps.MapTypeId.TERRAIN
		};
	};

	var initSocketIo = function() {
		socket = io.connect(config.socketIoPath);
		socket.on("get-hikes-in-bounds", incomingSocketDataArrived);
	};

	var deactivateMarker = function(marker) {
		var tooltip = marker.tooltips.pop();
		if (tooltip) {
			tooltip.destroy();
		}
		if (marker.tooltips.length === 0) {
			marker.setIcon(defaultMarker);
		}
		hideRoute(marker);
	};

	var deactivateActiveMarker = function() {
		deactivateMarker(activeMarker);
		activeMarker = null;
	};

	var activateMarker = function(marker) {
		if (activeMarker) {
			deactivateActiveMarker();
		}
		activeMarker = marker;
		fetchRouteForActiveMarker();
		var tooltip = mapTooltipFactory.create(activeMarker);
		activeMarker.tooltips.push(tooltip);
		activeMarker.setIcon(hoverMarker);
		showRoute(activeMarker);
	};

	var showRoute = function(marker) {
		if (marker.geoJson) {
			var polylines = marker.geoJson.polylines;
			for (var i = 0 ; i < polylines.length; i++) {
				polylines[i].setMap($scope.map);
			}
		}
	};

	var hideRoute = function(marker) {
		if (marker.geoJson) {
			var polylines = marker.geoJson.polylines;
			for (var i = 0 ; i < polylines.length; i++) {
				polylines[i].setMap(null);
			}
		}
	};

	var updateViewportToDefault = function() {
		if (google.loader.ClientLocation) {
			var clientLocation = google.loader.ClientLocation;
			center = new google.maps.LatLng(clientLocation.latitude, clientLocation.longitude);
			zoomLevel = 5;
		} else {
			// Default to a central view of the US
			center = new google.maps.LatLng(15, -90); // Center over the Americas since they have the most hikes currently.
			zoomLevel = 3;
		}
	};

	var updateViewportFromUrlParams = function(urlParams) {
		formattedLocationString = urlParams.address;
		searchQuery = urlParams.q;

		if (urlParams.lat && urlParams.lng) {
			center = new google.maps.LatLng(parseFloat(urlParams.lat), parseFloat(urlParams.lng));
			zoomLevel = parseInt(urlParams.zoom, 10) || 11;
		}

		if (searchQuery) {
			$scope.bannerString = "Unable to find \"" + searchQuery + "\"";
			$scope.showBanner = true;
		}
	};

	var updateViewport = function(urlParams) {
		updateViewportFromUrlParams(urlParams);
		if (!center) {
			updateViewportToDefault();
		}
	};

	var incomingSocketDataArrived = function(data) {
		$scope.$apply(function() {
			if (data.length === 0) {
				if (formattedLocationString && !doneShowingBanner) {
					$scope.bannerString = "Unable to find hikes near " + formattedLocationString + ". Try zooming out.";
					$scope.showBanner = true;
				}
			}
			mergeMarkers(data);
		});
	};

	var compareLatLng = function(a, b) {
		if		(a.lat() < b.lat())		{	return -1;	}
		else if (a.lat() > b.lat())		{	return 1;	}
		else if (a.lng() < b.lng())		{	return -1;	}
		else if (a.lng() > b.lng())		{	return 1;	}
		else							{	return 0;	}
	};

	var mergeMarkers = function(newMarkers) {
		var i = 0;
		var j = 0;
		var mergedMarkers = [];
		while (i !== newMarkers.length || j !== $scope.markers.length) {
			var newLatLng = null;
			var oldMarker = null;
			var oldLatLng = null;

			if (i < newMarkers.length) {
				newLatLng = new google.maps.LatLng(newMarkers[i].latitude, newMarkers[i].longitude);
			}

			if (j < $scope.markers.length) {
				oldMarker = $scope.markers[j];
				oldLatLng = oldMarker.getPosition();
			}

			if (!oldLatLng || (newLatLng && compareLatLng(newLatLng, oldLatLng) < 0)) {
				// This is a brand new marker, add it
				var marker = new google.maps.Marker({
					icon: defaultMarker,
					map: $scope.map,
					position: newLatLng,
					hikeData: newMarkers[i],
					tooltips: []
				});
				mergedMarkers.push(marker);
				i++;
			} else if (!newLatLng || (oldLatLng && compareLatLng(newLatLng, oldLatLng) > 0)) {
				// The old marker is no longer in use, remove it
				oldMarker.setMap(null);
				j++;
			} else {
				// Use the existing marker
				mergedMarkers.push(oldMarker);
				i++;
				j++;
			}
		}
		$scope.markers = mergedMarkers;
	};

	$scope.$on("resetMapViewport", function(event, urlParams) {
		deactivateMarker();
		center = null;
		zoomLevel = 0;
		doneShowingBanner = false;
		$scope.showBanner = false;
		mapStabilized = false;
		formattedLocationString = null;
		searchQuery = null;
		lastMarkerUpdateTime = null;

		updateViewport(urlParams);
		$scope.map.setCenter(center);
		$scope.map.setZoom(zoomLevel);
	});

	var fetchRouteForActiveMarker = function() {
		var marker = activeMarker;
		if (marker.geoJson) return;
		$http({method: "GET", url: "/api/v1/hikes/" + marker.hikeData.string_id + "?fields=route", cache:resourceCache}).
			success(function(data, status, headers, config) {
				/* global GeoJSON: true */
				if (data.route) {
					var googleOptions = {
						strokeColor: "#EB593C",
						strokeWeight: 3,
						strokeOpacity: 0.9
					};
					var geoJson = new GeoJSON(data.route, googleOptions);
					marker.geoJson = geoJson;
					if (marker === activeMarker) {
						showRoute(marker);
					}
				}
			}).
			error(function(data, status, headers, config) {
				$log.error(data, status, headers, config);
			});
	};

	$scope.markerMousedOver = function(marker) {
		if (!Modernizr.touch) {
			activateMarker(marker);
		}
	};

	$scope.markerMousedOut = function(marker) {
		if (!Modernizr.touch) {
			deactivateActiveMarker();
		}
	};

	$scope.markerClicked = function(marker) {
		if (!Modernizr.touch) {
			navigation.toEntry(marker.hikeData.string_id);
			return;
		}

		if (activeMarker) {
			if (marker === activeMarker) {
				navigation.toEntry(marker.hikeData.string_id);
				return;
			} else {
				deactivateActiveMarker();
			}
		}
		activateMarker(marker);
	};

	$scope.mapClicked = function(event) {
		deactivateActiveMarker();
	};

	$scope.mapMoved = function(event) {
		if (lastMarkerUpdateTime &&
				event.type !== "map-idle" &&
				event.timestamp - lastMarkerUpdateTime < MIN_TIME_BETWEEN_UPDATES) {
			// Recently updated, ignore this update request
			return;
		}
		lastMarkerUpdateTime = event.timestamp;

		var bounds = $scope.map.getBounds();
		var center = $scope.map.getCenter();
		var zoomLevel = $scope.map.getZoom();

		var northEast = bounds.getNorthEast();
		var northEastLatLng = {
			latitude: northEast.lat(),
			longitude: northEast.lng()
		};
		var southWest = bounds.getSouthWest();
		var southWestLatLng = {
			latitude: southWest.lat(),
			longitude: southWest.lng()
		};
		if (event.type !== "map-idle" && mapStabilized) {
			$scope.showBanner = false; // Map is being moved, hide banner
			doneShowingBanner = true;
			mapStabilized = false;
		}
		if (event.type === "map-idle") {
			mapStabilized = true;
			$location.search({lat: center.lat().toFixed(3), lng: center.lng().toFixed(3), zoom: zoomLevel}).replace();
		}
		socket.emit("get-hikes-in-bounds", { ne: northEastLatLng, sw: southWestLatLng });
	};

	/* Having issues with testing socket on localhost, so in order to easily test, just call this function */
	/*
	var seedWithTestData = function() {
		setTimeout(function() {
			incomingSocketDataArrived([{"string_id":"scotchman-peak","name":"Scotchman Peak","latitude":48.188865,"longitude":-116.081728,"distance":12.87472}, {"string_id":"the-narrows","name":"The Narrows","latitude":37.30669,"longitude":-112.94745,"distance":24.94477}])
		});
	};
	seedWithTestData();
	*/

	// Init
	initIcons();
	initMapOptions();
	initSocketIo();
	$scope.htmlReady();
};

MapController.$inject = ["$http", "$location", "$log", "$scope", "$timeout", "analytics", "config", "mapTooltipFactory", "navigation", "resourceCache"];<commit_msg>Add small delay to deactivation for mouse over.<commit_after>"use strict";
var MapController = function($http, $location, $log, $scope, $timeout, analytics, config, mapTooltipFactory, navigation, resourceCache) {

	var MIN_TIME_BETWEEN_UPDATES = 100; // .1 seconds

	var socket = null;
	var defaultMarker = null;
	var hoverMarker = null;

	var activeMarker = null;
	var lastMarkerUpdateTime = null;
	var mapStabilized = false;
	var doneShowingBanner = false;
	var formattedLocationString = null;
	var searchQuery = null;
	var center = null;
	var zoomLevel = 0;

	$scope.mapOptions = null;
	$scope.markers = [];
	$scope.bannerString = null;
	$scope.showBanner = false;

	var initIcons = function() {
		defaultMarker = {
			url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAVFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADrWTwAAADpWDsnDwqEMiHFSzLkVjoGAgGIMyOrQSwIAwKLNCOsQSytQiw4iv+eAAAADnRSTlMA56+UiVsDVO4GreOoB+a9iu8AAACKSURBVBjTdZFZDsMgDEQdwmKalGHvdv97VqIoAom+zydZ9oypYc3JwHEaSxdmQ2czXWkJhJhyTjEAUjcp8Xx51/C1QLZZlLe7eBQYIsuobqCCLSkEP0ofoOiG6CYiBDHSLBOYgDzLDKzlsRhfLlqetDr+3mJ+5pj/CiG9j9Xtel1yxyrBAAv1e8cXV0kSmGSl8t0AAAAASUVORK5CYII=",
			anchor: new google.maps.Point(5, 5),
			scaledSize: new google.maps.Size(10, 10),
			size: new google.maps.Size(20, 20)
		};
		hoverMarker = {
			url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAS1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//zMAAAAFBQHh4S1TUxCqqiGJiRv6+jLa2itbWxLy8jBQUBCIiBswpy9yAAAADHRSTlMAV6dZ8alwHr53TuKCjDZsAAAApklEQVQoz3WSWRKDIBBER4OiiY/FNfc/aaJlASXwPuliBrpbbvQ4KPgMo5YU3RJoE+kNxnm77Kt3Bl5y08OxTjfrAb1cNJh5SpgNzTUfrvPIF849LW564OjOC8Y+BWvQMrJNGRujDPhc8AyisLlgUQJLLiwgsJcFxVocVV1efW79g9IVLSmaOIOu2R6CcjEoB30a7faPdrF+u6IN6I5AF8sQ66NifX4aJRcT7izi8QAAAABJRU5ErkJggg==",
			anchor: new google.maps.Point(5, 5),
			scaledSize: new google.maps.Size(10, 10),
			size: new google.maps.Size(20, 20)
		};
	};

	var initMapOptions = function() {
		updateViewport($location.search());
		$scope.mapOptions = {
			zoom: zoomLevel,
			center: center,
			scaleControl: true,
			mapTypeId: google.maps.MapTypeId.TERRAIN
		};
	};

	var initSocketIo = function() {
		socket = io.connect(config.socketIoPath);
		socket.on("get-hikes-in-bounds", incomingSocketDataArrived);
	};

	var deactivateMarker = function(marker) {
		var tooltip = marker.tooltips.pop();
		if (tooltip) {
			tooltip.destroy();
		}
		if (marker.tooltips.length === 0) {
			marker.setIcon(defaultMarker);
		}
		hideRoute(marker);
	};

	var deactivateActiveMarker = function() {
		deactivateMarker(activeMarker);
		activeMarker = null;
	};

	var activateMarker = function(marker) {
		marker.activationCount++;
		if (activeMarker === marker) return;
		if (activeMarker) {
			deactivateActiveMarker();
		}
		activeMarker = marker;
		fetchRouteForActiveMarker();
		var tooltip = mapTooltipFactory.create(activeMarker);
		activeMarker.tooltips.push(tooltip);
		activeMarker.setIcon(hoverMarker);
		showRoute(activeMarker);
	};

	var showRoute = function(marker) {
		if (marker.geoJson) {
			var polylines = marker.geoJson.polylines;
			for (var i = 0 ; i < polylines.length; i++) {
				polylines[i].setMap($scope.map);
			}
		}
	};

	var hideRoute = function(marker) {
		if (marker.geoJson) {
			var polylines = marker.geoJson.polylines;
			for (var i = 0 ; i < polylines.length; i++) {
				polylines[i].setMap(null);
			}
		}
	};

	var updateViewportToDefault = function() {
		if (google.loader.ClientLocation) {
			var clientLocation = google.loader.ClientLocation;
			center = new google.maps.LatLng(clientLocation.latitude, clientLocation.longitude);
			zoomLevel = 5;
		} else {
			// Default to a central view of the US
			center = new google.maps.LatLng(15, -90); // Center over the Americas since they have the most hikes currently.
			zoomLevel = 3;
		}
	};

	var updateViewportFromUrlParams = function(urlParams) {
		formattedLocationString = urlParams.address;
		searchQuery = urlParams.q;

		if (urlParams.lat && urlParams.lng) {
			center = new google.maps.LatLng(parseFloat(urlParams.lat), parseFloat(urlParams.lng));
			zoomLevel = parseInt(urlParams.zoom, 10) || 11;
		}

		if (searchQuery) {
			$scope.bannerString = "Unable to find \"" + searchQuery + "\"";
			$scope.showBanner = true;
		}
	};

	var updateViewport = function(urlParams) {
		updateViewportFromUrlParams(urlParams);
		if (!center) {
			updateViewportToDefault();
		}
	};

	var incomingSocketDataArrived = function(data) {
		$scope.$apply(function() {
			if (data.length === 0) {
				if (formattedLocationString && !doneShowingBanner) {
					$scope.bannerString = "Unable to find hikes near " + formattedLocationString + ". Try zooming out.";
					$scope.showBanner = true;
				}
			}
			mergeMarkers(data);
		});
	};

	var compareLatLng = function(a, b) {
		if		(a.lat() < b.lat())		{	return -1;	}
		else if (a.lat() > b.lat())		{	return 1;	}
		else if (a.lng() < b.lng())		{	return -1;	}
		else if (a.lng() > b.lng())		{	return 1;	}
		else							{	return 0;	}
	};

	var mergeMarkers = function(newMarkers) {
		var i = 0;
		var j = 0;
		var mergedMarkers = [];
		while (i !== newMarkers.length || j !== $scope.markers.length) {
			var newLatLng = null;
			var oldMarker = null;
			var oldLatLng = null;

			if (i < newMarkers.length) {
				newLatLng = new google.maps.LatLng(newMarkers[i].latitude, newMarkers[i].longitude);
			}

			if (j < $scope.markers.length) {
				oldMarker = $scope.markers[j];
				oldLatLng = oldMarker.getPosition();
			}

			if (!oldLatLng || (newLatLng && compareLatLng(newLatLng, oldLatLng) < 0)) {
				// This is a brand new marker, add it
				var marker = new google.maps.Marker({
					icon: defaultMarker,
					map: $scope.map,
					position: newLatLng,
					hikeData: newMarkers[i],
					activationCount: 0,
					tooltips: []
				});
				mergedMarkers.push(marker);
				i++;
			} else if (!newLatLng || (oldLatLng && compareLatLng(newLatLng, oldLatLng) > 0)) {
				// The old marker is no longer in use, remove it
				oldMarker.setMap(null);
				j++;
			} else {
				// Use the existing marker
				mergedMarkers.push(oldMarker);
				i++;
				j++;
			}
		}
		$scope.markers = mergedMarkers;
	};

	$scope.$on("resetMapViewport", function(event, urlParams) {
		deactivateMarker();
		center = null;
		zoomLevel = 0;
		doneShowingBanner = false;
		$scope.showBanner = false;
		mapStabilized = false;
		formattedLocationString = null;
		searchQuery = null;
		lastMarkerUpdateTime = null;

		updateViewport(urlParams);
		$scope.map.setCenter(center);
		$scope.map.setZoom(zoomLevel);
	});

	var fetchRouteForActiveMarker = function() {
		var marker = activeMarker;
		if (marker.geoJson) return;
		$http({method: "GET", url: "/api/v1/hikes/" + marker.hikeData.string_id + "?fields=route", cache:resourceCache}).
			success(function(data, status, headers, config) {
				/* global GeoJSON: true */
				if (data.route) {
					var googleOptions = {
						strokeColor: "#EB593C",
						strokeWeight: 3,
						strokeOpacity: 0.9
					};
					var geoJson = new GeoJSON(data.route, googleOptions);
					marker.geoJson = geoJson;
					if (marker === activeMarker) {
						showRoute(marker);
					}
				}
			}).
			error(function(data, status, headers, config) {
				$log.error(data, status, headers, config);
			});
	};

	$scope.markerMousedOver = function(marker) {
		if (!Modernizr.touch) {
			activateMarker(marker);
		}
	};

	$scope.markerMousedOut = function(marker) {
		if (!Modernizr.touch) {
			// Give the marker some time before deactivating it, otherwise with jitery mouses the tooltip / route can flicker.
			// Also, make sure the mouse hasn't reactivated again in the meantime.
			var previousActivationCount = marker.activationCount;
			$timeout(function() {
				if (previousActivationCount === marker.activationCount) {
					deactivateActiveMarker();
				}
			}, 350);
		}
	};

	$scope.markerClicked = function(marker) {
		if (!Modernizr.touch) {
			navigation.toEntry(marker.hikeData.string_id);
			return;
		}

		if (activeMarker) {
			if (marker === activeMarker) {
				navigation.toEntry(marker.hikeData.string_id);
				return;
			} else {
				deactivateActiveMarker();
			}
		}
		activateMarker(marker);
	};

	$scope.mapClicked = function(event) {
		deactivateActiveMarker();
	};

	$scope.mapMoved = function(event) {
		if (lastMarkerUpdateTime &&
				event.type !== "map-idle" &&
				event.timestamp - lastMarkerUpdateTime < MIN_TIME_BETWEEN_UPDATES) {
			// Recently updated, ignore this update request
			return;
		}
		lastMarkerUpdateTime = event.timestamp;

		var bounds = $scope.map.getBounds();
		var center = $scope.map.getCenter();
		var zoomLevel = $scope.map.getZoom();

		var northEast = bounds.getNorthEast();
		var northEastLatLng = {
			latitude: northEast.lat(),
			longitude: northEast.lng()
		};
		var southWest = bounds.getSouthWest();
		var southWestLatLng = {
			latitude: southWest.lat(),
			longitude: southWest.lng()
		};
		if (event.type !== "map-idle" && mapStabilized) {
			$scope.showBanner = false; // Map is being moved, hide banner
			doneShowingBanner = true;
			mapStabilized = false;
		}
		if (event.type === "map-idle") {
			mapStabilized = true;
			$location.search({lat: center.lat().toFixed(3), lng: center.lng().toFixed(3), zoom: zoomLevel}).replace();
		}
		socket.emit("get-hikes-in-bounds", { ne: northEastLatLng, sw: southWestLatLng });
	};

	/* Having issues with testing socket on localhost, so in order to easily test, just call this function */
	/*
	var seedWithTestData = function() {
		setTimeout(function() {
			incomingSocketDataArrived([{"string_id":"scotchman-peak","name":"Scotchman Peak","latitude":48.188865,"longitude":-116.081728,"distance":12.87472}, {"string_id":"the-narrows","name":"The Narrows","latitude":37.30669,"longitude":-112.94745,"distance":24.94477}])
		});
	};
	seedWithTestData();
	*/

	// Init
	initIcons();
	initMapOptions();
	initSocketIo();
	$scope.htmlReady();
};

MapController.$inject = ["$http", "$location", "$log", "$scope", "$timeout", "analytics", "config", "mapTooltipFactory", "navigation", "resourceCache"];<|endoftext|>


<commit_before>define(['marionette',
        'views/pages',

        'modules/dc/dclist',
        'modules/dc/views/samplechanger',
        'modules/dc/views/dclog',
        'views/status',

        'views/search',
        'views/filter',

        'modules/stats/views/stack',

        'views/dialog',
        'modules/dc/views/queuebuilder',
        'modules/proposal/views/users',

        'tpl!templates/dc/dclist.html',
        ],
function(Marionette, Pages, DCListView,
         SampleChanger, DCLogView, StatusView, Search, Filter, StackView, DialogView, QueueBuilderView, UserView, template) {

  return Marionette.LayoutView.extend({
    dcListView: DCListView,
    filters: true,
    sampleChanger: true,

    className: 'content',
    template: template,
    regions: {
        data_collections: '.data_collections',
        pages: '.page_wrap',
        search: '.srch',
        type: '.type',
        sc: '.sc',
        log: '.lg',
        status: '.st',
        use: '.usage',
    },

    events: {
      //'click a.queue': 'showQueueBuilder',
      'mouseover a.users': 'showUsers',
      'mouseout a.users': 'hideUsers',
    },

    showUsers: function(e) {
        console.log('show users')
        e.preventDefault()
        if (!this.users) {
            this.users = new UserView({ visit: this.model.get('VISIT') })
            this.$el.find('.users').append(this.users.render().$el)
        }
        this.users.$el.show()
    },

    hideUsers: function(e) {
        if (this.users) this.users.$el.hide()
    },

    showQueueBuilder: function(e) {
        if (e) e.preventDefault()
        app.dialog.show(new DialogView({ title: 'Generate GDA Queue', view: new QueueBuilderView({ visit: this.model.get('VISIT'), bl: this.model.get('BL'), dcs: this.collection }), autoSize: 1 }))
        return false
    },

    templateHelpers: function() {
        return {
            IS_VISIT: !(!this.getOption('params').visit),
            IS_SAMPLE: !(!this.getOption('params').sid),
            IS_SINGLE: !(!this.getOption('params').id),
        }
    },

    initialize: function(options) {
        this.dclist = new (this.getOption('dcListView'))({ collection: options.collection, params: options.params, model: this.model })
        this.paginator = new Pages({ collection: options.collection, noUrl: options.noPageUrl })
        this.filter = new Search({ value: options.params.search, collection: options.collection, url: !options.noSearchUrl })
        if (this.getOption('filters')) this.ty = new Filter({ value: options.params.type, collection: options.collection, mobile: true, url: !options.noFilterUrl })
    },

    onRender: function() {
        this.data_collections.show(this.dclist)
        this.pages.show(this.paginator)
        this.search.show(this.filter)
        if (this.getOption('filters')) this.type.show(this.ty)

        if (this.options.params.visit) {
            this.use.show(new StackView({ visit: this.options.params.visit }))
        }

        if (this.model && this.model.get('ACTIVE') == 1) {
            // Sample changer
            console.log('get sc', this.getOption('sampleChanger'))
            if (this.getOption('sampleChanger') && this.options.params.visit && !app.mobile()) this.sc.show(new SampleChanger({ visit: this.options.params.visit, dcs: this.collection, bl: this.model.get('BL') }))

            // Log View
            this.log.show(new DCLogView({ collection: this.collection }))

            // Status Display

        }

        if (this.model && this.model.get('CAMS') == 1) {
            this.status.show(new StatusView({ bl: this.model.get('BL') }))
        }

        if (app.mobile()) {
            this.$el.find('div.links a').addClass('button-notext')
            this.$el.find('div.links a i').addClass('fa-2x')
        }
    },

    /*
     Why do we have to do this :(
    */
    onDomRefresh: function() {
      console.log('dom refresh dclist')
      if (this.sc.hasView()) this.sc.currentView.triggerMethod('dom:refresh')
      this.dclist.triggerMethod('dom:refresh')
    },

  })

})
<commit_msg>dont poll inactive visits (except cm, nt, nr)<commit_after>define(['marionette',
        'views/pages',

        'modules/dc/dclist',
        'modules/dc/views/samplechanger',
        'modules/dc/views/dclog',
        'views/status',

        'views/search',
        'views/filter',

        'modules/stats/views/stack',

        'views/dialog',
        'modules/dc/views/queuebuilder',
        'modules/proposal/views/users',

        'tpl!templates/dc/dclist.html',
        ],
function(Marionette, Pages, DCListView,
         SampleChanger, DCLogView, StatusView, Search, Filter, StackView, DialogView, QueueBuilderView, UserView, template) {

  return Marionette.LayoutView.extend({
    dcListView: DCListView,
    filters: true,
    sampleChanger: true,

    className: 'content',
    template: template,
    regions: {
        data_collections: '.data_collections',
        pages: '.page_wrap',
        search: '.srch',
        type: '.type',
        sc: '.sc',
        log: '.lg',
        status: '.st',
        use: '.usage',
    },

    events: {
      //'click a.queue': 'showQueueBuilder',
      'mouseover a.users': 'showUsers',
      'mouseout a.users': 'hideUsers',
    },

    showUsers: function(e) {
        console.log('show users')
        e.preventDefault()
        if (!this.users) {
            this.users = new UserView({ visit: this.model.get('VISIT') })
            this.$el.find('.users').append(this.users.render().$el)
        }
        this.users.$el.show()
    },

    hideUsers: function(e) {
        if (this.users) this.users.$el.hide()
    },

    showQueueBuilder: function(e) {
        if (e) e.preventDefault()
        app.dialog.show(new DialogView({ title: 'Generate GDA Queue', view: new QueueBuilderView({ visit: this.model.get('VISIT'), bl: this.model.get('BL'), dcs: this.collection }), autoSize: 1 }))
        return false
    },

    templateHelpers: function() {
        return {
            IS_VISIT: !(!this.getOption('params').visit),
            IS_SAMPLE: !(!this.getOption('params').sid),
            IS_SINGLE: !(!this.getOption('params').id),
        }
    },

    initialize: function(options) {
        if (this.model && this.model.get('ACTIVE') == "0") {
            console.log('MIGHT STOP?')
            var vis = this.getOption('params').visit
            if (vis) {
                if (vis.search('cm') == -1 && vis.search('nt') == -1 && vis.search('nr') == -1) options.collection.stop()
            } else options.collection.stop()
        }

        this.dclist = new (this.getOption('dcListView'))({ collection: options.collection, params: options.params, model: this.model })
        this.paginator = new Pages({ collection: options.collection, noUrl: options.noPageUrl })
        this.filter = new Search({ value: options.params.search, collection: options.collection, url: !options.noSearchUrl })
        if (this.getOption('filters')) this.ty = new Filter({ value: options.params.type, collection: options.collection, mobile: true, url: !options.noFilterUrl })
    },

    onRender: function() {
        this.data_collections.show(this.dclist)
        this.pages.show(this.paginator)
        this.search.show(this.filter)
        if (this.getOption('filters')) this.type.show(this.ty)

        if (this.options.params.visit) {
            this.use.show(new StackView({ visit: this.options.params.visit }))
        }

        if (this.model && this.model.get('ACTIVE') == 1) {
            // Sample changer
            console.log('get sc', this.getOption('sampleChanger'))
            if (this.getOption('sampleChanger') && this.options.params.visit && !app.mobile()) this.sc.show(new SampleChanger({ visit: this.options.params.visit, dcs: this.collection, bl: this.model.get('BL') }))

            // Log View
            this.log.show(new DCLogView({ collection: this.collection }))

            // Status Display

        }

        if (this.model && this.model.get('CAMS') == 1) {
            this.status.show(new StatusView({ bl: this.model.get('BL') }))
        }

        if (app.mobile()) {
            this.$el.find('div.links a').addClass('button-notext')
            this.$el.find('div.links a i').addClass('fa-2x')
        }
    },

    /*
     Why do we have to do this :(
    */
    onDomRefresh: function() {
      console.log('dom refresh dclist')
      if (this.sc.hasView()) this.sc.currentView.triggerMethod('dom:refresh')
      this.dclist.triggerMethod('dom:refresh')
    },

  })

})
<|endoftext|>