<commit_before>let promptly = require('promptly')
let migrationRunner = require('../../db/migration-runner')

module.exports = (cmd, options) => {

    promptly.prompt('Name: ', function (err, value) {
        // err is always null in this case, because no validators are set
        console.log(value);
        migrationRunner(() => {
            process.exit(0);
        });
    });

};<commit_msg>Pseudo-code for setup tool<commit_after>/**
 * Setup command
 *
 * This command is written (and should continue to be written) as idempotent.
 * It should be able to be run over and over to complete installation, even if cancelled by the user at a given step.
 */
let promptly = require('promptly')
let migrationRunner = require('../../db/migration-runner')

module.exports = (cmd, options) => {

    // Create a .env file if there is not one
    // If we create one, inform the user they should configure it now (and exit)
    // Also notify them to install mysql and redis if they haven't yet (and they plan to do so locally)

    // Test the redis connection

    // Test the mysql database connection

    // Run migrations

    // Check if there are any users in the users table
    // If not, walk the user through creating an admin user (interactively)

    //promptly.prompt('Name: ', function (err, value) {
    //    // err is always null in this case, because no validators are set
    //    console.log(value);
    //    migrationRunner(() => {
    //        process.exit(0);
    //    });
    //});

};<|endoftext|>


<commit_before>/*global
    define,
    require: true
*/

define(["./../core/game-state", "./../client/renderer", "./../core/delta-timer", "./mixins/input-funcs"],
  function (GameState, Renderer, DeltaTimer, input_functions) {

    'use strict';

    function GameClient(io, viewportEl){
      this.viewportEl = viewportEl;
      this.updateid = 0;

      this.connect(io);
    }

    GameClient.prototype = {

      start_game: function(me, others){

        this.local_time = 0;
        this.state = new GameState();
        this.renderer = new Renderer(this.state, this.viewportEl);

        this.state.add_players([me]);
        this.state.add_players(others);

        this.me = this.state.find_player(me.id);
        this.input_seq = 0;

        new DeltaTimer(4, function(delta, time){
          this.local_time = time;
        });

      },

      start: function(){
        this.update(new Date().getTime());
      },

      stop: function(){
        window.cancelAnimationFrame(this.updateid);
      },

      update: function(t){

        var inputs = this.sample_inputs();

        if(inputs.length){
          this.send_inputs(inputs);
        }

        this.updateid = window.requestAnimationFrame( this.update.bind(this), this.viewportEl );
      },

      send_inputs: function(inputs){

        //Update what sequence we are on now
        this.input_seq += 1;

        var time = this.local_time.toFixed(3);

        this.state.store_input(this.me.id, inputs, time, this.input_seq);

        //Send the packet of information to the server.
        //The input packets are labelled with an 'i' in front.
        var server_packet = 'i.';
        server_packet += inputs.join('-') + '.';
        server_packet += time.replace('.','-') + '.';
        server_packet += this.input_seq;
        //Go
        this.socket.send(  server_packet  );

      },

      update_physics: function(){

      },

      on_netmessage : function(data) {

      },

      on_disconnect : function(){

      },

      on_serverupdate_recieved : function(data){
        console.log(data);
      },

      on_entered_game : function(data){
        this.start_game(data.me, data.others);
      },

      on_player_joined : function(data){
        this.state.add_players([data.player]);
      },

      on_player_left : function(data){
        var player = this.state.find_player(data.playerid);
        this.state.remove_player(player);
      },

      connect : function (io) {

        //Store a local reference to our connection to the server
        var socket = this.socket = io.connect();

        socket.on('entered-game', this.on_entered_game.bind(this));

        socket.on('player-joined', this.on_player_joined.bind(this));

        socket.on('player-left', this.on_player_left.bind(this));

        socket.on('onserverupdate', this.on_serverupdate_recieved.bind(this));

       /* //Sent when we are disconnected (network, server down, etc)
        this.socket.on('disconnect', this.on_disconnect.bind(this) );
        //Sent each tick of the server simulation. This is our authoritive update
        this.socket.on('onserverupdate', this.on_serverupdate_recieved.bind(this) );
        //Handle when we connect to the server, showing state and storing id's.
        this.socket.on('onconnected', this.on_connected.bind(this) );
        //On error we just show that we are not connected for now. Can print the data.
        this.socket.on('error', this.on_disconnect.bind(this) );
        //On message from the server, we parse the commands and send it to the handlers
        this.socket.on('message', this.on_netmessage.bind(this) );*/

    }

  };

  input_functions.call(GameClient.prototype);

  return GameClient;

});
<commit_msg>server posts back to client<commit_after>/*global
    define,
    require: true
*/

define(["./../core/game-state", "./../client/renderer", "./../core/delta-timer", "./mixins/input-funcs"],
  function (GameState, Renderer, DeltaTimer, input_functions) {

    'use strict';

    function GameClient(io, viewportEl){
      this.viewportEl = viewportEl;
      this.updateid = 0;
      this.net_offset = 100;
      this.buffer_size = 2;               //The size of the server history to keep for rewinding/interpolating.

      this.connect(io);
    }

    GameClient.prototype = {

      start_game: function(me, others){

        this.local_time = 0;
        this.state = new GameState();
        this.renderer = new Renderer(this.state, this.viewportEl);

        this.state.add_players([me]);
        this.state.add_players(others);

        this.me = this.state.find_player(me.id);
        this.input_seq = 0;

        new DeltaTimer(4, function(delta, time){
          this.local_time = time;
        });

      },

      start: function(){
        this.update(new Date().getTime());
      },

      stop: function(){
        window.cancelAnimationFrame(this.updateid);
      },

      update: function(t){

        var inputs = this.sample_inputs();

        if(inputs.length){
          this.send_inputs(inputs);
        }

        this.updateid = window.requestAnimationFrame( this.update.bind(this), this.viewportEl );
      },

      send_inputs: function(inputs){

        //Update what sequence we are on now
        this.input_seq += 1;

        var time = this.local_time.toFixed(3);

        this.state.store_input(this.me.id, inputs, time, this.input_seq);

        //Send the packet of information to the server.
        //The input packets are labelled with an 'i' in front.
        var server_packet = 'i.';
        server_packet += inputs.join('-') + '.';
        server_packet += time.replace('.','-') + '.';
        server_packet += this.input_seq;
        //Go
        this.socket.send(  server_packet  );

      },

      update_time_from_server = function(server_time){
        this.client_time = server_time - (this.net_offset/1000);
        console.log(this.client_time);
      },

      record_server_update: function(server_update){

            //One approach is to set the position directly as the server tells you.
            //This is a common mistake and causes somewhat playable results on a local LAN, for example,
            //but causes terrible lag when any ping/latency is introduced. The player can not deduce any
            //information to interpolate with so it misses positions, and packet loss destroys this approach
            //even more so. See 'the bouncing ball problem' on Wikipedia.

            //Cache the data from the server,
            //and then play the timeline
            //back to the player with a small delay (net_offset), allowing
            //interpolation between the points.
        this.server_updates.push(server_update);

            //we limit the buffer in seconds worth of updates
            //60fps*buffer seconds = number of samples
        if(this.server_updates.length >= ( 60*this.buffer_size )) {
            this.server_updates.splice(0,1);
        }
      },

      update_physics: function(){

      },

      on_netmessage : function(data) {

      },

      on_disconnect : function(){

      },

      on_serverupdate_recieved : function(data){
        this.update_time_from_server(data.t);
        this.record_server_update(data);
      },

      on_entered_game : function(data){
        this.start_game(data.me, data.others);
      },

      on_player_joined : function(data){
        this.state.add_players([data.player]);
      },

      on_player_left : function(data){
        var player = this.state.find_player(data.playerid);
        this.state.remove_player(player);
      },

      connect : function (io) {

        //Store a local reference to our connection to the server
        var socket = this.socket = io.connect();

        socket.on('entered-game', this.on_entered_game.bind(this));

        socket.on('player-joined', this.on_player_joined.bind(this));

        socket.on('player-left', this.on_player_left.bind(this));

        socket.on('onserverupdate', this.on_serverupdate_recieved.bind(this));

       /* //Sent when we are disconnected (network, server down, etc)
        this.socket.on('disconnect', this.on_disconnect.bind(this) );
        //Sent each tick of the server simulation. This is our authoritive update
        this.socket.on('onserverupdate', this.on_serverupdate_recieved.bind(this) );
        //Handle when we connect to the server, showing state and storing id's.
        this.socket.on('onconnected', this.on_connected.bind(this) );
        //On error we just show that we are not connected for now. Can print the data.
        this.socket.on('error', this.on_disconnect.bind(this) );
        //On message from the server, we parse the commands and send it to the handlers
        this.socket.on('message', this.on_netmessage.bind(this) );*/

    }

  };

  input_functions.call(GameClient.prototype);

  return GameClient;

});
<|endoftext|>


<commit_before>import * as actions from './actions';
import Todo from './todo';
import {Range} from 'immutable';
import {getRandomString} from '../../lib/getrandomstring';
import {register} from '../dispatcher';
import {todosCursor} from '../state';

export const dispatchToken = register(({action, data}) => {

  switch (action) {
    case actions.addHundredTodos:
      todosCursor(todos => {
        return todos.update('list', list => {
          return list.withMutations(list => {
            Range(0, 100).forEach(i => {
              const id = getRandomString();
              list.push(new Todo({
                id: id,
                title: `Item #${id}`
              }));
            });
          });
        });
      });
      break;

    case actions.addTodo:
      todosCursor(todos => {
        return todos
          .update('list', (list) => {
            // Always denote what data represents. Favour readability over wtf.
            // Try to resist being smart ass. Fuck pride.
            // https://www.youtube.com/watch?v=ruhFmBrl4GM
            const todo = data;
            const newTodo = todo.merge({
              id: getRandomString()
            });
            return list.push(newTodo);
          })
          .set('newTodo', new Todo);
      });
      break;

    case actions.clearAll:
      todosCursor(todos => {
        return todos
          .update('list', list => list.clear())
          .set('newTodo', new Todo);
      });
      break;

    case actions.deleteTodo:
      todosCursor(todos => {
        const todo = data;
        return todos.update('list', list => list.delete(list.indexOf(todo)));
      });
      break;

    case actions.onEditableState:
      todosCursor(todos => {
        const {id, name, state} = data;
        return todos.setIn(['editables', id, name], state);
      });
      break;

    case actions.onNewTodoFieldChange:
      todosCursor(todos => {
        const {name, value} = data;
        return todos.setIn(['newTodo', name], value);
      });
      break;

    case actions.saveTitle:
      todosCursor(todos => {
        const {id, title} = data;
        return todos.update('list', list => {
          const idx = list.findIndex(todo => todo.id === id);
          return list.setIn([idx, 'title'], title);
        });
      });
      break;

  }

});
<commit_msg>Chore.<commit_after>import * as actions from './actions';
import Todo from './todo';
import {Range} from 'immutable';
import {getRandomString} from '../../lib/getrandomstring';
import {register} from '../dispatcher';
import {todosCursor} from '../state';

