<commit_before>/*
 * routing-proxy.js: A routing proxy consuming a RoutingTable and multiple HttpProxy instances
 *
 * (C) 2011 Nodejitsu Inc.
 * MIT LICENCE
 *
 */

var events = require('events'),
    util = require('util'),
    HttpProxy = require('./http-proxy').HttpProxy,
    ProxyTable = require('./proxy-table').ProxyTable;

//
// ### function RoutingProxy (options)
// #### @options {Object} Options for this instance
// Constructor function for the RoutingProxy object, a higher level
// reverse proxy Object which can proxy to multiple hosts and also interface
// easily with a RoutingTable instance.
//
var RoutingProxy = exports.RoutingProxy = function (options) {
  events.EventEmitter.call(this);

  var self = this;
  options = options || {};

  if (options.router) {
    this.proxyTable = new ProxyTable(options);
    this.proxyTable.on('routes', function (routes) {
      self.emit('routes', routes);
    });
  }

  //
  // Create a set of `HttpProxy` objects to be used later on calls
  // to `.proxyRequest()` and `.proxyWebSocketRequest()`.
  //
  this.proxies = {};

  //
  // Setup default target options (such as `https`).
  //
  this.target       = {};
  this.target.https = options.target && options.target.https;

  //
  // Setup other default options to be used for instances of
  // `HttpProxy` created by this `RoutingProxy` instance.
  //
  this.source  = options.source       || { host: 'localhost', port: 8000 };
  this.https   = this.source.https    || options.https;
  this.enable  = options.enable;
  this.forward = options.forward;
};

//
// Inherit from `events.EventEmitter`.
//
util.inherits(RoutingProxy, events.EventEmitter);

//
// ### function add (options)
// #### @options {Object} Options for the `HttpProxy` to add.
// Adds a new instance of `HttpProxy` to this `RoutingProxy` instance
// for the specified `options.host` and `options.port`.
//
RoutingProxy.prototype.add = function (options) {
  var self = this,
      key = this._getKey(options);

  //
  // TODO: Consume properties in `options` related to the `ProxyTable`.
  //
  options.target       = options.target       || {};
  options.target.host  = options.target.host  || options.host;
  options.target.port  = options.target.port  || options.port;
  options.target.https = this.target && this.target.https ||
                         options.target && options.target.https ||
                         options.https;

  //
  // Setup options to pass-thru to the new `HttpProxy` instance
  // for the specified `options.host` and `options.port` pair.
  //
  ['https', 'enable', 'forward'].forEach(function (key) {
    if (options[key] !== false && self[key]) {
      options[key] = self[key];
    }
  });

  this.proxies[key] = new HttpProxy(options);
  this.proxies[key].on('proxyError', this.emit.bind(this, 'proxyError'));
  this.proxies[key].on('webSocketProxyError', this.emit.bind(this, 'webSocketProxyError'));
  this.proxies[key].on('start', this.emit.bind(this, 'start'));
  this.proxies[key].on('forward', this.emit.bind(this, 'forward'));
  this.proxies[key].on('end', this.emit.bind(this, 'end'));
};

//
// ### function remove (options)
// #### @options {Object} Options mapping to the `HttpProxy` to remove.
// Removes an instance of `HttpProxy` from this `RoutingProxy` instance
// for the specified `options.host` and `options.port` (if they exist).
//
RoutingProxy.prototype.remove = function (options) {
  var key = this._getKey(options);
};

//
// ### function close()
// Cleans up any state left behind (sockets, timeouts, etc)
// associated with this instance.
//
RoutingProxy.prototype.close = function () {
  var self = this;

  if (this.proxyTable) {
    //
    // Close the `RoutingTable` associated with
    // this instance (if any).
    //
    this.proxyTable.close();
  }

  //
  // Close all sockets for all `HttpProxy` object(s)
  // associated with this instance.
  //
  Object.keys(this.proxies).forEach(function (key) {
    self.proxies[key].close();
  });
};

//
// ### function proxyRequest (req, res, [port, host, paused])
// #### @req {ServerRequest} Incoming HTTP Request to proxy.
// #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to.
// #### @options {Object} Options for the outgoing proxy request.
//
//     options.port {number} Port to use on the proxy target host.
//     options.host {string} Host of the proxy target.
//     options.buffer {Object} Result from `httpProxy.buffer(req)`
//     options.https {Object|boolean} Settings for https.
//
RoutingProxy.prototype.proxyRequest = function (req, res, options) {
  options = options || {};

  //
  // Check the proxy table for this instance to see if we need
  // to get the proxy location for the request supplied. We will
  // always ignore the proxyTable if an explicit `port` and `host`
  // arguments are supplied to `proxyRequest`.
  //
  if (this.proxyTable && !options.host) {
    location = this.proxyTable.getProxyLocation(req);

    //
    // If no location is returned from the ProxyTable instance
    // then respond with `404` since we do not have a valid proxy target.
    //
    if (!location) {
      try {
        res.writeHead(404);
        res.end();
      }
      catch (er) {
        console.error("res.writeHead/res.end error: %s", er.message);
      }

      return;
    }

    //
    // When using the ProxyTable in conjunction with an HttpProxy instance
    // only the following arguments are valid:
    //
    // * `proxy.proxyRequest(req, res, { host: 'localhost' })`: This will be skipped
    // * `proxy.proxyRequest(req, res, { buffer: buffer })`: Buffer will get updated appropriately
    // * `proxy.proxyRequest(req, res)`: Options will be assigned appropriately.
    //
    options.port = location.port;
    options.host = location.host;
  }

  var key = this._getKey(options),
    proxy;

  if (!this.proxies[key]) {
    this.add(options);

  }

  proxy = this.proxies[key];
  proxy.proxyRequest(req, res, options.buffer);
};

//
// ### function proxyWebSocketRequest (req, socket, head, options)
// #### @req {ServerRequest} Websocket request to proxy.
// #### @socket {net.Socket} Socket for the underlying HTTP request
// #### @head {string} Headers for the Websocket request.
// #### @options {Object} Options to use when proxying this request.
//
//     options.port {number} Port to use on the proxy target host.
//     options.host {string} Host of the proxy target.
//     options.buffer {Object} Result from `httpProxy.buffer(req)`
//     options.https {Object|boolean} Settings for https.
//
RoutingProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options) {
  options = options || {};

  if (this.proxyTable && !options.host) {
    location = this.proxyTable.getProxyLocation(req);

    if (!location) {
      return socket.destroy();
    }

    options.port = location.port;
    options.host = location.host;
  }

  var key = this._getKey(options),
    proxy;

  if (!this.proxies[key]) {
    this.add(options);
  }

  proxy = this.proxies[key];
  proxy.proxyWebSocketRequest(req, socket, head, options.buffer);
};

//
// ### @private function _getKey (options)
// #### @options {Object} Options to extract the key from
// Ensures that the appropriate options are present in the `options`
// provided and responds with a string key representing the `host`, `port`
// combination contained within.
//
RoutingProxy.prototype._getKey = function (options) {
  if (!options || ((!options.host || !options.port)
    && (!options.target || !options.target.host || !options.target.port))) {
    throw new Error('options.host and options.port or options.target are required.');
    return;
  }

  return [
    options.host || options.target.host,
    options.port || options.target.port
  ].join(':');
}
<commit_msg>Fix RoutingProxy hanging when there is an error<commit_after>/*
 * routing-proxy.js: A routing proxy consuming a RoutingTable and multiple HttpProxy instances
 *
 * (C) 2011 Nodejitsu Inc.
 * MIT LICENCE
 *
 */

var events = require('events'),
    util = require('util'),
    HttpProxy = require('./http-proxy').HttpProxy,
    ProxyTable = require('./proxy-table').ProxyTable;

//
// ### function RoutingProxy (options)
// #### @options {Object} Options for this instance
// Constructor function for the RoutingProxy object, a higher level
// reverse proxy Object which can proxy to multiple hosts and also interface
// easily with a RoutingTable instance.
//
var RoutingProxy = exports.RoutingProxy = function (options) {
  events.EventEmitter.call(this);

  var self = this;
  options = options || {};

  if (options.router) {
    this.proxyTable = new ProxyTable(options);
    this.proxyTable.on('routes', function (routes) {
      self.emit('routes', routes);
    });
  }

  //
  // Create a set of `HttpProxy` objects to be used later on calls
  // to `.proxyRequest()` and `.proxyWebSocketRequest()`.
  //
  this.proxies = {};

  //
  // Setup default target options (such as `https`).
  //
  this.target       = {};
  this.target.https = options.target && options.target.https;

  //
  // Setup other default options to be used for instances of
  // `HttpProxy` created by this `RoutingProxy` instance.
  //
  this.source  = options.source       || { host: 'localhost', port: 8000 };
  this.https   = this.source.https    || options.https;
  this.enable  = options.enable;
  this.forward = options.forward;

  //
  // Listen for 'newListener' events so that we can bind 'proxyError'
  // listeners to each HttpProxy's 'proxyError' event.
  //
  this.on('newListener', function (evt) {
    if (evt === 'proxyError' || evt === 'webSocketProxyError') {
      Object.keys(self.proxies).forEach(function (key) {
        self.proxies[key].on(evt, this.emit.bind(this, evt));
      });
    }
  });
};

//
// Inherit from `events.EventEmitter`.
//
util.inherits(RoutingProxy, events.EventEmitter);

//
// ### function add (options)
// #### @options {Object} Options for the `HttpProxy` to add.
// Adds a new instance of `HttpProxy` to this `RoutingProxy` instance
// for the specified `options.host` and `options.port`.
//
RoutingProxy.prototype.add = function (options) {
  var self = this,
      key = this._getKey(options);

  //
  // TODO: Consume properties in `options` related to the `ProxyTable`.
  //
  options.target       = options.target       || {};
  options.target.host  = options.target.host  || options.host;
  options.target.port  = options.target.port  || options.port;
  options.target.https = this.target && this.target.https ||
                         options.target && options.target.https ||
                         options.https;

  //
  // Setup options to pass-thru to the new `HttpProxy` instance
  // for the specified `options.host` and `options.port` pair.
  //
  ['https', 'enable', 'forward'].forEach(function (key) {
    if (options[key] !== false && self[key]) {
      options[key] = self[key];
    }
  });

  this.proxies[key] = new HttpProxy(options);
  if (this.listeners('proxyError').length > 0) {
    this.proxies[key].on('proxyError', this.emit.bind(this, 'proxyError'));
  }
  if (this.listeners('webSocketProxyError').length > 0) {
    this.proxies[key].on('webSocketProxyError', this.emit.bind(this, 'webSocketProxyError'));
  }
  this.proxies[key].on('start', this.emit.bind(this, 'start'));
  this.proxies[key].on('forward', this.emit.bind(this, 'forward'));
  this.proxies[key].on('end', this.emit.bind(this, 'end'));
};

//
// ### function remove (options)
// #### @options {Object} Options mapping to the `HttpProxy` to remove.
// Removes an instance of `HttpProxy` from this `RoutingProxy` instance
// for the specified `options.host` and `options.port` (if they exist).
//
RoutingProxy.prototype.remove = function (options) {
  var key = this._getKey(options);
};

//
// ### function close()
// Cleans up any state left behind (sockets, timeouts, etc)
// associated with this instance.
//
RoutingProxy.prototype.close = function () {
  var self = this;

  if (this.proxyTable) {
    //
    // Close the `RoutingTable` associated with
    // this instance (if any).
    //
    this.proxyTable.close();
  }

  //
  // Close all sockets for all `HttpProxy` object(s)
  // associated with this instance.
  //
  Object.keys(this.proxies).forEach(function (key) {
    self.proxies[key].close();
  });
};

//
// ### function proxyRequest (req, res, [port, host, paused])
// #### @req {ServerRequest} Incoming HTTP Request to proxy.
// #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to.
// #### @options {Object} Options for the outgoing proxy request.
//
//     options.port {number} Port to use on the proxy target host.
//     options.host {string} Host of the proxy target.
//     options.buffer {Object} Result from `httpProxy.buffer(req)`
//     options.https {Object|boolean} Settings for https.
//
RoutingProxy.prototype.proxyRequest = function (req, res, options) {
  options = options || {};

  //
  // Check the proxy table for this instance to see if we need
  // to get the proxy location for the request supplied. We will
  // always ignore the proxyTable if an explicit `port` and `host`
  // arguments are supplied to `proxyRequest`.
  //
  if (this.proxyTable && !options.host) {
    location = this.proxyTable.getProxyLocation(req);

    //
    // If no location is returned from the ProxyTable instance
    // then respond with `404` since we do not have a valid proxy target.
    //
    if (!location) {
      try {
        res.writeHead(404);
        res.end();
      }
      catch (er) {
        console.error("res.writeHead/res.end error: %s", er.message);
      }

      return;
    }

    //
    // When using the ProxyTable in conjunction with an HttpProxy instance
    // only the following arguments are valid:
    //
    // * `proxy.proxyRequest(req, res, { host: 'localhost' })`: This will be skipped
    // * `proxy.proxyRequest(req, res, { buffer: buffer })`: Buffer will get updated appropriately
    // * `proxy.proxyRequest(req, res)`: Options will be assigned appropriately.
    //
    options.port = location.port;
    options.host = location.host;
  }

  var key = this._getKey(options),
    proxy;

  if (!this.proxies[key]) {
    this.add(options);
  }

  proxy = this.proxies[key];
  proxy.proxyRequest(req, res, options.buffer);
};

//
// ### function proxyWebSocketRequest (req, socket, head, options)
// #### @req {ServerRequest} Websocket request to proxy.
// #### @socket {net.Socket} Socket for the underlying HTTP request
// #### @head {string} Headers for the Websocket request.
// #### @options {Object} Options to use when proxying this request.
//
//     options.port {number} Port to use on the proxy target host.
//     options.host {string} Host of the proxy target.
//     options.buffer {Object} Result from `httpProxy.buffer(req)`
//     options.https {Object|boolean} Settings for https.
//
RoutingProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options) {
  options = options || {};

  if (this.proxyTable && !options.host) {
    location = this.proxyTable.getProxyLocation(req);

    if (!location) {
      return socket.destroy();
    }

    options.port = location.port;
    options.host = location.host;
  }

  var key = this._getKey(options),
    proxy;

  if (!this.proxies[key]) {
    this.add(options);
  }

  proxy = this.proxies[key];
  proxy.proxyWebSocketRequest(req, socket, head, options.buffer);
};

//
// ### @private function _getKey (options)
// #### @options {Object} Options to extract the key from
// Ensures that the appropriate options are present in the `options`
// provided and responds with a string key representing the `host`, `port`
// combination contained within.
//
RoutingProxy.prototype._getKey = function (options) {
  if (!options || ((!options.host || !options.port)
    && (!options.target || !options.target.host || !options.target.port))) {
    throw new Error('options.host and options.port or options.target are required.');
    return;
  }

  return [
    options.host || options.target.host,
    options.port || options.target.port
  ].join(':');
}
<|endoftext|>


<commit_before>/** Layout for restful simplified editors.
 *
 * @xtype administratorrestfulsimplifiedlayout
 * */
Ext.define('devilry.extjshelpers.RestfulSimplifiedLayout', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.administratorrestfulsimplifiedlayout',
    requires: ['devilry.extjshelpers.ErrorList'],
    border: 0,

    config: {
        /**
         * @cfg
         * Items for the ``Ext.form.Panel`` used to edit the RestfulSimplified object. (Required).
         */
        editformitems: undefined,

        /**
         * @cfg
         * ``Ext.data.Model`` for the RestfulSimplified object. (Required).
         */
        model: undefined,

        /**
         * @cfg
         * Does the RestfulSimplified support delete? (Required).
         */
        supports_delete: undefined,

        /**
         * @cfg
         * List of foreign key field names in the model. (Required).
         */
        foreignkeyfieldnames: undefined
    },

    bodyStyle: {
        'background-color': 'transparent'
    },

    initComponent: function() {
        var me = this;

        var savebuttonargs = {
            xtype: 'button',
            text: 'Save',
            scale: 'large',
            iconCls: 'icon-save-32',
            handler: function() {
                me.errorlist.clearErrors();
                me.editform.getForm().submit({
                    submitEmptyText: true,
                    waitMsg: 'Saving item...',
                    success: function(form, action) {
                        var record = action.record;
                        me.editform.loadRecord(record); // Need to load the record. If not, the proxy will do a POST instead of PUT on next save.
                        me.readonly();
                    },
                    failure: function(form, action) {
                        var errormessages = action.operation.responseData.items.errormessages;
                        Ext.each(errormessages, function(errormessage) {
                            me.errorlist.addError(errormessage);
                        });
                    }
                });
            }
        };

        var deletebuttonargs = {
            xtype: 'button',
            text: 'Delete',
            flex: 0,
            hidden: !this.supports_delete,
            scale: 'large',
            iconCls: 'icon-delete-32',
            handler: function() {
                Ext.MessageBox.show({
                    title: 'Confirm delete',
                    msg: 'Are you sure you want to delete?',
                    animateTarget: this,
                    buttons: Ext.Msg.YESNO,
                    icon: Ext.Msg.ERROR,
                    fn: function(btn) {
                        if(btn == 'yes') {
                            me.deleteCurrent();
                        }
                    }
                });
            }
        };

        var clicktoeditbuttonargs = {
            xtype: 'button',
            text: 'Click to edit',
            scale: 'large',
            iconCls: 'icon-edit-32',
            listeners: {
                click: function(button, pressed) {
                    me.editable();
                    //me.errorlist.addError('Hello world');
                    //me.errorlist.addError('This is a long error message. Message message message message message message message message message message message message message message message message message message message message message message message message message message message message.');
                }
            }
        };

        this.overlayBar = Ext.create('Ext.container.Container', {
            floating: true,
            cls: 'form-overlay-bar',
            height: 40,
            width: 300,
            layout: {
                type: 'hbox',
                align: 'stretch',
                pack: 'end'
            },
            items: [
                deletebuttonargs,
                {xtype: 'component', width: 10},
                clicktoeditbuttonargs
            ]
        });

        this.errorlist = Ext.create('devilry.extjshelpers.ErrorList', {});

        var editformargs = {
            id: me.getChildIdBySuffix('editform'),
            xtype: 'form',
            model: this.model,
            items: this.editformitems,

            // Fields will be arranged vertically, stretched to full width
            layout: 'anchor',
            defaults: {
                anchor: '100%',
            },

            cls: 'editform',
            bodyCls: 'editform-body',

            // Disable by default
            disabled: true,

            // Only save button. Other buttons are in overlayBar
            buttons: [
                savebuttonargs
            ]
        };

        Ext.apply(this, {
            items: [
                this.errorlist,
                editformargs
            ],
            layout: 'fit'
        });
        this.callParent(arguments);

        this.editform = Ext.getCmp(editformargs.id);
    },

    deleteCurrent: function() {
        this.editform.submit({
            submitEmptyText: true,
            method: 'DELETE',
            waitMsg: 'Deleting item...',
            success: function() {
            }
        });
    },

    getChildIdBySuffix: function(idsuffix) {
        return this.id + '-' + idsuffix;
    },

    /** Load the request record into the form. */
    loadRecordFromStore: function (record_id) {
        var me = this;
        Ext.ModelManager.getModel(this.model).load(record_id, {
            success: function(record) {
                me.editform.loadRecord(record);
                //var title = Ext.String.format('Edit', record.data.long_name, record.data.short_name);
                //me.editform.setTitle(title);
                var fields = me.editform.getForm().getFields();
                Ext.each(me.foreignkeyfieldnames, function(fieldname) {
                    var field = fields.filter('name', fieldname).items[0];
                    field.store.load(function(store, records, successful) {
                        field.setValue(record.data[fieldname]);
                    });
                });
            }
        });
    },

    getFormButtonBar: function() {
        return this.editform.dockedItems.items[0];
    },

    readonly: function() {
        this.editform.disable();
        this.overlayBar.show();
        this.overlayBar.alignTo(this.editform, 'tr-tr');
        this.getFormButtonBar().hide();
    },
    editable: function() {
        this.editform.enable();
        this.overlayBar.hide();
        this.getFormButtonBar().show();
    },

    loadUpdateMode: function(record_id) {
        this.loadRecordFromStore(record_id);
        this.readonly();
    },
    loadCreateMode: function() {
        this.getFormButtonBar().hide(); // NOTE: This is required for some reason?
        this.editable();
    },
    loadMode: function(mode, record_id) {
        if(mode == "update") {
            this.loadUpdateMode(record_id);
        } else if(mode == "create") {
            this.loadCreateMode();
        }
    }
});
<commit_msg>apps.extjshelpers: Correct config for RestfulSimplifiedLayout.<commit_after>/** Layout for restful simplified editors.
 *
 * @xtype administratorrestfulsimplifiedlayout
 * */