export const dispatchToken = register(({action, data}) => {

  switch (action) {
    case actions.addHundredTodos:
      todosCursor(todos => {
        return todos.update('list', list => list.withMutations(list => {
          Range(0, 100).forEach(i => {
            const id = getRandomString();
            list.push(new Todo({
              id,
              title: `Item #${id}`
            }));
          });
        }));
      });
      break;

    case actions.addTodo:
      todosCursor(todos => {
        return todos
          .update('list', (list) => {
            // Always denote what data represents. Favour readability over wtf.
            // Try to resist being smart ass. Fuck pride.
            // https://www.youtube.com/watch?v=ruhFmBrl4GM
            const todo = data;
            const newTodo = todo.merge({
              id: getRandomString()
            });
            return list.push(newTodo);
          })
          .set('newTodo', new Todo);
      });
      break;

    case actions.clearAll:
      todosCursor(todos => {
        return todos
          .update('list', list => list.clear())
          .set('newTodo', new Todo);
      });
      break;

    case actions.deleteTodo:
      todosCursor(todos => {
        const todo = data;
        return todos.update('list', list => list.delete(list.indexOf(todo)));
      });
      break;

    case actions.onEditableState:
      todosCursor(todos => {
        const {id, name, state} = data;
        return todos.setIn(['editables', id, name], state);
      });
      break;

    case actions.onNewTodoFieldChange:
      todosCursor(todos => {
        const {name, value} = data;
        return todos.setIn(['newTodo', name], value);
      });
      break;

    case actions.saveTitle:
      todosCursor(todos => {
        const {id, title} = data;
        return todos.update('list', list => {
          const idx = list.findIndex(todo => todo.id === id);
          return list.setIn([idx, 'title'], title);
        });
      });
      break;

  }

});
<|endoftext|>


<commit_before>import request from 'superagent';
import APIError from '../errors/APIError';
import APIResponse from '../responses/APIResponse';
import Authentication from '../modules/common/Authentication';
import Cache from '../cache/Cache';
import RequestError from '../errors/RequestError';
import hashCode from '../utils/hashCode';
import mapValues from '../utils/mapValues';
import sortObjectByKey from '../utils/sortObjectByKey';

/**
 * The options available to use on a client constructor.
 * @typedef {Object} ClientOptions
 * @property {string} realm - The realm/region this client is for.
 * @property {string} applicationId - The application ID of this client.
 * @property {string} [accessToken=null] - The access token for this client,
 *   if it will be using one.
 * @param {string} [language=null] - The default localization language
 *   to use for API responses.
 * @param {?number} [options.cacheTimeToLive=600000] - The time to live in
 *   milliseconds for the client's data cache entries. `null` if no there is
 *   no TTL.
 */

/**
 * The options available to use when making a single request.
 * @typedef {Object} RequestOptions
 * @property {string} [type] - The API to send this request to. One of: `wot`,
 *   `wotb`, `wotx`, `wows`, `wowp`, `wgn`.
 * @property {string} [realm] - The realm/region to use for the request.
 *   One of: `ru`, `eu`, `na`, `kr`, `asia`, `xbox`, `ps4`.
 */

/**
 * Emitted when a client request is successful.
 * @event BaseClient#requestFulfilled
 * @param {APIResponse} response - The response object.
 */

/**
 * Emitted when a client request is rejected.
 * @event BaseClient#requestRejected
 * @param {RequestError} error - The error object.
 */

/**
 * Mapping between realms and their TLDs.
 * @type {Object}
 * @constant
 * @private
 */
const REALM_TLD = {
  ru: 'ru',
  eu: 'eu',
  na: 'com',
  kr: 'kr',
  asia: 'asia',
  xbox: 'xbox',
  ps4: 'ps4',
};

/**
 * Functions which generate the base URIs for various APIs.
 * @type {Object}
 * @constant
 * @private
 */
const BASE_URI = {
  wot: realm => `https://api.worldoftanks.${REALM_TLD[realm]}/wot`,
  wotb: realm => `https://api.wotblitz.${REALM_TLD[realm]}/wotb`,
  wotx: realm => `https://api-${REALM_TLD[realm]}-console.worldoftanks.com/wotx`,
  wows: realm => `https://api.worldofwarships.${REALM_TLD[realm]}/wows`,
  wowp: realm => `https://api.worldofwarplanes.${REALM_TLD[realm]}/wowp`,
  wgn: realm => `https://api.worldoftanks.${REALM_TLD[realm]}/wgn`,
};

/**
 * Returns the base URI for a given realm and API type.
 * @param {string} realm - The realm/region of the server.
 * @param {string} type - The desired API.
 * @returns {string} The base URI for the API that was specified.
 * @throws {Error} Thrown if the given `realm` or `type` don't exist.
 * @private
 */
const getBaseUri = (realm, type) => {
  if (!REALM_TLD[realm] || !BASE_URI[type]) {
    throw new Error('Unknown realm or type given.');
  }

  return BASE_URI[type](realm);
};

/**
 * @classdesc The base API client.
 */
class BaseClient {
  /**
   * Constructor.
   * @param {Object} options - The client options.
   * @param {string} options.type - The type of API this client is for.
   * @param {string} options.realm - The realm/region this client is for.
   * @param {string} options.applicationId - The application ID of this client.
   * @param {string} [options.accessToken=null] - The access token for this
   *   client, if it will be using one.
   * @param {string} [options.language=null] - The default localization language
   *   to use for API responses.
   * @param {?number} [options.cacheTimeToLive=600000] - The time to live in
   *   milliseconds for the client's data cache entries. `null` if no there is
   *   no TTL.
   * @throws {TypeError} Thrown if options are not well-formed.
   */
  constructor(options) {
    const {
      type,
      realm,
      applicationId,
      accessToken = null,
      language = null,
      cacheTimeToLive = 600 * 1000,
    } = options;

    if (typeof realm !== 'string' || !REALM_TLD[realm.toLowerCase()]) {
      throw new TypeError('Must specify a valid realm for the client.');
    } else if (typeof applicationId !== 'string') {
      throw new TypeError('Must specify an application ID for the client.');
    }

    const normalizedRealm = realm.toLowerCase();

    /**
     * The type of API this client is for.
     * @type {string}
     */
    this.type = type;

    /**
     * The realm, i.e. region of this client.
     * @type {string}
     */
    this.realm = normalizedRealm;

    /**
     * The application ID for this client.
     * @type {string}
     */
    this.applicationId = applicationId;

    /**
     * The access token for this client.
     * @type {?string}
     */
    this.accessToken = accessToken;

    /**
     * The default localization language for this client.
     * @type {?string}
     */
    this.language = language;

    /**
     * The client's Authentication module.
     * @type {Authentication}
     */
    this.authentication = new Authentication(this);

    /**
     * The base API URI for this client.
     * @type {string}
     * @private
     */
    this.baseUri = getBaseUri(normalizedRealm, type);

    /**
     * The API response cache.
     * @type {Cache}
     * @private
     */
    this.cache = new Cache({ timeToLive: cacheTimeToLive });
  }

  /**
   * Normalizes a given parameter type so the API can consume it.
   * @param {*} parameter - The parameter to normalize.
   * @returns {*} The normalized parameter.
   * @static
   * @private
   */
  static normalizeParameterValue(parameter) {
    if (Array.isArray(parameter)) {
      return parameter.join(',');
    } else if (parameter instanceof Date) {
      return parameter.toISOString();
    }

    return parameter;
  }

  /**
   * Sends a GET request to the API.
   * @param {string} method - The method to request.
   * @param {Object} [params={}] - The parameters to include in the request.
   * @param {RequestOptions} [options={}] - Options used to override client defaults.
   * @returns {Promise.<APIResponse, Error>} Returns a promise resolving to the
   *   returned API data, or rejecting with an error.
   */
  get(method, params = {}, options = {}) {
    return this.request(method, params, { ...options, method: 'GET' });
  }

  /**
   * Sends a POST request to the API.
   * @param {string} method - The method to request.
   * @param {Object} [params={}] - The parameters to include in the request.
   * @param {RequestOptions} [options={}] - Options used to override client defaults.
   * @returns {Promise.<APIResponse, Error>} Returns a promise resolving to the
   *   returned API data, or rejecting with an error.
   */
  post(method, params = {}, options = {}) {
    return this.request(method, params, { ...options, method: 'POST' });
  }