Ext.define('devilry.extjshelpers.RestfulSimplifiedLayout', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.administratorrestfulsimplifiedlayout',
    requires: ['devilry.extjshelpers.ErrorList'],
    border: 0,

    config: {
        /**
         * @cfg
         * Items for the ``Ext.form.Panel`` used to edit the RestfulSimplified object. (Required).
         */
        editformitems: undefined,

        /**
         * @cfg
         * ``Ext.data.Model`` for the RestfulSimplified object. (Required).
         */
        model: undefined,

        /**
         * @cfg
         * Does the RestfulSimplified support delete? (Required).
         */
        supports_delete: undefined,

        /**
         * @cfg
         * List of foreign key field names in the model. (Required).
         */
        foreignkeyfieldnames: undefined
    },

    bodyStyle: {
        'background-color': 'transparent'
    },

    constructor: function(config) {
        this.callParent([config]);
        this.initConfig(config);
    },

    initComponent: function() {
        var me = this;

        var savebuttonargs = {
            xtype: 'button',
            text: 'Save',
            scale: 'large',
            iconCls: 'icon-save-32',
            handler: function() {
                me.errorlist.clearErrors();
                me.editform.getForm().submit({
                    submitEmptyText: true,
                    waitMsg: 'Saving item...',
                    success: function(form, action) {
                        var record = action.record;
                        me.editform.loadRecord(record); // Need to load the record. If not, the proxy will do a POST instead of PUT on next save.
                        me.readonly();
                    },
                    failure: function(form, action) {
                        var errormessages = action.operation.responseData.items.errormessages;
                        Ext.each(errormessages, function(errormessage) {
                            me.errorlist.addError(errormessage);
                        });
                    }
                });
            }
        };

        var deletebuttonargs = {
            xtype: 'button',
            text: 'Delete',
            flex: 0,
            hidden: !this.supports_delete,
            scale: 'large',
            iconCls: 'icon-delete-32',
            handler: function() {
                Ext.MessageBox.show({
                    title: 'Confirm delete',
                    msg: 'Are you sure you want to delete?',
                    animateTarget: this,
                    buttons: Ext.Msg.YESNO,
                    icon: Ext.Msg.ERROR,
                    fn: function(btn) {
                        if(btn == 'yes') {
                            me.deleteCurrent();
                        }
                    }
                });
            }
        };

        var clicktoeditbuttonargs = {
            xtype: 'button',
            text: 'Click to edit',
            scale: 'large',
            iconCls: 'icon-edit-32',
            listeners: {
                click: function(button, pressed) {
                    me.editable();
                    //me.errorlist.addError('Hello world');
                    //me.errorlist.addError('This is a long error message. Message message message message message message message message message message message message message message message message message message message message message message message message message message message message.');
                }
            }
        };

        this.overlayBar = Ext.create('Ext.container.Container', {
            floating: true,
            cls: 'form-overlay-bar',
            height: 40,
            width: 300,
            layout: {
                type: 'hbox',
                align: 'stretch',
                pack: 'end'
            },
            items: [
                deletebuttonargs,
                {xtype: 'component', width: 10},
                clicktoeditbuttonargs
            ]
        });

        this.errorlist = Ext.create('devilry.extjshelpers.ErrorList', {});

        var editformargs = {
            id: me.getChildIdBySuffix('editform'),
            xtype: 'form',
            model: this.model,
            items: this.editformitems,

            // Fields will be arranged vertically, stretched to full width
            layout: 'anchor',
            defaults: {
                anchor: '100%',
            },

            cls: 'editform',
            bodyCls: 'editform-body',

            // Disable by default
            disabled: true,

            // Only save button. Other buttons are in overlayBar
            buttons: [
                savebuttonargs
            ]
        };

        Ext.apply(this, {
            items: [
                this.errorlist,
                editformargs
            ],
            layout: 'fit'
        });
        this.callParent(arguments);

        this.editform = Ext.getCmp(editformargs.id);
    },

    deleteCurrent: function() {
        this.editform.submit({
            submitEmptyText: true,
            method: 'DELETE',
            waitMsg: 'Deleting item...',
            success: function() {
            }
        });
    },

    getChildIdBySuffix: function(idsuffix) {
        return this.id + '-' + idsuffix;
    },

    /** Load the request record into the form. */
    loadRecordFromStore: function (record_id) {
        var me = this;
        Ext.ModelManager.getModel(this.model).load(record_id, {
            success: function(record) {
                me.editform.loadRecord(record);
                var fields = me.editform.getForm().getFields();
                Ext.each(me.foreignkeyfieldnames, function(fieldname) {
                    var field = fields.filter('name', fieldname).items[0];
                    field.store.load(function(store, records, successful) {
                        field.setValue(record.data[fieldname]);
                    });
                });
            }
        });
    },

    getFormButtonBar: function() {
        return this.editform.dockedItems.items[0];
    },

    readonly: function() {
        this.editform.disable();
        this.overlayBar.show();
        this.overlayBar.alignTo(this.editform, 'tr-tr');
        this.getFormButtonBar().hide();
    },
    editable: function() {
        this.editform.enable();
        this.overlayBar.hide();
        this.getFormButtonBar().show();
    },

    loadUpdateMode: function(record_id) {
        this.loadRecordFromStore(record_id);
        this.readonly();
    },
    loadCreateMode: function() {
        this.getFormButtonBar().hide(); // NOTE: This is required for some reason?
        this.editable();
    },
    loadMode: function(mode, record_id) {
        if(mode == "update") {
            this.loadUpdateMode(record_id);
        } else if(mode == "create") {
            this.loadCreateMode();
        }
    }
});
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc service
 * @description
 *
 * This service is a simple wrapper around a list of Objects.
 * It provides some convenience methods for manipulating the list.
 * It's main purpose is to make breadcrumb handling less cumbersome.
 *
 * @constructor
 */
function CatBreadcrumbsService() {
    var _bc = [];
    var that = this;

    this.clear = function () {
        _bc = [];
    };

    this.set = function (bc) {
        _bc = bc;
    };

    this.get = function () {
        return _bc;
    };

    this.addFirst = function (entry) {
        _bc.unshift(entry);
    };

    this.push = function (entry) {
        _bc.push(entry);
    };

    this.pop = function () {
        _bc.pop();
    };

    this.length = function () {
        return _bc.length;
    };

    function capitalize(string) {
        return string.charAt(0).toUpperCase() + string.substring(1);
    }

    this.replaceLast = function (newVal) {
        _bc[_bc.length - 1] = newVal;
    };

    function splitShiftAndJoin(path, amount) {
        return _.initial(path.split('/'), amount).join('/');
    }

    /**
     * This method auto-generates the breadcrumbs from a given view configuration
     * @param {Object} config a config object as provided to CatBaseDetailController
     * @return {Array} an array which represents the 'ui stack' of directly related parents
     */
    this.generateFromConfig = function (config) {
        that.clear();
        var uiStack = [];
        if (!_.isUndefined(config.endpoint.parentEndpoint)) {
            var currentEndpoint = config.endpoint;
            var parentEndpoint = currentEndpoint.parentEndpoint;
            var parentUrl = config.baseUrl;
            var count = 0;

            while (!_.isUndefined(parentEndpoint)) {
                var parent = config.parents[count++];
                parentUrl = splitShiftAndJoin(parentUrl, 1);

                var detailBreadcrumb = {
                    url: '#' + parentUrl + '?tab=' + window.cat.util.pluralize(currentEndpoint.getEndpointName()),
                    title: parent.name
                };
                uiStack.unshift(detailBreadcrumb);
                that.addFirst(detailBreadcrumb);

                parentUrl = splitShiftAndJoin(parentUrl, 1);
                var breadcrumb = {
                    title: capitalize(window.cat.util.pluralize(parentEndpoint.getEndpointName())),
                    url: '#' + parentUrl
                };
                that.addFirst(breadcrumb);

                currentEndpoint = parentEndpoint;
                parentEndpoint = currentEndpoint.parentEndpoint;
            }
        } else {
            that.push({
                title: capitalize(window.cat.util.pluralize(config.endpoint.getEndpointName())),
                url: '#' + config.baseUrl
            });
        }
        return uiStack;
    };
}

angular.module('cat.service').service('catBreadcrumbsService', CatBreadcrumbsService);

// TODO remove in future release
angular.module('cat.service').service('$breadcrumbs', CatBreadcrumbsService);<commit_msg>added i18n to cat-breadcrumbs-service<commit_after>'use strict';

/**
 * @ngdoc service
 * @name catBreadcrumbService
 * @service
 *
 * @description
 *
 * This service is a simple wrapper around a list of Objects.
 * It provides some convenience methods for manipulating the list.
 * It's main purpose is to make breadcrumb handling less cumbersome.
 *
 * @constructor
 */