  /**
   * Fetches data from an endpoint method.
   * @param {string} apiMethod - The method to request.
   * @param {Object} [params={}] - The parameters to include in the request.
   * @param {RequestOptions} [options={}] - Options used to override client defaults.
   * @returns {Promise.<APIResponse, Error>} Returns a promise resolving to the
   *   returned API data, or rejecting with an error.
   * @private
   */
  request(apiMethod, params = {}, options = {}) {
    return new Promise((resolve) => {
      const { type = this.type, realm = this.realm, method = 'GET' } = options;

      if (typeof apiMethod !== 'string') {
        throw new TypeError('Expected API method to be a string.');
      }

      const normalizedApiMethod = apiMethod.toLowerCase();
      const normalizedRealm = realm.toLowerCase();

      // construct the request URL
      const baseUrl = normalizedRealm === this.realm
        ? this.baseUri
        : getBaseUri(normalizedRealm, type);
      const requestUrl = `${baseUrl}/${normalizedApiMethod.replace(/^\/*(.+?)\/*$/, '$1')}/`;

      // construct the payload
      const payload = {
        application_id: this.applicationId,
        access_token: this.accessToken,
        language: this.language,
        ...params,
      };

      const normalizedPayload = mapValues(payload, this.constructor.normalizeParameterValue);

      // compute information for the cache
      const { application_id, ...rest } = normalizedPayload; // eslint-disable-line no-unused-vars
      const cacheKey = hashCode(`${requestUrl}${JSON.stringify(sortObjectByKey(rest))}`);

      const fulfillResponse = (response) => {
        const { error = null } = response.body;

        if (error) {
          // Wargaming API error
          throw new APIError({
            client: this,
            statusCode: response.status,
            method: normalizedApiMethod,
            error,
          });
        }

        return new APIResponse({
          client: this,
          requestRealm: normalizedRealm,
          method: normalizedApiMethod,
          body: response.body,
        });
      };

      const rejectResponse = (value) => {
        // check if this is a HTTP error or a Wargaming error
        if (value instanceof APIError) {
          throw value;
        }

        const { response: { error, req } } = value;

        throw new RequestError({
          message: value.body.error.message,
          client: this,
          statusCode: error.status,
          url: req.url,
        });
      };

      if (method === 'GET') {
        const cached = this.cache.get(cacheKey);

        if (cached) {
          const response = new APIResponse({
            client: this,
            requestRealm: normalizedRealm,
            method: normalizedApiMethod,
            body: cached,
          });

          resolve(response);
        }

        const promise = request.get(requestUrl)
          .query(normalizedPayload)
          .then(fulfillResponse)
          .then((apiResponse) => {
            this.cache.set(cacheKey, apiResponse.body);

            return apiResponse;
          })
          .catch(rejectResponse);

        resolve(promise);
      } else if (method === 'POST') {
        const promise = request.post(requestUrl)
          .type('form')
          .send(normalizedPayload)
          .then(fulfillResponse)
          .catch(rejectResponse);

        resolve(promise);
      }

      // we should never get here
      throw new Error('Received invalid request method.');
    });
  }
}

export default BaseClient;
<commit_msg>Trim some docs.<commit_after>import request from 'superagent';
import APIError from '../errors/APIError';
import APIResponse from '../responses/APIResponse';
import Authentication from '../modules/common/Authentication';
import Cache from '../cache/Cache';
import RequestError from '../errors/RequestError';
import hashCode from '../utils/hashCode';
import mapValues from '../utils/mapValues';
import sortObjectByKey from '../utils/sortObjectByKey';

/**
 * The options available to use on a client constructor.
 * @typedef {Object} ClientOptions
 * @property {string} realm - The realm/region this client is for.
 * @property {string} applicationId - The application ID of this client.
 * @property {string} [accessToken=null] - The access token for this client,
 *   if it will be using one.
 * @param {string} [language=null] - The default localization language
 *   to use for API responses.
 * @param {?number} [options.cacheTimeToLive=600000] - The time to live in
 *   milliseconds for the client's data cache entries. `null` if no there is
 *   no TTL.
 */

/**
 * The options available to use when making a single request.
 * @typedef {Object} RequestOptions
 * @property {string} [type] - The API to send this request to. One of: `wot`,
 *   `wotb`, `wotx`, `wows`, `wowp`, `wgn`.
 * @property {string} [realm] - The realm/region to use for the request.
 *   One of: `ru`, `eu`, `na`, `kr`, `asia`, `xbox`, `ps4`.
 */

/**
 * Mapping between realms and their TLDs.
 * @type {Object}
 * @constant
 * @private
 */
const REALM_TLD = {
  ru: 'ru',
  eu: 'eu',
  na: 'com',
  kr: 'kr',
  asia: 'asia',
  xbox: 'xbox',
  ps4: 'ps4',
};

/**
 * Functions which generate the base URIs for various APIs.
 * @type {Object}
 * @constant
 * @private
 */
const BASE_URI = {
  wot: realm => `https://api.worldoftanks.${REALM_TLD[realm]}/wot`,
  wotb: realm => `https://api.wotblitz.${REALM_TLD[realm]}/wotb`,
  wotx: realm => `https://api-${REALM_TLD[realm]}-console.worldoftanks.com/wotx`,
  wows: realm => `https://api.worldofwarships.${REALM_TLD[realm]}/wows`,
  wowp: realm => `https://api.worldofwarplanes.${REALM_TLD[realm]}/wowp`,
  wgn: realm => `https://api.worldoftanks.${REALM_TLD[realm]}/wgn`,
};

/**
 * Returns the base URI for a given realm and API type.
 * @param {string} realm - The realm/region of the server.
 * @param {string} type - The desired API.
 * @returns {string} The base URI for the API that was specified.
 * @throws {Error} Thrown if the given `realm` or `type` don't exist.
 * @private
 */
const getBaseUri = (realm, type) => {
  if (!REALM_TLD[realm] || !BASE_URI[type]) {
    throw new Error('Unknown realm or type given.');
  }

  return BASE_URI[type](realm);
};

/**
 * @classdesc The base API client.
 */
class BaseClient {
  /**
   * Constructor.
   * @param {Object} options - The client options.
   * @param {string} options.type - The type of API this client is for.
   * @param {string} options.realm - The realm/region this client is for.
   * @param {string} options.applicationId - The application ID of this client.
   * @param {string} [options.accessToken=null] - The access token for this
   *   client, if it will be using one.
   * @param {string} [options.language=null] - The default localization language
   *   to use for API responses.
   * @param {?number} [options.cacheTimeToLive=600000] - The time to live in
   *   milliseconds for the client's data cache entries. `null` if no there is
   *   no TTL.
   * @throws {TypeError} Thrown if options are not well-formed.
   */
  constructor(options) {
    const {
      type,
      realm,
      applicationId,
      accessToken = null,
      language = null,
      cacheTimeToLive = 600 * 1000,
    } = options;

    if (typeof realm !== 'string' || !REALM_TLD[realm.toLowerCase()]) {
      throw new TypeError('Must specify a valid realm for the client.');
    } else if (typeof applicationId !== 'string') {
      throw new TypeError('Must specify an application ID for the client.');
    }

    const normalizedRealm = realm.toLowerCase();

    /**
     * The type of API this client is for.
     * @type {string}
     */
    this.type = type;

    /**
     * The realm, i.e. region of this client.
     * @type {string}
     */
    this.realm = normalizedRealm;

    /**
     * The application ID for this client.
     * @type {string}
     */
    this.applicationId = applicationId;

    /**
     * The access token for this client.
     * @type {?string}
     */
    this.accessToken = accessToken;

    /**
     * The default localization language for this client.
     * @type {?string}
     */
    this.language = language;

    /**
     * The client's Authentication module.
     * @type {Authentication}
     */
    this.authentication = new Authentication(this);

    /**
     * The base API URI for this client.
     * @type {string}
     * @private
     */
    this.baseUri = getBaseUri(normalizedRealm, type);

    /**
     * The API response cache.
     * @type {Cache}
     * @private
     */
    this.cache = new Cache({ timeToLive: cacheTimeToLive });
  }

  /**
   * Normalizes a given parameter type so the API can consume it.
   * @param {*} parameter - The parameter to normalize.
   * @returns {*} The normalized parameter.
   * @static
   * @private
   */
  static normalizeParameterValue(parameter) {
    if (Array.isArray(parameter)) {
      return parameter.join(',');
    } else if (parameter instanceof Date) {
      return parameter.toISOString();
    }

    return parameter;
  }

  /**
   * Sends a GET request to the API.
   * @param {string} method - The method to request.
   * @param {Object} [params={}] - The parameters to include in the request.
   * @param {RequestOptions} [options={}] - Options used to override client defaults.
   * @returns {Promise.<APIResponse, Error>} Returns a promise resolving to the
   *   returned API data, or rejecting with an error.
   */
  get(method, params = {}, options = {}) {
    return this.request(method, params, { ...options, method: 'GET' });
  }

  /**
   * Sends a POST request to the API.
   * @param {string} method - The method to request.
   * @param {Object} [params={}] - The parameters to include in the request.
   * @param {RequestOptions} [options={}] - Options used to override client defaults.
   * @returns {Promise.<APIResponse, Error>} Returns a promise resolving to the
   *   returned API data, or rejecting with an error.
   */
  post(method, params = {}, options = {}) {
    return this.request(method, params, { ...options, method: 'POST' });
  }

  /**
   * Fetches data from an endpoint method.
   * @param {string} apiMethod - The method to request.
   * @param {Object} [params={}] - The parameters to include in the request.
   * @param {RequestOptions} [options={}] - Options used to override client defaults.
   * @returns {Promise.<APIResponse, Error>} Returns a promise resolving to the
   *   returned API data, or rejecting with an error.
   * @private
   */
  request(apiMethod, params = {}, options = {}) {
    return new Promise((resolve) => {
      const { type = this.type, realm = this.realm, method = 'GET' } = options;

      if (typeof apiMethod !== 'string') {
        throw new TypeError('Expected API method to be a string.');
      }

      const normalizedApiMethod = apiMethod.toLowerCase();
      const normalizedRealm = realm.toLowerCase();

      // construct the request URL
      const baseUrl = normalizedRealm === this.realm
        ? this.baseUri
        : getBaseUri(normalizedRealm, type);
      const requestUrl = `${baseUrl}/${normalizedApiMethod.replace(/^\/*(.+?)\/*$/, '$1')}/`;

      // construct the payload
      const payload = {
        application_id: this.applicationId,
        access_token: this.accessToken,
        language: this.language,
        ...params,
      };

      const normalizedPayload = mapValues(payload, this.constructor.normalizeParameterValue);

      // compute information for the cache
      const { application_id, ...rest } = normalizedPayload; // eslint-disable-line no-unused-vars
      const cacheKey = hashCode(`${requestUrl}${JSON.stringify(sortObjectByKey(rest))}`);

      const fulfillResponse = (response) => {
        const { error = null } = response.body;

        if (error) {
          // Wargaming API error
          throw new APIError({
            client: this,
            statusCode: response.status,
            method: normalizedApiMethod,
            error,
          });
        }

        return new APIResponse({
          client: this,
          requestRealm: normalizedRealm,
          method: normalizedApiMethod,
          body: response.body,
        });
      };

      const rejectResponse = (value) => {
        // check if this is a HTTP error or a Wargaming error
        if (value instanceof APIError) {
          throw value;
        }

        const { response: { error, req } } = value;

        throw new RequestError({
          message: value.body.error.message,
          client: this,
          statusCode: error.status,
          url: req.url,
        });
      };

      if (method === 'GET') {
        const cached = this.cache.get(cacheKey);

        if (cached) {
          const response = new APIResponse({
            client: this,
            requestRealm: normalizedRealm,
            method: normalizedApiMethod,
            body: cached,
          });

          resolve(response);
        }

        const promise = request.get(requestUrl)
          .query(normalizedPayload)
          .then(fulfillResponse)
          .then((apiResponse) => {
            this.cache.set(cacheKey, apiResponse.body);

            return apiResponse;
          })
          .catch(rejectResponse);

        resolve(promise);
      } else if (method === 'POST') {
        const promise = request.post(requestUrl)
          .type('form')
          .send(normalizedPayload)
          .then(fulfillResponse)
          .catch(rejectResponse);

        resolve(promise);
      }

      // we should never get here
      throw new Error('Received invalid request method.');
    });
  }
}

export default BaseClient;
<|endoftext|>


<commit_before>(function() {
    "use strict";

    var ipc = require('electron').ipcRenderer,
        shell = require('electron').shell;

    var mentionCount = 0;
    var unreadCount = 0;

    var notifyHost = function () {
        // send back the counts to the webview host
        ipc.sendToHost('mention-count', mentionCount);
        ipc.sendToHost('unread-count', unreadCount);
    };

    // we'll only notify if there's a change in the counts
    var checkActivity = function () {
        var notify = false;
        var localMentionCount = 0;
        // get the actual count of mentions from Mattermost
        $('.unread-title.has-badge .badge').each(function () {
            localMentionCount += parseInt($(this).text(), 10);
        });

        // set flag to notify if the mention count has changed
        if (localMentionCount != mentionCount) {
            mentionCount = localMentionCount;
            notify = true;
        }

        var localUnreadCount = $('.unread-title').length;
        // set flag to notify if the unread count has changed
        if (localUnreadCount != unreadCount) {
            unreadCount = localUnreadCount;
            notify = true;
        }

        if (notify) notifyHost();
    };

    var getCookie = function (cname) {
        var ca = document.cookie.split(';');
        return ca.reduce(function(p, c) { if(c.indexOf(cname) > -1) { return c.split('=')[1].trim() } }, null) || null;
    };

    var teamName = getCookie("MMTEAM");
    var pathname = window.location.pathname;

    switch (pathname) {
        case '/':
            if (teamName) {
                window.location.href = '/' + teamName;
            } else {
                ipc.sendToHost('no-team');
            }
            break;
        case '/find_team':
            ipc.sendToHost('no-team');
            break;
    }

    document.addEventListener("DOMContentLoaded", function () {
        // observe the DOM for mutations, specifically the .ps-container
        // which contains all the sidebar channels
        var MutationObserver = window.MutationObserver;
        var list = document.querySelector('#sidebar-left');

        var observer = new MutationObserver(function (mutations) {
            if (mutations.length) {
                checkActivity();
            }
        });

        if (list) {
            observer.observe(list, {
                subtree: true,
                attributes: true,
                childList: true
            });
        }

        // initial one time notification
        checkActivity();
    });
})();<commit_msg>Fix unread & mention badge<commit_after>(function() {
    "use strict";

    var ipc = require('electron').ipcRenderer;

    var mentionCount = 0;
    var unreadCount = 0;

    var notifyHost = function () {
        // send back the counts to the webview host
        ipc.sendToHost('mention-count', mentionCount);
        ipc.sendToHost('unread-count', unreadCount);
    };

    // we'll only notify if there's a change in the counts
    var checkActivity = function () {
        var notify = false;
        var localMentionCount = 0;
        // get the actual count of mentions from Mattermost
        $('.unread-title.has-badge .badge').each(function () {
            localMentionCount += parseInt($(this).text(), 10);
        });

        // set flag to notify if the mention count has changed
        if (localMentionCount != mentionCount) {
            mentionCount = localMentionCount;
            notify = true;
        }

        var localUnreadCount = $('.unread-title').length;
        // set flag to notify if the unread count has changed
        if (localUnreadCount != unreadCount) {
            unreadCount = localUnreadCount;
            notify = true;
        }

        if (notify) notifyHost();
    };

    var getCookie = function (cname) {
        var ca = document.cookie.split(';');
        return ca.reduce(function(p, c) { if(c.indexOf(cname) > -1) { return c.split('=')[1].trim() } }, null) || null;
    };

    var teamName = getCookie("MMTEAM");
    var pathname = window.location.pathname;

    switch (pathname) {
        case '/':
            if (teamName) {
                window.location.href = '/' + teamName;
            } else {
                ipc.sendToHost('no-team');
            }
            break;
        case '/find_team':
            ipc.sendToHost('no-team');
            break;
    }

    document.addEventListener("DOMContentLoaded", function () {
        // observe the DOM for mutations, specifically the .ps-container
        // which contains all the sidebar channels

        setTimeout(function() {
            var MutationObserver = window.MutationObserver;
            var list = document.querySelector('#sidebar-left');

            var observer = new MutationObserver(function (mutations) {
                if (mutations.length) {
                    checkActivity();
                }
            });

            if (list) {
                observer.observe(list, {
                    subtree: true,
                    attributes: true,
                    childList: true
                });
            }
            // initial one time notification
            checkActivity();
        }, 0);

    });
})();<|endoftext|>