function CatBreadcrumbsService() {
    var _bc = [];
    var that = this;

    this.clear = function () {
        _bc = [];
    };

    this.set = function (bc) {
        _bc = bc;
    };

    this.get = function () {
        return _bc;
    };

    this.addFirst = function (entry) {
        _bc.unshift(entry);
    };

    this.removeFirst = function () {
        return _bc.shift();
    };

    this.push = function (entry) {
        _bc.push(entry);
    };

    this.pop = function () {
        return _bc.pop();
    };

    this.length = function () {
        return _bc.length;
    };

    function capitalize(string) {
        return string.charAt(0).toUpperCase() + string.substring(1);
    }

    this.replaceLast = function (newVal) {
        _bc[_bc.length - 1] = newVal;
    };

    function splitShiftAndJoin(path, amount) {
        return _.initial(path.split('/'), amount).join('/');
    }

    /**
     * This method auto-generates the breadcrumbs from a given view configuration
     * @param {Object} config a config object as provided to CatBaseDetailController
     * @return {Array} an array which represents the 'ui stack' of directly related parents
     */
    this.generateFromConfig = function (config) {
        that.clear();
        var uiStack = [];
        if (!_.isUndefined(config.endpoint.parentEndpoint)) {
            var currentEndpoint = config.endpoint;
            var parentEndpoint = currentEndpoint.parentEndpoint;
            var parentUrl = config.baseUrl;
            var count = 0;

            while (!_.isUndefined(parentEndpoint)) {
                var parent = config.parents[count++];
                parentUrl = splitShiftAndJoin(parentUrl, 1);

                var detailBreadcrumb = {
                    url: '#' + parentUrl + '?tab=' + currentEndpoint.getEndpointName(),
                    title: parent.name
                };
                uiStack.unshift(detailBreadcrumb);
                that.addFirst(detailBreadcrumb);

                parentUrl = splitShiftAndJoin(parentUrl, 1);
                var breadcrumb = {
                    title: capitalize(window.cat.util.pluralize(parentEndpoint.getEndpointName())),
                    key: 'cc.catalysts.cat-breadcrumbs.entry.' + config.endpoint.getEndpointName(),
                    url: '#' + parentUrl
                };
                that.addFirst(breadcrumb);

                currentEndpoint = parentEndpoint;
                parentEndpoint = currentEndpoint.parentEndpoint;
            }
        } else {
            that.push({
                title: capitalize(window.cat.util.pluralize(config.endpoint.getEndpointName())),
                key: 'cc.catalysts.cat-breadcrumbs.entry.' + config.endpoint.getEndpointName(),
                url: '#' + config.baseUrl
            });
        }
        return uiStack;
    };
}

angular.module('cat.service').service('catBreadcrumbsService', CatBreadcrumbsService);

// TODO remove in future release
angular.module('cat.service').service('$breadcrumbs', CatBreadcrumbsService);<|endoftext|>


<commit_before>/* global MultiMarket, ValueSummary */
'use strict'

angular.module('ripplecharts.landing', [
  'ui.state'
])
.filter('trust', ['$sce', function($sce) {
  return function(htmlCode) {
    return $sce.trustAsHtml(htmlCode)
  }
}])
.config(function config($stateProvider) {
  $stateProvider.state('landing', {
    url: '/',
    views: {
      'main': {
        controller: 'LandingCtrl',
        templateUrl: 'landing/landing.tpl.html'
      }
    },
    data: {},
    resolve: {
      gateInit: function(gateways) {
        return gateways.promise
      }
    }
  })
})
.controller('LandingCtrl', function LandingCtrl($scope, $state, gateways) {

  var api = new ApiHandler(API)
  var donut = new ValueSummary({
    id: 'metricDetail',
    gateways: gateways
  })

  var exchangeRates = {}
  var refreshInterval
  var rateInterval

  var valueCurrencies = {
    USD: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',  // bitstamp
    EUR: 'rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq', // gatehub
    JPY: 'r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN', // tokoyo jpy
    CNY: 'rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y', // ripplefox
    XRP: ''
  }

  $scope.metrics = {
    totalTradeVolume: {
      label: 'Total XRP Trade Volume (All Exchanges)',
      link: '#/xrp-markets'
    },
    tradeVolumeRCL: {
      label: 'XRP Ledger Trade Volume (All Pairs)',
      link: '#/trade-volume'
    },
    paymentVolumeRCL: {
      label: 'XRP Ledger Payment Volume'
    },
    capitalizationXRP: {
      label: 'Market Capitalization',
      live: true
    },
    numAccounts: {
      label: '# of Ripple Accounts'
    }
  }

  for (var key in $scope.metrics) {
    $scope.metrics[key].key = key
  }

  $scope.currencies = Object.keys(valueCurrencies)
  $scope.selectedCurrency = 'USD'

  $scope.showMetricDetails = function(name) {

    if (name) {
      $scope.selectedMetric = $scope.metrics[name]
    }

    donut.load($scope.selectedMetric, {
      rate: 1 / $scope.valueRate,
      currency: $scope.selectedCurrency
    })
  }

  /**
   * getExchangeRates
   */

  function getExchangeRates(c, callback) {
    function getRate(live) {
      return new Promise(function(resolve, reject) {
        api.exchangeRate({
          base: {
            currency: 'XRP'
          },
          counter: {
            currency: c.currency,
            issuer: c.issuer
          },
          period: live ? '' : 'day',
          live: live
        },
        function(err, rate) {
          if (err) {
            reject(err)
          } else {
            resolve(rate)
          }
        })
      })
    }

    Promise.all([
      getRate(true),
      getRate()
    ])
    .then(function(rates) {
      exchangeRates[c.currency + '.' + c.issuer] = {
        live: rates[0],
        period: rates[1]
      }

      callback(null, exchangeRates[c.currency + '.' + c.issuer])
    })
    .catch(callback)
  }

  /**
   * setValueRates
   */

  // set the value rate for the selected
  // currency, retreiving it from the
  // API if its not cached or
  // if we are updating the cache
  function setValueRates(currency, useCached, callback) {
    var issuer = valueCurrencies[currency]
    $scope.valueRate = undefined
    $scope.valueRatePair = ''

    function apply() {
      $scope.valueRate = exchangeRates[currency + '.' + issuer]
      $scope.valueRatePair = 'XRP/' + currency
      callback()
    }

    if (currency === 'XRP') {
      $scope.valueRate = {
        live: 1,
        period: 1
      }

      $scope.valueRatePair = ''
      callback()
      return

    // check for cached
    } else if (useCached && exchangeRates[currency + '.' + issuer]) {
      apply()
      return
    }

    getExchangeRates({
      currency: currency,
      issuer: issuer
    }, function(err) {
      if (err) {
        console.log(err)
        callback(err)
        $scope.$apply()
        return
      }

      apply()
      $scope.$apply()
    })
  }

  /**
   * setMetricValue
   */

  function setMetricValue(metric, value) {
    if (value) {
      $scope.metrics[metric].total = value
    }

    if (!$scope.valueRate) {
      return
    }

    var rate = $scope.metrics[metric].live ?
      $scope.valueRate.live : $scope.valueRate.period

    if ($scope.metrics[metric].total && rate) {
      $scope.metrics[metric].converted =
        rate * $scope.metrics[metric].total
    }
  }

  /**
   * refreshRate
   */

  function refreshRate(useCached) {
    setValueRates($scope.selectedCurrency, useCached, function() {
      for (var k in $scope.metrics) {
        setMetricValue(k)
      }

      $scope.showMetricDetails()
    })
  }

  /**
   * getTotalAccounts
   */

  /*
  NOTE: currently unused
  function getTotalAccounts() {
    api.getTotalAccounts(null, function(err, total) {
      if (err) {
        console.log(err)
      }

      $scope.metrics.numAccounts.total = total
      $scope.$apply()
    })
  }
  */

  /**
   * handleNewAccount
   */

  function handleNewAccount(tx) {
    var meta = tx.meta
    if (meta.TransactionResult !== 'tesSUCCESS') {
      return
    }

    meta.AffectedNodes.forEach(function(affNode) {
      if (affNode.CreatedNode &&
          affNode.CreatedNode.LedgerEntryType === 'AccountRoot') {
        $scope.metrics.numAccounts.total++
        $scope.$apply()
      }
    })
  }

  /**
   * filterXRPVolume
   */

  function filterXRPVolume(components) {
    var total = 0
    var count = 0

    components.forEach(function(c) {
      if (c.base.currency === 'XRP' ||
         c.counter.currency === 'XRP') {
        total += c.converted_amount
        count += c.count
      }
    })

    return {
      source: 'rcl',
      base_volume: total,
      count: count
    }
  }

  /**
   * getMetricValues
   */

  function getMetricValues() {

    // get payments
    api.getPaymentVolume({}, function(err, resp) {
      var total = 0
      var components

      if (err || !resp || !resp.rows || !resp.rows.length) {
        console.log(err)

      } else {
        components = resp.rows[0].components
        total = resp.rows[0].total
      }

      $scope.metrics.paymentVolumeRCL.components = components
      setMetricValue('paymentVolumeRCL', total)
      if ($scope.selectedMetric === $scope.metrics.paymentVolumeRCL) {
        $scope.showMetricDetails()
      }
      $scope.$apply()
    })

    // get RCL exchanges
    api.getExchangeVolume({}, function(err, resp) {
      var total = 0
      var components
      var xrpVolume

      if (err || !resp || !resp.rows || !resp.rows.length) {
        console.log(err)

      } else {
        components = resp.rows[0].components
        total = resp.rows[0].total
      }

      $scope.metrics.tradeVolumeRCL.components = components
      xrpVolume = filterXRPVolume(components)

      // add RCL XRP volume to total metric
      if ($scope.metrics.totalTradeVolume.components &&
          !$scope.metrics.totalTradeVolume.withRCL) {
        $scope.metrics.totalTradeVolume.components.unshift(xrpVolume)
        setMetricValue('totalTradeVolume',
          $scope.metrics.totalTradeVolume.total + xrpVolume.base_volume)

        // sort by volume
        $scope.metrics.totalTradeVolume.components.sort(function(a, b) {
          return b.base_volume - a.base_volume
        })
      }

      setMetricValue('tradeVolumeRCL', total)
      if ($scope.selectedMetric !== $scope.metrics.paymentVolumeRCL) {
        $scope.showMetricDetails()
      }
      $scope.$apply()
    })

    // get external exchanges
    api.getExternalMarkets({}, function(err, resp) {
      var total = 0
      var components = {}
      var xrpVolume

      if (err || !resp) {
        console.log(err)
        return
      }

      resp.components.forEach(function(c) {
        var amount = Number(c.base_volume)

        if (!amount) {
          return
        }

        if (!components[c.source]) {
          components[c.source] = {
            source: c.source,
            base_volume: 0,
            count: 0,
            components: []
          }
        }

        components[c.source].base_volume += amount
        components[c.source].count += c.count || 0

        components[c.source].components.push({
          key: 'XRP/' + c.counter_currency,
          value: amount,
          amount: amount,
          counter_currency: c.counter_currency,
          count: c.count
        })
      })

      $scope.metrics.totalTradeVolume.withRCL = false
      $scope.metrics.totalTradeVolume.components = []
      Object.keys(components).map(function(c) {
        $scope.metrics.totalTradeVolume.components.push(components[c])
        total += components[c].base_volume
      })

      // add RCL XRP volume
      if ($scope.metrics.tradeVolumeRCL.components) {
        xrpVolume = filterXRPVolume($scope.metrics.tradeVolumeRCL.components)
        $scope.metrics.totalTradeVolume.components.unshift(xrpVolume)
        total += xrpVolume.base_volume
        $scope.metrics.totalTradeVolume.withRCL = true
      }

      // sort by volume
      $scope.metrics.totalTradeVolume.components.sort(function(a, b) {
        return b.base_volume - a.base_volume
      })

      setMetricValue('totalTradeVolume', total)
      if ($scope.selectedMetric === $scope.metrics.totalTradeVolume) {
        $scope.showMetricDetails()
      }
      $scope.$apply()
    })
  }

  $scope.$watch('selectedCurrency', function(d) {
    var name

    switch (d) {
      case 'USD':
        $scope.sign = '$'
        break
      case 'JPY':
        $scope.sign = '¥'
        break
      case 'CNY':
        $scope.sign = '¥'
        break
      case 'EUR':
        $scope.sign = '€'
        break
      case 'XRP':
        $scope.sign = ''
        break
      default:
        $scope.sign = ''
    }

    for (name in $scope.metrics) {
      $scope.metrics[name].converted = undefined
    }

    refreshRate(true)
  })

  // add to new accounts total
  remote.on('transaction_all', handleNewAccount)

  remote.on('connect', function() {
    // getTotalAccounts()
  })

  // getTotalAccounts()

  // get 'fixed' multimarket charts for
  // the most important markets
  var markets = new MultiMarket({
    url: API,
    id: 'topMarkets',
    fixed: true,
    clickable: true,
    updateInterval: 60,
    gateways: gateways
  })

  markets.list(9)

  markets.on('chartClick', function(chart) {
    $state.transitionTo('markets.pair', {
      base: chart.base.currency +
        (chart.base.issuer ? ':' + chart.base.issuer : ''),
      counter: chart.counter.currency +
      (chart.counter.issuer ? ':' + chart.counter.issuer : ''),
      interval: '5m',
      range: '1d',
      type: store.get('chartType') || 'line'
    })
  })

  $scope.$watch('totalXRP', function(d) {
    setMetricValue('capitalizationXRP', d)
  })

  // stuff to do when leaving the page
  $scope.$on('$destroy', function() {
    markets.list([])

    clearInterval(refreshInterval)
    clearInterval(rateInterval)
  })

  // reload data when coming back online
  $scope.$watch('online', function(online) {
    if (online) {
      markets.reload()
    }
  })

  // get value metrics at load time and every 5 minutes
  getMetricValues()
  refreshInterval = setInterval(getMetricValues, 60 * 5 * 1000)
  rateInterval = setInterval(refreshRate, 15 * 1000)
  $scope.showMetricDetails('totalTradeVolume')
})