<commit_before>/**
 * Marionette Routing
 *
 * Copyright © 2015-2016 Luiz Américo Pereira Câmara. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import _ from 'underscore'
import Radio from 'backbone.radio'
import cherrytree from 'cherrytree'
import Route from './route'

let mnRouteMap = Object.create(null)
export const routerChannel = Radio.channel('router')
let router

export function createRouter(options) {
  if (router) {
    throw new Error('Instance of router already created')
  }
  return router = Route.prototype.$router = cherrytree(options)
}

export function destroyRouter(instance) {
  router = null
  Route.prototype.$router = null
  mnRouteMap = Object.create(null)
  instance.destroy()
}

export function getMnRoutes(routes) {
  return routes.map(function (route) {
    return mnRouteMap[route.name]
  })
}

routerChannel.reply('transitionTo', function () {
  return router.transitionTo.apply(router, arguments)
})

routerChannel.reply('isActive', function () {
  return router.isActive.apply(router, arguments)
})

routerChannel.reply('generate', function () {
  return router.generate.apply(router, arguments)
})

routerChannel.reply('goBack', function () {
  //in wait of a better implementation
  history.back();
})

function getChangingIndex(prevRoutes, currentRoutes){
  var index, prev, current;
  const count = Math.max(prevRoutes.length, currentRoutes.length)
  for (index = 0; index < count; index++) {
    prev = prevRoutes[index]
    current = currentRoutes[index]
    if (!(prev && current) || (prev.name !== current.name) || !_.isEqual(prev.params, current.params)) {
      break
    }
  }
  return index
}

function findRouteConfig(routeName, index, routes) {
  let parentRoutes = routes.slice(0, index).reverse().map(function (route) {
    return mnRouteMap[route.name]
  })
  let config
  parentRoutes.some(function (route) {
    let childRoutes = _.result(route, 'childRoutes')
    config = childRoutes && childRoutes[routeName]
    if (!config) return;
    if (config.prototype instanceof Route) {
      config = {routeClass: config}
    } else if (_.isFunction(config)) {
      config = config.call(route)
      if (config.prototype instanceof Route) {
        config = {routeClass: config}
      }
    }
    return config
  })
  return config
}

function createRouteInstance(options) {
  let routeOptions = Object.assign({}, options.routeOptions, _.pick(options, ['viewClass', 'viewOptions']))
  if (options.routeClass) {
    return new options.routeClass(routeOptions)
  } else if (options.viewClass || options.abstract) {
    return new Route(routeOptions)
  }
}

function createMnRoute(route, index, routes) {
  let instance = createRouteInstance(route.options)
  if (!instance) {
    let config = findRouteConfig(route.name, index, routes)
    return Promise.resolve(config).then(function (options) {
      if (options.prototype instanceof Route) {
        options = {routeClass: options}
      }
      return options && createRouteInstance(options)
    })
  }
  if (!instance) {
    throw new Error(`Unable to create route ${route.name}: routeClass or viewClass must be defined`)
  }
  instance.$name = route.name
  return instance
}

function getParentRegion(routes, route) {
  var region, parent
  let routeIndex = routes.indexOf(route) - 1
  while (routeIndex >= 0) {
    parent = routes[routeIndex]
    if (parent.view) {
      region = parent.view.getRegion('outlet')
      if (region) {
        return region
      } else {
        throw new Error('No outlet region in view')
      }
    }
    routeIndex--
  }
  if (!router.rootRegion) {
    throw new Error('No outlet region')
  }
  return router.rootRegion
}

export function middleware(transition) {

  routerChannel.trigger('before:transition', transition)

  if (transition.isCancelled) {
    return
  }

  let prevRoutes = transition.prev.routes
  let changingIndex = getChangingIndex(prevRoutes, transition.routes)
  var routeIndex, routeInstance

  //deactivate previous routes
  for (routeIndex = prevRoutes.length - 1; routeIndex >= changingIndex; routeIndex--) {
    routeInstance = mnRouteMap[prevRoutes[routeIndex].name]
    if (routeInstance) {
      routerChannel.trigger('before:deactivate', transition, routeInstance)
      if (!transition.isCancelled) {
        routeInstance.deactivate(transition)
        routerChannel.trigger('deactivate', transition, routeInstance)
      }
      if (transition.isCancelled) {
        return
      }
    }
  }

  //build route tree and creating instances if necessary
  let mnRoutes = transition.mnRoutes = []

  let promise = transition.routes.reduce(function (acc, route, i, routes) {
    return acc.then(function (res) {
      let instance = mnRouteMap[route.name]
      if (instance) {
        res.push(instance)
        return res
      } else {
        instance = createMnRoute(route, i, routes)
        return Promise.resolve(instance).then(function (mnRoute) {
          mnRouteMap[route.name] = mnRoute
          res.push(mnRoute)
          return res
        })
      }
    });
  }, Promise.resolve(mnRoutes));

  //activate routes in order
  let activated

  promise = promise.then(function () {
    activated = mnRoutes.slice(changingIndex)
    return activated.reduce(function (prevPromise, mnRoute) {
      return prevPromise.then(function () {
        if (!transition.isCancelled) {
          routerChannel.trigger('before:activate', transition, mnRoute)
          if (!transition.isCancelled) {
            return Promise.resolve(mnRoute.activate(transition)).then(function () {
              routerChannel.trigger('activate', transition, mnRoute)
            })
          }
        }
        return Promise.resolve()
      })
    }, Promise.resolve())
  })

  transition.then(function () {
    router.state.mnRoutes = mnRoutes
    routerChannel.trigger('transition', transition)
  })

  //render views
  return promise.then(function () {
    //ensure at least the target (last) route is rendered
    if (!activated.length && mnRoutes.length) {
      activated.push(mnRoutes[mnRoutes.length - 1])
    }
    activated.forEach(function (mnRoute) {
      if (mnRoute.viewClass) {
        let parentRegion = getParentRegion(mnRoutes, mnRoute)
        mnRoute.renderView(parentRegion)
      }
    })
  })
}<commit_msg>Move Route class check to createRouteInstance function<commit_after>/**
 * Marionette Routing
 *
 * Copyright © 2015-2016 Luiz Américo Pereira Câmara. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import _ from 'underscore'
import Radio from 'backbone.radio'
import cherrytree from 'cherrytree'
import Route from './route'

let mnRouteMap = Object.create(null)
export const routerChannel = Radio.channel('router')
let router

export function createRouter(options) {
  if (router) {
    throw new Error('Instance of router already created')
  }
  return router = Route.prototype.$router = cherrytree(options)
}

export function destroyRouter(instance) {
  router = null
  Route.prototype.$router = null
  mnRouteMap = Object.create(null)
  instance.destroy()
}

export function getMnRoutes(routes) {
  return routes.map(function (route) {
    return mnRouteMap[route.name]
  })
}

routerChannel.reply('transitionTo', function () {
  return router.transitionTo.apply(router, arguments)
})

routerChannel.reply('isActive', function () {
  return router.isActive.apply(router, arguments)
})

routerChannel.reply('generate', function () {
  return router.generate.apply(router, arguments)
})

routerChannel.reply('goBack', function () {
  //in wait of a better implementation
  history.back();
})

function getChangingIndex(prevRoutes, currentRoutes){
  var index, prev, current;
  const count = Math.max(prevRoutes.length, currentRoutes.length)
  for (index = 0; index < count; index++) {
    prev = prevRoutes[index]
    current = currentRoutes[index]
    if (!(prev && current) || (prev.name !== current.name) || !_.isEqual(prev.params, current.params)) {
      break
    }
  }
  return index
}

function findRouteConfig(routeName, index, routes) {
  let parentRoutes = routes.slice(0, index).reverse().map(function (route) {
    return mnRouteMap[route.name]
  })
  let config
  parentRoutes.some(function (route) {
    let childRoutes = _.result(route, 'childRoutes')
    config = childRoutes && childRoutes[routeName]
    if (_.isFunction(config) && !(config.prototype instanceof Route)) {
      config = config.call(route)
    }
    return config
  })
  return config
}

function createRouteInstance(options) {
  if (options.prototype instanceof Route) {
    return new options()
  }
  let routeOptions = Object.assign({}, options.routeOptions, _.pick(options, ['viewClass', 'viewOptions']))
  if (options.routeClass) {
    return new options.routeClass(routeOptions)
  } else if (options.viewClass || options.abstract) {
    return new Route(routeOptions)
  }
}

function createMnRoute(route, index, routes) {
  let instance = createRouteInstance(route.options)
  if (!instance) {
    let config = findRouteConfig(route.name, index, routes)
    return Promise.resolve(config).then(function (options) {
      return options && createRouteInstance(options)
    })
  }
  if (!instance) {
    throw new Error(`Unable to create route ${route.name}: routeClass or viewClass must be defined`)
  }
  instance.$name = route.name
  return instance
}

function getParentRegion(routes, route) {
  var region, parent
  let routeIndex = routes.indexOf(route) - 1
  while (routeIndex >= 0) {
    parent = routes[routeIndex]
    if (parent.view) {
      region = parent.view.getRegion('outlet')
      if (region) {
        return region
      } else {
        throw new Error('No outlet region in view')
      }
    }
    routeIndex--
  }
  if (!router.rootRegion) {
    throw new Error('No outlet region')
  }
  return router.rootRegion
}

export function middleware(transition) {

  routerChannel.trigger('before:transition', transition)

  if (transition.isCancelled) {
    return
  }

  let prevRoutes = transition.prev.routes
  let changingIndex = getChangingIndex(prevRoutes, transition.routes)
  var routeIndex, routeInstance

  //deactivate previous routes
  for (routeIndex = prevRoutes.length - 1; routeIndex >= changingIndex; routeIndex--) {
    routeInstance = mnRouteMap[prevRoutes[routeIndex].name]
    if (routeInstance) {
      routerChannel.trigger('before:deactivate', transition, routeInstance)
      if (!transition.isCancelled) {
        routeInstance.deactivate(transition)
        routerChannel.trigger('deactivate', transition, routeInstance)
      }
      if (transition.isCancelled) {
        return
      }
    }
  }

  //build route tree and creating instances if necessary
  let mnRoutes = transition.mnRoutes = []

  let promise = transition.routes.reduce(function (acc, route, i, routes) {
    return acc.then(function (res) {
      let instance = mnRouteMap[route.name]
      if (instance) {
        res.push(instance)
        return res
      } else {
        instance = createMnRoute(route, i, routes)
        return Promise.resolve(instance).then(function (mnRoute) {
          mnRouteMap[route.name] = mnRoute
          res.push(mnRoute)
          return res
        })
      }
    });
  }, Promise.resolve(mnRoutes));

  //activate routes in order
  let activated

  promise = promise.then(function () {
    activated = mnRoutes.slice(changingIndex)
    return activated.reduce(function (prevPromise, mnRoute) {
      return prevPromise.then(function () {
        if (!transition.isCancelled) {
          routerChannel.trigger('before:activate', transition, mnRoute)
          if (!transition.isCancelled) {
            return Promise.resolve(mnRoute.activate(transition)).then(function () {
              routerChannel.trigger('activate', transition, mnRoute)
            })
          }
        }
        return Promise.resolve()
      })
    }, Promise.resolve())
  })

  transition.then(function () {
    router.state.mnRoutes = mnRoutes
    routerChannel.trigger('transition', transition)
  })

  //render views
  return promise.then(function () {
    //ensure at least the target (last) route is rendered
    if (!activated.length && mnRoutes.length) {
      activated.push(mnRoutes[mnRoutes.length - 1])
    }
    activated.forEach(function (mnRoute) {
      if (mnRoute.viewClass) {
        let parentRegion = getParentRegion(mnRoutes, mnRoute)
        mnRoute.renderView(parentRegion)
      }
    })
  })
}<|endoftext|>


<commit_before>/*
 * Lively4 ContextMenu
 * - creates the "world menu" for Lively4
 */

import html from 'src/client/html.js';
import {pt} from 'lively.graphics'

export default class ContextMenu {

  static hide() {
    if (this.menu) $(this.menu).remove();
    lively.removeEventListener("contextMenu",  document.documentElement);
  }

  static openComponentInWindow (name, evt) {
    this.hide();
    return lively.openComponentInWindow(name, pt(evt.pageX, evt.pageY));
  }

  static openInWindow(comp, evt) {
    var pos = lively.getPosition(comp);
	  lively.components.openInWindow(comp).then(function (w) {
        lively.setPosition(w, pos);
        lively.setPosition(comp, {x:0, y:0});
        if (comp.windowTitle) w.setAttribute('title', '' + comp.windowTitle);
        return comp;
    });
  }
  static items (target, worldContext) {
    if (!worldContext) worldContext = document.body;
    if (target) {
      var wasEditable = (target.contentEditable == "true");
      var wasDisabled = (target.disabled == "true");
      var targetInWindow = target.parentElement.tagName == 'LIVELY-WINDOW';
      var menu = [
        ["show", (evt) => {
           this.hide();
           lively.showElement(target);
        }],
        ["browse template source", (evt) => {
           this.hide();
           lively.showSource(target, evt);
        }],
        ["browse class source", (evt) => {
           this.hide();
           lively.showClassSource(target, evt);
        }],
        ["trace", (evt) => {
           System.import("src/client/tracer.js").then(tracer => {
             tracer.default.traceObject(target);
           });
           this.hide();
        }],
        [wasEditable ? "make uneditable" : "make editable", (evt) => {
           this.hide();
           target.contentEditable = !wasEditable;
        }],
        [wasDisabled ? "enable" : "disable", (evt) => {
           this.hide();
           target.disabled = !wasDisabled;
        }],
        [targetInWindow ? "strip window" : "open in window", (evt) => {
            this.hide();
            targetInWindow ?
              target.parentElement.embedContentInParent() :
              ContextMenu.openInWindow(target, evt)
          }]
      ];
      return menu;
    } else {
      return [
      ["Workspace", (evt) => {
        this.hide();
        lively.openWorkspace("", pt(evt.pageX, evt.pageY));
      }, "CMD+K"],
      ["Browser",     (evt) => {
        this.openComponentInWindow("lively-container", evt).then(comp => {
          var container = _.last(document.querySelectorAll("lively-container"));
          if (container)
            comp.followPath("" +container.getURL());
          else
            comp.followPath(lively4url +"/");
          comp.parentElement.style.width = "850px";
          comp.parentElement.style.height = "600px";
        });
      }, "CMD+B"],
      // ["File Editor",     (evt) => this.openComponentInWindow("lively-editor", evt)],
      // ["File Browser",    (evt) => this.openComponentInWindow("lively-file-browser", evt)],
      ["Mount",     (evt) => this.openComponentInWindow("lively-filesystems", evt)],
      ["Sync",     (evt) => this.openComponentInWindow("lively-sync", evt)],
      ["Services",     (evt) => this.openComponentInWindow("lively-services", evt)],
      // ["Terminal",        (evt) => this.openComponentInWindow("lively-terminal", evt)],
      // ["Console",         (evt) => this.openComponentInWindow("lively-console", evt)],
      ["File Search",         (evt) => this.openComponentInWindow("lively-search", evt)],
      ["TestRunner",         (evt) => this.openComponentInWindow("lively-testrunner", evt)],
      ["Component Bin",   (evt) => this.openComponentInWindow("lively-component-bin", evt),
       "CMD+O"],
      ["Customize Page",   (evt) => {
        this.hide();
        lively.import("customize").then(c => c.openCustomizeWorkspace(evt));
      }],
      // ["Persistens Settings", (evt) => {
      //    this.openComponentInWindow("lively-persistence-settings", evt).then((comp) => {
      //        comp.parentElement.style.height = "150px"
      //        comp.parentElement.style.width = "400px"
      //    })
      // }],
      // #TODO use sub menues here
      ["Devdocs.io",     (evt) => {
        this.openComponentInWindow("lively-help",  pt(evt.pageX, evt.pageY));
      }, "CMD+H"],
      ["Wiki (Docs)",     (evt) => {
        this.openComponentInWindow("lively-container", evt).then(comp => {
          comp.followPath("https://lively-kernel.org/lively4/Lively4.wiki/Home.md");
      });
      }],
      ["Journal",     (evt) => {
        this.openComponentInWindow("lively-container", evt).then(comp => {
          comp.followPath("https://lively-kernel.org/lively4/Lively4.wiki/Journal.md");
      });
      }],
      ["Issues", (evt) => {
        window.open("https://github.com/LivelyKernel/lively4-core/issues") ;
      }],
      ["Text", (evt) => {
        var text  = document.createElement("p");
        text.innerHTML = "Hello";
        text.contentEditable = true;
        worldContext.appendChild(text);
        var pos = pt(evt.pageX, evt.pageY)
        if (worldContext.localizePosition) pos = worldContext.localizePosition(pos)

        lively.setPosition(text, pos);
        this.hide();
      }],
      ["Rectangle", (evt) => {
        var morph  = document.createElement("div");
        morph.style.width = "200px";
        morph.style.height = "100px";
        var pos = pt(evt.pageX, evt.pageY)
        if (worldContext.localizePosition) pos = worldContext.localizePosition(pos)
        lively.setPosition(morph, pos);
        // morph.style.backgroundColor = "blue";
        morph.style.backgroundColor = 'rgba(40,40,40,0.5)';
        worldContext.appendChild(morph);
        this.hide();
      }],
      ["save as ..", (evt) => {
        html.saveCurrentPageAs();
      }],
      ["save", (evt) => {
        html.saveCurrentPage();
      }]
    ]}
  }

  static openIn(container, evt, target, worldContext) {
    this.hide();

    lively.addEventListener("contextMenu", document.documentElement, "click", () => {
      this.hide();
    }, true);

    var menu = lively.components.createComponent("lively-menu");
    return lively.components.openIn(container, menu).then(() => {
      this.menu = menu;
      if (evt) {
        var xOffset = 0;
        var menuWidth = menu.clientWidth;
        var bodyWidth = $('body')[0].clientWidth;
        if (evt.pageX + menuWidth > bodyWidth) {
          xOffset = menuWidth;
        }
        lively.setPosition(menu, pt(evt.pageX - xOffset, evt.pageY));
      }
      menu.openOn(this.items(target, worldContext), evt).then(() => {
      });
      return menu;
    });
  }
}

console.log("loaded contextmenu");<commit_msg>AUTO-COMMIT src/client/contextmenu.js<commit_after>/*
 * Lively4 ContextMenu
 * - creates the "world menu" for Lively4
 */

import html from 'src/client/html.js';
import {pt} from 'lively.graphics';

export default class ContextMenu {

  static hide() {
    if (this.menu) $(this.menu).remove();
    lively.removeEventListener("contextMenu",  document.documentElement);
  }

  static openComponentInWindow (name, evt) {
    this.hide();
    return lively.openComponentInWindow(name, pt(evt.pageX, evt.pageY));
  }

  static openInWindow(comp, evt) {
    var pos = lively.getPosition(comp);
	  lively.components.openInWindow(comp).then(function (w) {
        lively.setPosition(w, pos);
        lively.setPosition(comp, {x:0, y:0});
        if (comp.windowTitle) w.setAttribute('title', '' + comp.windowTitle);
        return comp;
    });
  }

  static targetMenuItems(target) {
    var wasEditable = (target.contentEditable == "true");
    var wasDisabled = (target.disabled == "true");
    var targetInWindow = target.parentElement.tagName == 'LIVELY-WINDOW';
    return [
      ["show", (evt) => {
         this.hide();
         lively.showElement(target);
      }],
      ["browse template source", (evt) => {
         this.hide();
         lively.showSource(target, evt);
      }],
      ["browse class source", (evt) => {
         this.hide();
         lively.showClassSource(target, evt);
      }],
      ["trace", (evt) => {
         System.import("src/client/tracer.js").then(tracer => {
           tracer.default.traceObject(target);
         });
         this.hide();
      }],
      [wasEditable ? "make uneditable" : "make editable", (evt) => {
         this.hide();
         target.contentEditable = !wasEditable;
      }],
      [wasDisabled ? "enable" : "disable", (evt) => {
         this.hide();
         target.disabled = !wasDisabled;
      }],
      [targetInWindow ? "strip window" : "open in window", (evt) => {
          this.hide();
          targetInWindow ?
            target.parentElement.embedContentInParent() :
            ContextMenu.openInWindow(target, evt);
        }]
    ];
  }

  static worldMenuItems(worldContext) {
    return  [
      ["Workspace", (evt) => {
        this.hide();
        lively.openWorkspace("", pt(evt.pageX, evt.pageY));
      }, "CMD+K"],
      ["Browser",     (evt) => {
        this.openComponentInWindow("lively-container", evt).then(comp => {
          var container = _.last(document.querySelectorAll("lively-container"));
          if (container)
            comp.followPath("" +container.getURL());
          else
            comp.followPath(lively4url +"/");
          comp.parentElement.style.width = "850px";
          comp.parentElement.style.height = "600px";
        });
      }, "CMD+B"],
      // ["File Editor",     (evt) => this.openComponentInWindow("lively-editor", evt)],
      // ["File Browser",    (evt) => this.openComponentInWindow("lively-file-browser", evt)],
      ["Mount",     (evt) => this.openComponentInWindow("lively-filesystems", evt)],
      ["Sync",     (evt) => this.openComponentInWindow("lively-sync", evt)],
      ["Services",     (evt) => this.openComponentInWindow("lively-services", evt)],
      // ["Terminal",        (evt) => this.openComponentInWindow("lively-terminal", evt)],
      // ["Console",         (evt) => this.openComponentInWindow("lively-console", evt)],
      ["File Search",         (evt) => this.openComponentInWindow("lively-search", evt)],
      ["TestRunner",         (evt) => this.openComponentInWindow("lively-testrunner", evt)],
      ["Component Bin",   (evt) => this.openComponentInWindow("lively-component-bin", evt),
       "CMD+O"],
      ["Customize Page",   (evt) => {
        this.hide();
        lively.import("customize").then(c => c.openCustomizeWorkspace(evt));
      }],
      // #TODO use sub menues here
      ["Devdocs.io",     (evt) => {
        this.openComponentInWindow("lively-help",  pt(evt.pageX, evt.pageY));
      }, "CMD+H"],
      ["Wiki (Docs)",     (evt) => {
        this.openComponentInWindow("lively-container", evt).then(comp => {
          comp.followPath("https://lively-kernel.org/lively4/Lively4.wiki/Home.md");
      });
      }],
      ["Journal",     (evt) => {
        this.openComponentInWindow("lively-container", evt).then(comp => {
          comp.followPath("https://lively-kernel.org/lively4/Lively4.wiki/Journal.md");
      });
      }],
      ["Issues", (evt) => {
        window.open("https://github.com/LivelyKernel/lively4-core/issues") ;
      }],
      ["Text", (evt) => {
        var text  = document.createElement("p");
        text.innerHTML = "Hello";
        text.contentEditable = true;
        worldContext.appendChild(text);
        var pos = pt(evt.pageX, evt.pageY);
        if (worldContext.localizePosition) pos = worldContext.localizePosition(pos);

        lively.setPosition(text, pos);
        this.hide();
      }],
      ["Rectangle", (evt) => {
        var morph  = document.createElement("div");
        morph.style.width = "200px";
        morph.style.height = "100px";
        var pos = pt(evt.pageX, evt.pageY);
        if (worldContext.localizePosition) pos = worldContext.localizePosition(pos);
        lively.setPosition(morph, pos);
        // morph.style.backgroundColor = "blue";
        morph.style.backgroundColor = 'rgba(40,40,40,0.5)';
        worldContext.appendChild(morph);
        this.hide();
      }],
      ["save as ..", (evt) => {
        html.saveCurrentPageAs();
      }],
      ["save", (evt) => {
        html.saveCurrentPage();
      }]
    ];
  }

  static items (target, worldContext) {
    if (!worldContext) worldContext = document.body;
    if (target) {
      return this.targetMenuItems(target);
    } else {
      return this.worldMenuItems(worldContext);
    }
  }

  static openIn(container, evt, target, worldContext) {
    this.hide();

    lively.addEventListener("contextMenu", document.documentElement, "click", () => {
      this.hide();
    }, true);

    var menu = lively.components.createComponent("lively-menu");
    return lively.components.openIn(container, menu).then(() => {
      this.menu = menu;
      if (evt) {
        var xOffset = 0;
        var menuWidth = menu.clientWidth;
        var bodyWidth = $('body')[0].clientWidth;
        if (evt.pageX + menuWidth > bodyWidth) {
          xOffset = menuWidth;
        }
        lively.setPosition(menu, pt(evt.pageX - xOffset, evt.pageY));
      }
      menu.openOn(this.items(target, worldContext), evt).then(() => {
      });
      return menu;
    });
  }
}

console.log("loaded contextmenu");<|endoftext|>


<commit_before>import {
  action,
  observable,
  runInAction,
} from 'mobx'
import { app } from '@mindhive/di'
import { meteorTracker } from './tracker'

// Originally based on https://github.com/meteor-space/tracker-mobx-autorun 0.2.0

const observableName = ({ observableArray, observableMap }) => {
  const names = []
  if (observableArray) {
    names.push(observableArray.$mobx.atom.name)
  }
  if (observableMap) {
    names.push(observableMap.name)
  }
  return names.join('+')
}

const groundCollectionName = groundCollection =>
  groundCollection.storage._config.name

export const checkFindOptions = ({
  findOptions,
  observableArray,
}) => {
  const { Meteor } = app()
  if (Meteor.isDevelopment) {
    if (findOptions && findOptions.sort && ! observableArray) {
      console.warn('sort findOptions does not make sense when not observing an array')  // eslint-disable-line no-console
    }
  }
}

export const checkObservableModes = ({
  observableArray,
  observableMap,
}) => {
  const { Meteor } = app()
  if (Meteor.isDevelopment) {
    if (observableArray && observableArray.enhancer.name !== 'referenceEnhancer') {
      console.warn('observableArray does not appear to be shallow, ' +  // eslint-disable-line no-console
        'declare as: @observable.shallow array = []')
    }
    if (observableMap && observableMap.enhancer.name !== 'referenceEnhancer') {
      console.warn('observableMap does not appear to be shallow, ' +  // eslint-disable-line no-console
        'declare as: @observable.shallow map = new Map()')
    }
  }
}