<commit_msg>fix bug<commit_after>/* global MultiMarket, ValueSummary */
'use strict'

angular.module('ripplecharts.landing', [
  'ui.state'
])
.filter('trust', ['$sce', function($sce) {
  return function(htmlCode) {
    return $sce.trustAsHtml(htmlCode)
  }
}])
.config(function config($stateProvider) {
  $stateProvider.state('landing', {
    url: '/',
    views: {
      'main': {
        controller: 'LandingCtrl',
        templateUrl: 'landing/landing.tpl.html'
      }
    },
    data: {},
    resolve: {
      gateInit: function(gateways) {
        return gateways.promise
      }
    }
  })
})
.controller('LandingCtrl', function LandingCtrl($scope, $state, gateways) {

  var api = new ApiHandler(API)
  var donut = new ValueSummary({
    id: 'metricDetail',
    gateways: gateways
  })

  var exchangeRates = {}
  var refreshInterval
  var rateInterval

  var valueCurrencies = {
    USD: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',  // bitstamp
    EUR: 'rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq', // gatehub
    JPY: 'r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN', // tokoyo jpy
    CNY: 'rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y', // ripplefox
    XRP: ''
  }

  $scope.metrics = {
    totalTradeVolume: {
      label: 'Total XRP Trade Volume (All Exchanges)',
      link: '#/xrp-markets'
    },
    tradeVolumeRCL: {
      label: 'XRP Ledger Trade Volume (All Pairs)',
      link: '#/trade-volume'
    },
    paymentVolumeRCL: {
      label: 'XRP Ledger Payment Volume'
    },
    capitalizationXRP: {
      label: 'Market Capitalization',
      live: true
    },
    numAccounts: {
      label: '# of Ripple Accounts'
    }
  }

  for (var key in $scope.metrics) {
    $scope.metrics[key].key = key
  }

  $scope.currencies = Object.keys(valueCurrencies)
  $scope.selectedCurrency = 'USD'

  $scope.showMetricDetails = function(name) {

    if (name) {
      $scope.selectedMetric = $scope.metrics[name]
    }

    if ($scope.valueRate) {
      donut.load($scope.selectedMetric, {
        rate: 1 / $scope.valueRate.period,
        currency: $scope.selectedCurrency
      })
    }
  }

  /**
   * getExchangeRates
   */

  function getExchangeRates(c, callback) {
    function getRate(live) {
      return new Promise(function(resolve, reject) {
        api.exchangeRate({
          base: {
            currency: 'XRP'
          },
          counter: {
            currency: c.currency,
            issuer: c.issuer
          },
          period: live ? '' : 'day',
          live: live
        },
        function(err, rate) {
          if (err) {
            reject(err)
          } else {
            resolve(rate)
          }
        })
      })
    }

    Promise.all([
      getRate(true),
      getRate()
    ])
    .then(function(rates) {
      exchangeRates[c.currency + '.' + c.issuer] = {
        live: rates[0],
        period: rates[1]
      }

      callback(null, exchangeRates[c.currency + '.' + c.issuer])
    })
    .catch(callback)
  }

  /**
   * setValueRates
   */

  // set the value rate for the selected
  // currency, retreiving it from the
  // API if its not cached or
  // if we are updating the cache
  function setValueRates(currency, useCached, callback) {
    var issuer = valueCurrencies[currency]
    $scope.valueRate = undefined
    $scope.valueRatePair = ''

    function apply() {
      $scope.valueRate = exchangeRates[currency + '.' + issuer]
      $scope.valueRatePair = 'XRP/' + currency
      callback()
    }

    if (currency === 'XRP') {
      $scope.valueRate = {
        live: 1,
        period: 1
      }

      $scope.valueRatePair = ''
      callback()
      return

    // check for cached
    } else if (useCached && exchangeRates[currency + '.' + issuer]) {
      apply()
      return
    }

    getExchangeRates({
      currency: currency,
      issuer: issuer
    }, function(err) {
      if (err) {
        console.log(err)
        callback(err)
        $scope.$apply()
        return
      }

      apply()
      $scope.$apply()
    })
  }

  /**
   * setMetricValue
   */

  function setMetricValue(metric, value) {
    if (value) {
      $scope.metrics[metric].total = value
    }

    if (!$scope.valueRate) {
      return
    }

    var rate = $scope.metrics[metric].live ?
      $scope.valueRate.live : $scope.valueRate.period

    if ($scope.metrics[metric].total && rate) {
      $scope.metrics[metric].converted =
        rate * $scope.metrics[metric].total
    }
  }

  /**
   * refreshRate
   */

  function refreshRate(useCached) {
    setValueRates($scope.selectedCurrency, useCached, function() {
      for (var k in $scope.metrics) {
        setMetricValue(k)
      }

      $scope.showMetricDetails()
    })
  }

  /**
   * getTotalAccounts
   */

  /*
  NOTE: currently unused
  function getTotalAccounts() {
    api.getTotalAccounts(null, function(err, total) {
      if (err) {
        console.log(err)
      }

      $scope.metrics.numAccounts.total = total
      $scope.$apply()
    })
  }
  */

  /**
   * handleNewAccount
   */

  function handleNewAccount(tx) {
    var meta = tx.meta
    if (meta.TransactionResult !== 'tesSUCCESS') {
      return
    }

    meta.AffectedNodes.forEach(function(affNode) {
      if (affNode.CreatedNode &&
          affNode.CreatedNode.LedgerEntryType === 'AccountRoot') {
        $scope.metrics.numAccounts.total++
        $scope.$apply()
      }
    })
  }

  /**
   * filterXRPVolume
   */

  function filterXRPVolume(components) {
    var total = 0
    var count = 0

    components.forEach(function(c) {
      if (c.base.currency === 'XRP' ||
         c.counter.currency === 'XRP') {
        total += c.converted_amount
        count += c.count
      }
    })

    return {
      source: 'rcl',
      base_volume: total,
      count: count
    }
  }

  /**
   * getMetricValues
   */

  function getMetricValues() {

    // get payments
    api.getPaymentVolume({}, function(err, resp) {
      var total = 0
      var components

      if (err || !resp || !resp.rows || !resp.rows.length) {
        console.log(err)

      } else {
        components = resp.rows[0].components
        total = resp.rows[0].total
      }

      $scope.metrics.paymentVolumeRCL.components = components
      setMetricValue('paymentVolumeRCL', total)
      if ($scope.selectedMetric === $scope.metrics.paymentVolumeRCL) {
        $scope.showMetricDetails()
      }
      $scope.$apply()
    })

    // get RCL exchanges
    api.getExchangeVolume({}, function(err, resp) {
      var total = 0
      var components
      var xrpVolume

      if (err || !resp || !resp.rows || !resp.rows.length) {
        console.log(err)

      } else {
        components = resp.rows[0].components
        total = resp.rows[0].total
      }

      $scope.metrics.tradeVolumeRCL.components = components
      xrpVolume = filterXRPVolume(components)

      // add RCL XRP volume to total metric
      if ($scope.metrics.totalTradeVolume.components &&
          !$scope.metrics.totalTradeVolume.withRCL) {
        $scope.metrics.totalTradeVolume.components.unshift(xrpVolume)
        setMetricValue('totalTradeVolume',
          $scope.metrics.totalTradeVolume.total + xrpVolume.base_volume)

        // sort by volume
        $scope.metrics.totalTradeVolume.components.sort(function(a, b) {
          return b.base_volume - a.base_volume
        })
      }

      setMetricValue('tradeVolumeRCL', total)
      if ($scope.selectedMetric !== $scope.metrics.paymentVolumeRCL) {
        $scope.showMetricDetails()
      }
      $scope.$apply()
    })

    // get external exchanges
    api.getExternalMarkets({}, function(err, resp) {
      var total = 0
      var components = {}
      var xrpVolume

      if (err || !resp) {
        console.log(err)
        return
      }

      resp.components.forEach(function(c) {
        var amount = Number(c.base_volume)

        if (!amount) {
          return
        }

        if (!components[c.source]) {
          components[c.source] = {
            source: c.source,
            base_volume: 0,
            count: 0,
            components: []
          }
        }

        components[c.source].base_volume += amount
        components[c.source].count += c.count || 0

        components[c.source].components.push({
          key: 'XRP/' + c.counter_currency,
          value: amount,
          amount: amount,
          counter_currency: c.counter_currency,
          count: c.count
        })
      })

      $scope.metrics.totalTradeVolume.withRCL = false
      $scope.metrics.totalTradeVolume.components = []
      Object.keys(components).map(function(c) {
        $scope.metrics.totalTradeVolume.components.push(components[c])
        total += components[c].base_volume
      })

      // add RCL XRP volume
      if ($scope.metrics.tradeVolumeRCL.components) {
        xrpVolume = filterXRPVolume($scope.metrics.tradeVolumeRCL.components)
        $scope.metrics.totalTradeVolume.components.unshift(xrpVolume)
        total += xrpVolume.base_volume
        $scope.metrics.totalTradeVolume.withRCL = true
      }

      // sort by volume
      $scope.metrics.totalTradeVolume.components.sort(function(a, b) {
        return b.base_volume - a.base_volume
      })

      setMetricValue('totalTradeVolume', total)
      if ($scope.selectedMetric === $scope.metrics.totalTradeVolume) {
        $scope.showMetricDetails()
      }
      $scope.$apply()
    })
  }

  $scope.$watch('selectedCurrency', function(d) {
    var name

    switch (d) {
      case 'USD':
        $scope.sign = '$'
        break
      case 'JPY':
        $scope.sign = '¥'
        break
      case 'CNY':
        $scope.sign = '¥'
        break
      case 'EUR':
        $scope.sign = '€'
        break
      case 'XRP':
        $scope.sign = ''
        break
      default:
        $scope.sign = ''
    }

    for (name in $scope.metrics) {
      $scope.metrics[name].converted = undefined
    }

    refreshRate(true)
  })

  // add to new accounts total
  remote.on('transaction_all', handleNewAccount)

  remote.on('connect', function() {
    // getTotalAccounts()
  })

  // getTotalAccounts()

  // get 'fixed' multimarket charts for
  // the most important markets
  var markets = new MultiMarket({
    url: API,
    id: 'topMarkets',
    fixed: true,
    clickable: true,
    updateInterval: 60,
    gateways: gateways
  })

  markets.list(9)

  markets.on('chartClick', function(chart) {
    $state.transitionTo('markets.pair', {
      base: chart.base.currency +
        (chart.base.issuer ? ':' + chart.base.issuer : ''),
      counter: chart.counter.currency +
      (chart.counter.issuer ? ':' + chart.counter.issuer : ''),
      interval: '5m',
      range: '1d',
      type: store.get('chartType') || 'line'
    })
  })

  $scope.$watch('totalXRP', function(d) {
    setMetricValue('capitalizationXRP', d)
  })

  // stuff to do when leaving the page
  $scope.$on('$destroy', function() {
    markets.list([])

    clearInterval(refreshInterval)
    clearInterval(rateInterval)
  })

  // reload data when coming back online
  $scope.$watch('online', function(online) {
    if (online) {
      markets.reload()
    }
  })

  // get value metrics at load time and every 5 minutes
  getMetricValues()
  refreshInterval = setInterval(getMetricValues, 60 * 5 * 1000)
  rateInterval = setInterval(refreshRate, 15 * 1000)
  $scope.showMetricDetails('totalTradeVolume')
})