let uniqueNameCounter = 0

const generateUniqueId = () => {
  uniqueNameCounter += 1
  return uniqueNameCounter
}

class SubscriptionHandle {

  stopped = false

  @observable loading = true

  _ready(disposer) {
    this.disposer = disposer
    this.loading = false
  }

  stop() {
    this.stopped = true
    if (this.disposer) {
      this.disposer.stop()
      this.disposer = null
    }
  }
}

export class MongoMirror {

  // Automatically pump data from a Mongo cursor to a Mobx array or map
  // See subscriptionToObservable for something more high level
  cursorToObservable({
    context = `cursorToObservable@${generateUniqueId()}`,
    mongoCursor,
    observableArray,  // Should be declared as observable.shallow
    observableMap,  // Should be declared as observable.shallow
  }) {
    checkObservableModes({ observableArray, observableMap })
    meteorTracker.nonreactive(() => {
      runInAction(`${context}: initial fetch`, () => {
        const initialDocs = mongoCursor.fetch()
        if (observableArray) {
          observableArray.replace(initialDocs)
        }
        if (observableMap) {
          observableMap.clear()
          initialDocs.forEach((doc) => {
            observableMap.set(doc._id, doc)
          })
        }
      })
    })
    if (observableArray) {
      return mongoCursor.observe({
        _suppress_initial: true,  // suppresses addedAt for documents initially fetched above
        addedAt: action(`${context}: document added`, (doc, index) => {
          observableArray.splice(index, 0, doc)
          if (observableMap) {
            observableMap.set(doc._id, doc)
          }
        }),
        changedAt: action(`${context}: document changed`, (doc, oldDoc, index) => {
          // REVISIT: if existing value isObservable we could be more efficient here and just assign top level fields
          // Probably using extendObservable but also need to handle fields disappearing
          // Or use observeChanges instead so we just get fields
          observableArray[index] = doc
          if (observableMap) {
            observableMap.set(doc._id, doc)
          }
        }),
        removedAt: action(`${context}: document removed`, (doc, index) => {
          observableArray.splice(index, 1)
          if (observableMap) {
            observableMap.delete(doc._id)
          }
        }),
        movedTo: action(`${context}: document moved`, (doc, fromIndex, toIndex) => {
          observableArray.splice(fromIndex, 1)
          observableArray.splice(toIndex, 0, doc)
        }),
      })
    } else if (observableMap) {
      return mongoCursor.observe({
        _suppress_initial: true,  // suppresses added for documents initially fetched above
        added: action(`${context}: document added`, (doc) => {
          observableMap.set(doc._id, doc)
        }),
        removed: action(`${context}: document removed`, (oldDoc) => {
          observableMap.delete(oldDoc._id)
        }),
        changed: action(`${context}: document changed`, (doc) => {
          observableMap.set(doc._id, doc)
        }),
      })
    }
    throw new Error('Neither observableArray, not observableMap')
  }

  subscriptionToLocal({
    publicationName,
    subscriptionArgs,
    context = `subscription:${publicationName}`,
    onReady = null,  // Can optionally return a disposer with a stop function
  }) {
    const subscriptionHandle = new SubscriptionHandle(context)
    Meteor.subscribe(publicationName, subscriptionArgs, () => {
      if (subscriptionHandle.stopped) {
        return
      }
      let disposer = null
      if (onReady) {
        disposer = onReady()
      }
      runInAction(`${context}: ready`, () => {
        subscriptionHandle._ready(disposer)
      })
    })
    return subscriptionHandle
  }

  // What it says on the can, returns handle with stop() (from autorun) and observable property loading
  subscriptionToObservable({
    publicationName,
    subscriptionArgs,
    collection,
    findSelector = {},
    findOptions = {},
    observableArray,
    observableMap,
    context = `subscription:${publicationName}->${observableName({ observableArray, observableMap })}`,
  }) {
    checkFindOptions({ findOptions, observableArray })
    return this.subscriptionToLocal({
      publicationName,
      subscriptionArgs,
      context,
      onReady: () => this.cursorToObservable({
        context,
        observableArray,
        observableMap,
        mongoCursor: collection.find(findSelector, findOptions),
      }),
    })
  }

  subscriptionToOffline({
    publicationName,
    subscriptionArgs,
    collection,
    findSelector = {},
    findOptions = {},
    groundCollection,
    context = `subscription:${publicationName}->${groundCollectionName(groundCollection)}(offline)`,
  }) {
    // REVISIT: this could be more efficient and not go through minimongo
    return this.subscriptionToLocal({
      publicationName,
      subscriptionArgs,
      context,
      onReady: () => {
        const cursor = collection.find(findSelector, findOptions)
        groundCollection.keep(cursor)
        return groundCollection.observeSource(cursor)
      },
    })
  }

  offlineToObservable({
    groundCollection,
    observableArray,
    observableMap,
    context = `offline:${groundCollectionName(groundCollection)}->${
      observableName({ observableArray, observableMap })}`,
  }) {
    return this.cursorToObservable({
      context,
      observableArray,
      observableMap,
      mongoCursor: groundCollection.find(),
    })
  }

  subscriptionToObservableCachedOffline({
    publicationName,
    subscriptionArgs,
    collection,
    findSelector = {},
    findOptions = {},
    groundCollection,
    observableArray,
    observableMap,
  }) {
    // REVISIT: this will have 3 copies in memory (groundCollection._collection, subscription collection, observable(s))
    checkFindOptions({ findOptions, observableArray })
    this.offlineToObservable({
      groundCollection,
      observableArray,
      observableMap,
    })
    return this.subscriptionToOffline({
      publicationName,
      subscriptionArgs,
      collection,
      findSelector,
      findOptions,
      groundCollection,
    })
  }
}
<commit_msg>Better detection of shallow arrays and maps<commit_after>import {
  action,
  observable,
  runInAction,
  isObservable,
} from 'mobx'
import { app } from '@mindhive/di'
import { meteorTracker } from './tracker'

// Originally based on https://github.com/meteor-space/tracker-mobx-autorun 0.2.0

const observableName = ({ observableArray, observableMap }) => {
  const names = []
  if (observableArray) {
    names.push(observableArray.$mobx.atom.name)
  }
  if (observableMap) {
    names.push(observableMap.name)
  }
  return names.join('+')
}

const groundCollectionName = groundCollection =>
  groundCollection.storage._config.name

export const checkFindOptions = ({
  findOptions,
  observableArray,
}) => {
  const { Meteor } = app()
  if (Meteor.isDevelopment) {
    if (findOptions && findOptions.sort && ! observableArray) {
      console.warn('sort findOptions does not make sense when not observing an array')  // eslint-disable-line no-console
    }
  }
}

const isShallowReferenceEnhancer = enhancer => ! isObservable(enhancer({}))

export const checkObservableModes = ({
  observableArray,
  observableMap,
}) => {
  const { Meteor } = app()
  if (Meteor.isDevelopment) {
    if (observableArray && ! isShallowReferenceEnhancer(observableArray.$mobx.enhancer)) {
      console.warn('observableArray does not appear to be shallow, ' +  // eslint-disable-line no-console
        'declare as: @observable.shallow array = []')
    }
    if (observableMap && ! isShallowReferenceEnhancer(observableMap.enhancer)) {
      console.warn('observableMap does not appear to be shallow, ' +  // eslint-disable-line no-console
        'declare as: @observable.shallow map = new Map()')
    }
  }
}

let uniqueNameCounter = 0

const generateUniqueId = () => {
  uniqueNameCounter += 1
  return uniqueNameCounter
}

class SubscriptionHandle {

  stopped = false

  @observable loading = true

  _ready(disposer) {
    this.disposer = disposer
    this.loading = false
  }

  stop() {
    this.stopped = true
    if (this.disposer) {
      this.disposer.stop()
      this.disposer = null
    }
  }
}

export class MongoMirror {

  // Automatically pump data from a Mongo cursor to a Mobx array or map
  // See subscriptionToObservable for something more high level
  cursorToObservable({
    context = `cursorToObservable@${generateUniqueId()}`,
    mongoCursor,
    observableArray,  // Should be declared as observable.shallow
    observableMap,  // Should be declared as observable.shallow
  }) {
    checkObservableModes({ observableArray, observableMap })
    meteorTracker.nonreactive(() => {
      runInAction(`${context}: initial fetch`, () => {
        const initialDocs = mongoCursor.fetch()
        if (observableArray) {
          observableArray.replace(initialDocs)
        }
        if (observableMap) {
          observableMap.clear()
          initialDocs.forEach((doc) => {
            observableMap.set(doc._id, doc)
          })
        }
      })
    })
    if (observableArray) {
      return mongoCursor.observe({
        _suppress_initial: true,  // suppresses addedAt for documents initially fetched above
        addedAt: action(`${context}: document added`, (doc, index) => {
          observableArray.splice(index, 0, doc)
          if (observableMap) {
            observableMap.set(doc._id, doc)
          }
        }),
        changedAt: action(`${context}: document changed`, (doc, oldDoc, index) => {
          // REVISIT: if existing value isObservable we could be more efficient here and just assign top level fields
          // Probably using extendObservable but also need to handle fields disappearing
          // Or use observeChanges instead so we just get fields
          observableArray[index] = doc
          if (observableMap) {
            observableMap.set(doc._id, doc)
          }
        }),
        removedAt: action(`${context}: document removed`, (doc, index) => {
          observableArray.splice(index, 1)
          if (observableMap) {
            observableMap.delete(doc._id)
          }
        }),
        movedTo: action(`${context}: document moved`, (doc, fromIndex, toIndex) => {
          observableArray.splice(fromIndex, 1)
          observableArray.splice(toIndex, 0, doc)
        }),
      })
    } else if (observableMap) {
      return mongoCursor.observe({
        _suppress_initial: true,  // suppresses added for documents initially fetched above
        added: action(`${context}: document added`, (doc) => {
          observableMap.set(doc._id, doc)
        }),
        removed: action(`${context}: document removed`, (oldDoc) => {
          observableMap.delete(oldDoc._id)
        }),
        changed: action(`${context}: document changed`, (doc) => {
          observableMap.set(doc._id, doc)
        }),
      })
    }
    throw new Error('Neither observableArray, not observableMap')
  }