<|endoftext|>


<commit_before>// 0) Connect to Mongo
conn = new Mongo();
db = conn.getDB("local");

// 1) Create a collection named “test”, and insert into this collection the documents
// found in this link (10 documents):
// http://docs.mongodb.org/manual/reference/bios-example-collection/
// Run from terminal
// mongoimport --db local --collection test --drop --file ./data.json --jsonArray

// 2) Write a CRUD operation(s) that changes the _id of “John McCarthy” to value 2.
doc = db.test.findOne(
	  {
		name: {
            first: "John",
            last: "McCarthy"
        }
    }
);
old_id = doc._id;
doc._id = 2;
db.test.insert(doc);
db.test.remove({_id: old_id});

// 3) Write a CRUD operation(s) that inserts the following new records into the
// collection:
new_bios = [
	{
		"_id" : 20,
		"name" : {
			"first" : "Alex",
			"last" : "Chen"
		},
		"birth" : ISODate("1933-08-27T04:00:00Z"),
		"death" : ISODate("1984-11-07T04:00:00Z"),
		"contribs" : ["C++", "Simula"],
		"awards" : [
			{
				"award" : "WPI Award",
				"year" : 1977,
				"by" : "WPI"
			}
		]
	},
 	{
		"_id" : 30,
		"name" : {
			"first" : "David",
			"last" : "Mark"
		},
		"birth" : ISODate("1911-04-12T04:00:00Z"),
		"death" : ISODate("2000-11-07T04:00:00Z"),
		"contribs" : ["C++", "FP", "Lisp"],
		"awards" : [
			{
				"award" : "WPI Award",
				"year" : 1963,
				"by" : "WPI"
			},
			{
				"award" : "Turing Award",
				"year" : 1966,
				"by" : "ACM"
			}
		]
	}
];
db.test.insert(new_bios);

// 4) Report all documents of people who got a “Turing Award” after 1976
cursor = db.test.find(
	{
		awards: {
        	$elemMatch: {
            	award: "Turing Award",
            	year: { $gt: 1976 }
        	}
      	}
	}
);
print("========================================================================");
print("4");
print("========================================================================");
cursor.forEach(printjson);

// 5) Report all documents of people who got less than 3 awards or have contribution
// in “FP”
cursor = db.test.find(
	{
		$or: [
			{ $where: "this.awards ? this.awards.length < 3 : false" },
			{ contribs: "FP" }
		]
	}
);
print("========================================================================");
print("5");
print("========================================================================");
cursor.forEach(printjson);

// 6) Report the contributions of “Dennis Ritchie” (only report the name and the
// contribution array)
cursor = db.test.find(
	{
        name: {
            "first": "Dennis",
            "last": "Ritchie"
        }
    },
    {
    	_id: 0,
    	name: 1,
    	contribs: 1
    }
);
print("========================================================================");
print("6");
print("========================================================================");
cursor.forEach(printjson);

// 7) Update the document of “Guido van Rossum” to add “OOP” to the contribution list.
db.test.update(
	{
        name: {
            "first": "Guido",
            "last": "van Rossum"
        }
	},
   	{
		$push: { contribs: "OOP" }
   }
);

// 8) Insert a new filed of type array, called “comments”, into the document of “Alex Chen”
// storing the following comments: “He taught in 3 universities”, “died from cancer”,
// “lived in CA”
db.test.update(
	{
        name: {
            "first": "Alex",
            "last": "Chen"
        }
	},
   	{
		$set: {
			comments: [
				"He taught in 3 universities",
				"died from cancer",
				"lived in CA"
			]
		}
   }
);

// 9) For each contribution by “Alex Chen”, say X, list the peoples’ names (fisrt and last)
// who have contribution X. E.g., Alex Chen has two contributions in “C++” and “Simula”.
// Then, the output should be similar to: a.
// { Contribution: “C++”, People: [{first: “Alex”, last: “Chen”},
// {first: “David”, last: “Mark”}]}, { Contribution: “Simula”, ....}
result = db.test.findOne(
	{
        name: {
            "first": "Alex",
            "last": "Chen"
        }
    },
    {
    	_id: 1,
    	contribs: 1
    }
);
cursor = db.test.find(
	{
		contribs: {
			$elemMatch: {
				$in: result.contribs
			}
		},
		_id: {
			$ne: result._id
		}
    },
    {
		_id: 0,
		name: 1,
		contribs: 1
    }
);
print("========================================================================");
print("9");
print("========================================================================");
cursor.forEach(printjson);

// 10) Report all documents where the first name matches the regular expression “Jo*”,
// where “*” means any number of characters. Report the documents sorted by the last name.
cursor = db.test.find(
	{
        "name.first": { $regex: /^Jo/ }
    },
    {
    	_id: 0,
    	name: 1
    }
).sort({ 'name.last': 1 });
print("========================================================================");
print("10");
print("========================================================================");
cursor.forEach(printjson);

// 11) Report the distinct organization that gave awards. This information can be found
// in the “by” field inside the “awards” array. The output should be an array of the
// distinct values, e.g., [“wpi’, “acm’, ...]
cursor = db.test.distinct(
	'awards.by'
)
print("========================================================================");
print("11");
print("========================================================================");
cursor.forEach(printjson);

// 12) Delete from all documents the “death” field.
db.test.update(
	{},
	{
		$unset: {
			death: 1
		}
	},
	{
		multi: true
	}
);

// 13) Delete from all documents any award given on 2011.
match_year = 2011
db.test.update(
	{
		"awards.year": match_year
    },
    {
		$pull: {
			awards: {
				year: match_year
			}
		}
    },
    {
		multi: true
	}
);

// 14) Update the award of document _id =30, which is given by WPI, and set the year
// to 1965.
db.test.update(
   	{
   		_id: 30,
   		"awards.by": "WPI"
   	},
   	{
   		$set: {
   			"awards.$.year": 1965
   		}
   	}
);

// 15) Add (copy) all the contributions of document _id = 3 to that of document _id = 30
doc = db.test.findOne(
	{
        _id: 3
    },
    {
    	_id: 0,
    	contribs: 1
    }
);
contribs = doc.contribs;

db.test.update(
	{
        _id: 30
	},
   	{
		$push: { contribs: { $each: contribs } }
   }
);

// 16) Report only the names (first and last) of those individuals who won at least two
// awards in 2001.
cursor=db.test.find(
	{
		awards.count('awards.year':2001):{ $gte: 2 }
	},
	{
		_id:0,
		name:1,
	}
);
print("========================================================================");
print("16");
print("========================================================================");
cursor.forEach(printjson);

// 17) Report the document with the largest id. First, you need to find the largest _id
// (using a CRUD statement), and then use that to report the corresponding document.
cursor = db.test.find().sort({ _id: -1 }).limit(1);
max_id = cursor.next()._id
doc = db.test.findOne(
	{
		_id: max_id
    }
);
print("========================================================================");
print("17");
print("========================================================================");
print(doc);

// 18) Report only one document where one of the awards is given by “ACM”.
cursor = db.test.find(
	{
		awards: {
        	$elemMatch: {
            	by: "ACM"
        	}
      	}
	}
);
print("========================================================================");
print("18");
print("========================================================================");
cursor.forEach(printjson);

// 19) Delete the documents inserted in Q3, i.e., _id = 20 and 30.
db.test.remove({_id: { $in: [20, 30] }});

// 20) Report the number of documents in the collection.
print("========================================================================");
print("20");
print("========================================================================");
print(db.test.count());
<commit_msg>Update project.js<commit_after>// 0) Connect to Mongo
conn = new Mongo();
db = conn.getDB("local");

// 1) Create a collection named “test”, and insert into this collection the documents
// found in this link (10 documents):
// http://docs.mongodb.org/manual/reference/bios-example-collection/
// Run from terminal
// mongoimport --db local --collection test --drop --file ./data.json --jsonArray

// 2) Write a CRUD operation(s) that changes the _id of “John McCarthy” to value 2.
db.test.update(
	  {
		"name": {
            "first": "John",
            "last": "McCarthy"
        }
	  },
	{
		  $set: {"_id":2}
	}
);

// 3) Write a CRUD operation(s) that inserts the following new records into the
// collection:
new_bios = [
	{
		"_id" : 20,
		"name" : {
			"first" : "Alex",
			"last" : "Chen"
		},
		"birth" : ISODate("1933-08-27T04:00:00Z"),
		"death" : ISODate("1984-11-07T04:00:00Z"),
		"contribs" : ["C++", "Simula"],
		"awards" : [
			{
				"award" : "WPI Award",
				"year" : 1977,
				"by" : "WPI"
			}
		]
	},
 	{
		"_id" : 30,
		"name" : {
			"first" : "David",
			"last" : "Mark"
		},
		"birth" : ISODate("1911-04-12T04:00:00Z"),
		"death" : ISODate("2000-11-07T04:00:00Z"),
		"contribs" : ["C++", "FP", "Lisp"],
		"awards" : [
			{
				"award" : "WPI Award",
				"year" : 1963,
				"by" : "WPI"
			},
			{
				"award" : "Turing Award",
				"year" : 1966,
				"by" : "ACM"
			}
		]
	}
];
db.test.insert(new_bios);

// 4) Report all documents of people who got a “Turing Award” after 1976
cursor = db.test.find(
	{
		awards: {
        	$elemMatch: {
            	award: "Turing Award",
            	year: { $gt: 1976 }
        	}
      	}
	}
);
print("========================================================================");
print("4");
print("========================================================================");
cursor.forEach(printjson);

// 5) Report all documents of people who got less than 3 awards or have contribution
// in “FP”
cursor = db.test.find(
	{
		$or: [
			{ $where: "this.awards ? this.awards.length < 3 : false" },
			{ contribs: "FP" }
		]
	}
);
print("========================================================================");
print("5");
print("========================================================================");
cursor.forEach(printjson);

// 6) Report the contributions of “Dennis Ritchie” (only report the name and the
// contribution array)
cursor = db.test.find(
	{
        name: {
            "first": "Dennis",
            "last": "Ritchie"
        }
    },
    {
    	_id: 0,
    	name: 1,
    	contribs: 1
    }
);
print("========================================================================");
print("6");
print("========================================================================");
cursor.forEach(printjson);

// 7) Update the document of “Guido van Rossum” to add “OOP” to the contribution list.
db.test.update(
	{
        name: {
            "first": "Guido",
            "last": "van Rossum"
        }
	},
   	{
		$push: { contribs: "OOP" }
   }
);

// 8) Insert a new filed of type array, called “comments”, into the document of “Alex Chen”
// storing the following comments: “He taught in 3 universities”, “died from cancer”,
// “lived in CA”
db.test.update(
	{
        name: {
            "first": "Alex",
            "last": "Chen"
        }
	},
   	{
		$set: {
			comments: [
				"He taught in 3 universities",
				"died from cancer",
				"lived in CA"
			]
		}
   }
);

// 9) For each contribution by “Alex Chen”, say X, list the peoples’ names (fisrt and last)
// who have contribution X. E.g., Alex Chen has two contributions in “C++” and “Simula”.
// Then, the output should be similar to: a.
// { Contribution: “C++”, People: [{first: “Alex”, last: “Chen”},
// {first: “David”, last: “Mark”}]}, { Contribution: “Simula”, ....}
var contributions=[];
result = db.test.find(
	{
        name: {
            "first": "Alex",
            "last": "Chen"
        }
    }
).forEach(
	function(u){
		contributions=u.contribs
	}
	);