  subscriptionToLocal({
    publicationName,
    subscriptionArgs,
    context = `subscription:${publicationName}`,
    onReady = null,  // Can optionally return a disposer with a stop function
  }) {
    const subscriptionHandle = new SubscriptionHandle(context)
    Meteor.subscribe(publicationName, subscriptionArgs, () => {
      if (subscriptionHandle.stopped) {
        return
      }
      let disposer = null
      if (onReady) {
        disposer = onReady()
      }
      runInAction(`${context}: ready`, () => {
        subscriptionHandle._ready(disposer)
      })
    })
    return subscriptionHandle
  }

  // What it says on the can, returns handle with stop() (from autorun) and observable property loading
  subscriptionToObservable({
    publicationName,
    subscriptionArgs,
    collection,
    findSelector = {},
    findOptions = {},
    observableArray,
    observableMap,
    context = `subscription:${publicationName}->${observableName({ observableArray, observableMap })}`,
  }) {
    checkFindOptions({ findOptions, observableArray })
    return this.subscriptionToLocal({
      publicationName,
      subscriptionArgs,
      context,
      onReady: () => this.cursorToObservable({
        context,
        observableArray,
        observableMap,
        mongoCursor: collection.find(findSelector, findOptions),
      }),
    })
  }

  subscriptionToOffline({
    publicationName,
    subscriptionArgs,
    collection,
    findSelector = {},
    findOptions = {},
    groundCollection,
    context = `subscription:${publicationName}->${groundCollectionName(groundCollection)}(offline)`,
  }) {
    // REVISIT: this could be more efficient and not go through minimongo
    return this.subscriptionToLocal({
      publicationName,
      subscriptionArgs,
      context,
      onReady: () => {
        const cursor = collection.find(findSelector, findOptions)
        groundCollection.keep(cursor)
        return groundCollection.observeSource(cursor)
      },
    })
  }

  offlineToObservable({
    groundCollection,
    observableArray,
    observableMap,
    context = `offline:${groundCollectionName(groundCollection)}->${
      observableName({ observableArray, observableMap })}`,
  }) {
    return this.cursorToObservable({
      context,
      observableArray,
      observableMap,
      mongoCursor: groundCollection.find(),
    })
  }

  subscriptionToObservableCachedOffline({
    publicationName,
    subscriptionArgs,
    collection,
    findSelector = {},
    findOptions = {},
    groundCollection,
    observableArray,
    observableMap,
  }) {
    // REVISIT: this will have 3 copies in memory (groundCollection._collection, subscription collection, observable(s))
    checkFindOptions({ findOptions, observableArray })
    this.offlineToObservable({
      groundCollection,
      observableArray,
      observableMap,
    })
    return this.subscriptionToOffline({
      publicationName,
      subscriptionArgs,
      collection,
      findSelector,
      findOptions,
      groundCollection,
    })
  }
}
<|endoftext|>


<commit_before>import { Home } from './components/home';
console.log('app is loaded');
<commit_msg>updated file directory<commit_after>import { Home } from './components/home/home';
console.log('app is loaded');
<|endoftext|>


<commit_before>import React from 'react';
import StateWrapper from './stateWrapper.component';
import * as client from './client';

class State {

  constructor() {
    this.listeners = [];
    this.state = {
      hierarchy: []
    };
  }

  listenTo(props, cb) {
    this.listeners = this.listeners.concat([{props, cb}]);
    return cb;
  }

  removeListener(listener) {
    this.listeners = this.listeners.filter(({cb}) => cb !== listener);
  }

  onChange() {
    this.listeners.forEach(({cb, props}) => cb(this.getState(props)));
  }

  setState(newState) {
    this.state = Object.assign(this.state, newState);
    this.onChange();
  }

  getState(props) {
    const state = {};
    props.forEach(prop => {
      state[prop] = this.state[prop] || null;
    });
    return state;
  }

  getHierarchy = (index) => {
    client.hierarchy(index).then(hierarchy => this.setState({hierarchy}));
  }
}

const globalState = new State();

export function wrapper(Component, listenTo) {
  const WrapperComponent = (props) => {
    return (
      <StateWrapper Component={Component} listenTo={listenTo}
                    globalState={globalState} transfer={props}
      />
    );
  };
  return WrapperComponent;
}
<commit_msg>Handle errors in state<commit_after>import React from 'react';
import StateWrapper from './stateWrapper.component';
import * as client from './client';

class State {

  constructor() {
    this.listeners = [];
    this.state = {
      hierarchy: []
    };
  }

  listenTo(props, cb) {
    this.listeners = this.listeners.concat([{props, cb}]);
    return cb;
  }

  removeListener(listener) {
    this.listeners = this.listeners.filter(({cb}) => cb !== listener);
  }

  onChange() {
    this.listeners.forEach(({cb, props}) => cb(this.getState(props)));
  }

  setState(newState) {
    this.state = Object.assign(this.state, newState);
    this.onChange();
  }

  getState(props) {
    const state = {};
    props.forEach(prop => {
      state[prop] = this.state[prop] || null;
    });
    return state;
  }

  getHierarchy = (index) => {
    const currentHierarchy = this.getState(['hierarchy']);
    client.hierarchy(index)
      .then(hierarchy => {
        if (hierarchy.index) {
          this.setState({hierarchy});
        }
      })
      .catch(err => {
        currentHierarchy.error = err;
        this.setState({hierarchy: currentHierarchy});
      });
  }
}

const globalState = new State();

export function wrapper(Component, listenTo) {
  const WrapperComponent = (props) => {
    return (
      <StateWrapper Component={Component} listenTo={listenTo}
                    globalState={globalState} transfer={props}
      />
    );
  };
  return WrapperComponent;
}
<|endoftext|>


<commit_before>// Import Dependencies.
var gulp = require("gulp");
var webpack = require('webpack');
var webpackStream = require("webpack-stream");
var path = require("path");
var processShaders = require("../helpers/gulp-processShaders");
var uncommentShaders = require('../helpers/gulp-removeShaderComments');

// Read the full config.
var config = require("../config.json");

/**
 * Watch ts files and fire repective tasks.
 */
gulp.task("watchLibraries", function startWatch() {
    var tasks = [];

    config.modules.map(function(module) {
        var settings = config[module].build;
        if (!config[module].isCore && settings && settings.webpack) {
            for (var index = 0; index < config[module].libraries.length; index++) {
                var library = config[module].libraries[index];
                if (library.preventLoadLibrary) {
                    continue;
                }

                let wpConfig = require(settings.webpack);
                // watch on.
                wpConfig.watch = true;
                // dev mode and absolute path sourcemaps for debugging
                wpConfig.mode = "development";
                wpConfig.devtool = "nosources-source-map";

                var rootPath = path.resolve(__dirname, "../../../");
                var absoluteSrc = path.resolve(__dirname, "../", settings.srcDirectory);
                wpConfig.output.devtoolModuleFilenameTemplate = (info) => {
                    info.resourcePath = path.normalize(info.resourcePath);

                    if (!path.isAbsolute(info.resourcePath)) {
                        info.resourcePath = path.join(absoluteSrc, info.resourcePath);
                    }

                    return `"../../${path.relative(rootPath, info.resourcePath).replace(/\\/g, "/")}`;
                };

                tasks.push(
                    gulp.src(settings.srcDirectory + "**/*.fx")
                        .pipe(uncommentShaders())
                        .pipe(processShaders(false))
                );

                var outputDirectory = config.build.tempDirectory + settings.distOutputDirectory;
                tasks.push(
                    webpackStream(wpConfig, webpack).pipe(gulp.dest(outputDirectory))
                );

                tasks.push(
                    gulp.watch(settings.srcDirectory + "**/*.fx", { interval: 1000 }, function() {
                        console.log(library.output + ": Shaders.");
                        return gulp.src(settings.srcDirectory + "**/*.fx")
                            .pipe(uncommentShaders())
                            .pipe(processShaders(false));
                    })
                );
            }
        }
    });

    return Promise.resolve();
});<commit_msg>.temp structure change<commit_after>// Import Dependencies.
var gulp = require("gulp");
var webpack = require('webpack');
var webpackStream = require("webpack-stream");
var path = require("path");
var processShaders = require("../helpers/gulp-processShaders");
var uncommentShaders = require('../helpers/gulp-removeShaderComments');

// Read the full config.
var config = require("../config.json");

/**
 * Watch ts files and fire repective tasks.
 */
gulp.task("watchLibraries", function startWatch() {
    var tasks = [];

    config.modules.map(function(module) {
        var settings = config[module].build;
        if (!config[module].isCore && settings && settings.webpack) {
            for (var index = 0; index < config[module].libraries.length; index++) {
                var library = config[module].libraries[index];
                if (library.preventLoadLibrary) {
                    continue;
                }

                let wpConfig = require(settings.webpack);
                // watch on.
                wpConfig.watch = true;
                // dev mode and absolute path sourcemaps for debugging
                wpConfig.mode = "development";
                wpConfig.devtool = "nosources-source-map";

                var rootPath = path.resolve(__dirname, "../../../");
                var absoluteSrc = path.resolve(__dirname, "../", settings.srcDirectory);
                wpConfig.output.devtoolModuleFilenameTemplate = (info) => {
                    info.resourcePath = path.normalize(info.resourcePath);

                    if (!path.isAbsolute(info.resourcePath)) {
                        info.resourcePath = path.join(absoluteSrc, info.resourcePath);
                    }

                    return `"../../../${path.relative(rootPath, info.resourcePath).replace(/\\/g, "/")}`;
                };

                tasks.push(
                    gulp.src(settings.srcDirectory + "**/*.fx")
                        .pipe(uncommentShaders())
                        .pipe(processShaders(false))
                );

                var outputDirectory = config.build.tempDirectory + "localDevUMD" + settings.distOutputDirectory;
                tasks.push(
                    webpackStream(wpConfig, webpack).pipe(gulp.dest(outputDirectory))
                );

                tasks.push(
                    gulp.watch(settings.srcDirectory + "**/*.fx", { interval: 1000 }, function() {
                        console.log(library.output + ": Shaders.");
                        return gulp.src(settings.srcDirectory + "**/*.fx")
                            .pipe(uncommentShaders())
                            .pipe(processShaders(false));
                    })
                );
            }
        }
    });

    return Promise.resolve();
});<|endoftext|>