cursor = db.test.find(
	{
		contribs: {
			$elemMatch: {
				$in: contributions
			}
		},
    {
		_id: 0,
		name: 1,
		contribs: 1
    }
);
print("========================================================================");
print("9");
print("========================================================================");
cursor.forEach(printjson);

// 10) Report all documents where the first name matches the regular expression “Jo*”,
// where “*” means any number of characters. Report the documents sorted by the last name.
cursor = db.test.find(
	{
        "name.first": { $regex: /^Jo/ }
    },
    {
    	_id: 0,
    	name: 1
    }
).sort({ 'name.last': 1 });
print("========================================================================");
print("10");
print("========================================================================");
cursor.forEach(printjson);

// 11) Report the distinct organization that gave awards. This information can be found
// in the “by” field inside the “awards” array. The output should be an array of the
// distinct values, e.g., [“wpi’, “acm’, ...]
cursor = db.test.distinct(
	'awards.by'
)
print("========================================================================");
print("11");
print("========================================================================");
cursor.forEach(printjson);

// 12) Delete from all documents the “death” field.
db.test.update(
	{},
	{
		$unset: {
			death: 1
		}
	},
	{
		multi: true
	}
);

// 13) Delete from all documents any award given on 2011.
match_year = 2011
db.test.update(
	{
		"awards.year": match_year
    },
    {
		$pull: {
			awards: {
				year: match_year
			}
		}
    },
    {
		multi: true
	}
);

// 14) Update the award of document _id =30, which is given by WPI, and set the year
// to 1965.
db.test.update(
   	{
   		_id: 30,
   		"awards.by": "WPI"
   	},
   	{
   		$set: {
   			"awards.$.year": 1965
   		}
   	}
);

// 15) Add (copy) all the contributions of document _id = 3 to that of document _id = 30
doc = db.test.findOne(
	{
        _id: 3
    },
    {
    	_id: 0,
    	contribs: 1
    }
);
contribs = doc.contribs;

db.test.update(
	{
        _id: 30
	},
   	{
		$push: { contribs: { $each: contribs } }
   }
);

// 16) Report only the names (first and last) of those individuals who won at least two
// awards in 2001.
cursor=db.test.find(
	{
		awards.count('awards.year':2001):{ $gte: 2 }
	},
	{
		_id:0,
		name:1,
	}
);
print("========================================================================");
print("16");
print("========================================================================");
cursor.forEach(printjson);

// 17) Report the document with the largest id. First, you need to find the largest _id
// (using a CRUD statement), and then use that to report the corresponding document.
cursor = db.test.find().sort({ _id: -1 }).limit(1);
max_id = cursor.next()._id
doc = db.test.findOne(
	{
		_id: max_id
    }
);
print("========================================================================");
print("17");
print("========================================================================");
print(doc);

// 18) Report only one document where one of the awards is given by “ACM”.
cursor = db.test.find(
	{
		awards: {
        	$elemMatch: {
            	by: "ACM"
        	}
      	}
	}
);
print("========================================================================");
print("18");
print("========================================================================");
cursor.forEach(printjson);

// 19) Delete the documents inserted in Q3, i.e., _id = 20 and 30.
db.test.remove({_id: { $in: [20, 30] }});

// 20) Report the number of documents in the collection.
print("========================================================================");
print("20");
print("========================================================================");
print(db.test.count());
<|endoftext|>


<commit_before>import React from 'react';
import {Helmet} from 'react-helmet';
import {connect} from 'react-redux';
import {Link, Route, Switch} from 'react-router-dom';
import {css} from 'emotion';
import i18n, { i18nInit } from 'i18n';
import {MEDIA, SHADES} from 'styles/consts';
import {ToastContainer} from 'react-toastify';
import Loadable from 'react-loadable';
import {Col, Container, Input, Row} from 'reactstrap';
import {auth, database} from 'services/firebase';
import {setLanguageAction, setTranslations, setUserIdAction} from 'actions/root';
import {setCustomLocations, setSelectedLocations} from 'actions/config';
import {TRANSLATIONS} from 'consts';
import Discord from 'images/discord.png';

import SpinnerModal from 'components/SpinnerModal/SpinnerModal';
import SpyIcon from 'components/SpyIcon/SpyIcon';

const LoadableSettings = Loadable({loader: () => import('../Settings/Settings'), loading: SpinnerModal});
const LoadableGame = Loadable({loader: () => import('../Game/Game'), loading: SpinnerModal});
const LoadableJoinRoom = Loadable({loader: () => import('../JoinRoom/JoinRoom'), loading: SpinnerModal});

export class App extends React.Component{
  state = { loading: true };

  componentDidMount() {
    const { setUserId } = this.props;
    i18nInit.then(() => {
      if (!this.props.language) {
        this.setLanguage(i18n.language);
      }
      this.setState({ loading: false });
    });
    auth.signInAnonymously().then((authUser) => {
      setUserId(authUser.user.uid);
      this.importTranslations();
    });
  }

  importTranslations = async () => {
    // imported less than 24 hours ago
    if(this.props.translationsImportTime && Date.now() - this.props.translationsImportTime < 24 * 60 * 60 * 1000) return null;

    const translationsSnapshot = await database.ref('translations').once('value');
    this.props.setTranslations(translationsSnapshot.val() || {});
  };

  setLanguage = (language) => {
    const { setLanguage } = this.props;
    i18n.changeLanguage(language);
    setLanguage(language);
  };

  render() {
    const { language, translations = {} } = this.props;

    if (this.state.loading) return null;

    return (
      <Container className={styles.container}>
        <Helmet
          defaultTitle="Spyfall"
        >
          <meta name="description" content="Ensemble React Web Template" />
        </Helmet>
        <Row className="justify-content-center">
          <Col xs={12} md={8} lg={6}>
            <Row className="align-items-center justify-content-center">
              <Col xs="12" sm="4" className="text-center">
                <Link to="/">
                  <SpyIcon />
                  Spyfall
                </Link>
              </Col>
              <Col xs="12" sm="8" className={`${styles.languageSelector} text-center`}>
                <Input type="select" name="languages" id="languages" value={language} onChange={(evt) => this.setLanguage(evt.target.value)}>
                  {TRANSLATIONS.map((translation) =>
                    <option key={translation.id} value={translation.id}>{translation.name} - {translations[translation.id] || 0}%</option>
                  )}
                </Input>
              </Col>
            </Row>
            <Switch>
              <Route exact path="/settings" component={LoadableSettings} />
              <Route exact path="/join" component={LoadableJoinRoom} />
              <Route exact path="/" component={LoadableGame} />
            </Switch>
            <Row className={styles.localizationContainer}>
              <Col className="text-center">

                Help us with translations!
              </Col>
            </Row>
            <Row className={styles.localizationContainer}>
              <Col className="text-center">

                  Join Spyfall Discord server

              </Col>
            </Row>
            <Row className={styles.footer}>
              <Col className="text-center">
                <Row>
                  <Col>
                    Spyfall is designed by Alexandr Ushan and published by Hobby World
                  </Col>
                </Row>
                <Row>
                  <Col>
                    Spy icon created by Dan Hetteix (TheNounProject)
                  </Col>
                </Row>
                <Row>
                  <Col>
                    https://github.com/adrianocola/spyfall
                  </Col>
                </Row>
              </Col>
            </Row>
          </Col>
        </Row>
        <ToastContainer />
      </Container>
    );
  }
}

const styles = {
  container: css({
    paddingTop: 50,
  }),
  languageSelector: css({
    [MEDIA.xsOnly]: {
      marginTop: 10,
    },
  }),
  localizationContainer: css({
    marginTop: 40,
    fontSize: '0.8rem',
  }),
  localizationLink: css({
    marginLeft: 10,
  }),
  discordImage: css({
    height: 32,
  }),
  discordLink: css({
    marginLeft: 10,
  }),
  footer: css({
    marginTop: 20,
    marginBottom: 30,
    fontSize: '0.8rem',
    color: SHADES.light,
  }),
};

const mapStateToProps = (state) => ({
  language: state.root.language,
  translations: state.root.translations,
  translationsImportTime: state.root.translationsImportTime,
});

const mapDispatchToProps = (dispatch) => ({
  setUserId: (userId) => dispatch(setUserIdAction(userId)),
  setLanguage: (language) => dispatch(setLanguageAction(language)),
  setSelectedLocations: (selectedLocations) => dispatch(setSelectedLocations(selectedLocations)),
  setCustomLocations: (customLocations) => dispatch(setCustomLocations(customLocations)),
  setTranslations: (translations) => dispatch(setTranslations(translations)),
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
<commit_msg>Updated meta description<commit_after>import React from 'react';
import {Helmet} from 'react-helmet';
import {connect} from 'react-redux';
import {Link, Route, Switch} from 'react-router-dom';
import {css} from 'emotion';
import i18n, { i18nInit } from 'i18n';
import {MEDIA, SHADES} from 'styles/consts';
import {ToastContainer} from 'react-toastify';
import Loadable from 'react-loadable';
import {Col, Container, Input, Row} from 'reactstrap';
import {auth, database} from 'services/firebase';
import {setLanguageAction, setTranslations, setUserIdAction} from 'actions/root';
import {setCustomLocations, setSelectedLocations} from 'actions/config';
import {TRANSLATIONS} from 'consts';
import Discord from 'images/discord.png';

import SpinnerModal from 'components/SpinnerModal/SpinnerModal';
import SpyIcon from 'components/SpyIcon/SpyIcon';

const LoadableSettings = Loadable({loader: () => import('../Settings/Settings'), loading: SpinnerModal});
const LoadableGame = Loadable({loader: () => import('../Game/Game'), loading: SpinnerModal});
const LoadableJoinRoom = Loadable({loader: () => import('../JoinRoom/JoinRoom'), loading: SpinnerModal});

export class App extends React.Component{
  state = { loading: true };

  componentDidMount() {
    const { setUserId } = this.props;
    i18nInit.then(() => {
      if (!this.props.language) {
        this.setLanguage(i18n.language);
      }
      this.setState({ loading: false });
    });
    auth.signInAnonymously().then((authUser) => {
      setUserId(authUser.user.uid);
      this.importTranslations();
    });
  }

  importTranslations = async () => {
    // imported less than 24 hours ago
    if(this.props.translationsImportTime && Date.now() - this.props.translationsImportTime < 24 * 60 * 60 * 1000) return null;

    const translationsSnapshot = await database.ref('translations').once('value');
    this.props.setTranslations(translationsSnapshot.val() || {});
  };

  setLanguage = (language) => {
    const { setLanguage } = this.props;
    i18n.changeLanguage(language);
    setLanguage(language);
  };

  render() {
    const { language, translations = {} } = this.props;

    if (this.state.loading) return null;

    return (
      <Container className={styles.container}>
        <Helmet
          defaultTitle="Spyfall"
        >
          <meta name="description" content="Free online spyfall game" />
        </Helmet>
        <Row className="justify-content-center">
          <Col xs={12} md={8} lg={6}>
            <Row className="align-items-center justify-content-center">
              <Col xs="12" sm="4" className="text-center">
                <Link to="/">
                  <SpyIcon />
                  Spyfall
                </Link>
              </Col>
              <Col xs="12" sm="8" className={`${styles.languageSelector} text-center`}>
                <Input type="select" name="languages" id="languages" value={language} onChange={(evt) => this.setLanguage(evt.target.value)}>
                  {TRANSLATIONS.map((translation) =>
                    <option key={translation.id} value={translation.id}>{translation.name} - {translations[translation.id] || 0}%</option>
                  )}
                </Input>
              </Col>
            </Row>
            <Switch>
              <Route exact path="/settings" component={LoadableSettings} />
              <Route exact path="/join" component={LoadableJoinRoom} />
              <Route exact path="/" component={LoadableGame} />
            </Switch>
            <Row className={styles.localizationContainer}>
              <Col className="text-center">

                Help us with translations!
              </Col>
            </Row>
            <Row className={styles.localizationContainer}>
              <Col className="text-center">

                  Join Spyfall Discord server

              </Col>
            </Row>
            <Row className={styles.footer}>
              <Col className="text-center">
                <Row>
                  <Col>
                    Spyfall is designed by Alexandr Ushan and published by Hobby World
                  </Col>
                </Row>
                <Row>
                  <Col>
                    Spy icon created by Dan Hetteix (TheNounProject)
                  </Col>
                </Row>
                <Row>
                  <Col>
                    https://github.com/adrianocola/spyfall
                  </Col>
                </Row>
              </Col>
            </Row>
          </Col>
        </Row>
        <ToastContainer />
      </Container>
    );
  }
}

const styles = {
  container: css({
    paddingTop: 50,
  }),
  languageSelector: css({
    [MEDIA.xsOnly]: {
      marginTop: 10,
    },
  }),
  localizationContainer: css({
    marginTop: 40,
    fontSize: '0.8rem',
  }),
  localizationLink: css({
    marginLeft: 10,
  }),
  discordImage: css({
    height: 32,
  }),
  discordLink: css({
    marginLeft: 10,
  }),
  footer: css({
    marginTop: 20,
    marginBottom: 30,
    fontSize: '0.8rem',
    color: SHADES.light,
  }),
};

const mapStateToProps = (state) => ({
  language: state.root.language,
  translations: state.root.translations,
  translationsImportTime: state.root.translationsImportTime,
});

const mapDispatchToProps = (dispatch) => ({
  setUserId: (userId) => dispatch(setUserIdAction(userId)),
  setLanguage: (language) => dispatch(setLanguageAction(language)),
  setSelectedLocations: (selectedLocations) => dispatch(setSelectedLocations(selectedLocations)),
  setCustomLocations: (customLocations) => dispatch(setCustomLocations(customLocations)),
  setTranslations: (translations) => dispatch(setTranslations(translations)),
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
<|endoftext|>