<commit_before>/**
 * Copyright 2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ReactUpdateQueue
 */

'use strict';

var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactElement = require('ReactElement');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactUpdates = require('ReactUpdates');

var assign = require('Object.assign');
var invariant = require('invariant');
var warning = require('warning');

function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}

function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
  var internalInstance = ReactInstanceMap.get(publicInstance);
  if (!internalInstance) {
    if (__DEV__) {
      // Only warn when we have a callerName. Otherwise we should be silent.
      // We're probably calling from enqueueCallback. We don't want to warn
      // there because we already warned for the corresponding lifecycle method.
      warning(
        !callerName,
        '%s(...): Can only update a mounted or mounting component. ' +
        'This usually means you called %s() on an unmounted component. ' +
        'This is a no-op. Please check the code for the %s component.',
        callerName,
        callerName,
        publicInstance.constructor.displayName
      );
    }
    return null;
  }

  if (__DEV__) {
    warning(
      ReactCurrentOwner.current == null,
      '%s(...): Cannot update during an existing state transition ' +
      '(such as within `render`). Render methods should be a pure function ' +
      'of props and state.',
      callerName
    );
  }

  return internalInstance;
}

/**
 * ReactUpdateQueue allows for state updates to be scheduled into a later
 * reconciliation step.
 */
var ReactUpdateQueue = {

  /**
   * Checks whether or not this composite component is mounted.
   * @param {ReactClass} publicInstance The instance we want to test.
   * @return {boolean} True if mounted, false otherwise.
   * @protected
   * @final
   */
  isMounted: function(publicInstance) {
    if (__DEV__) {
      var owner = ReactCurrentOwner.current;
      if (owner !== null) {
        warning(
          owner._warnedAboutRefsInRender,
          '%s is accessing isMounted inside its render() function. ' +
          'render() should be a pure function of props and state. It should ' +
          'never access something that requires stale data from the previous ' +
          'render, such as refs. Move this logic to componentDidMount and ' +
          'componentDidUpdate instead.',
          owner.getName() || 'A component'
        );
        owner._warnedAboutRefsInRender = true;
      }
    }
    var internalInstance = ReactInstanceMap.get(publicInstance);
    if (internalInstance) {
      // During componentWillMount and render this will still be null but after
      // that will always render to something. At least for now. So we can use
      // this hack.
      return !!internalInstance._renderedComponent;
    } else {
      return false;
    }
  },

  /**
   * Enqueue a callback that will be executed after all the pending updates
   * have processed.
   *
   * @param {ReactClass} publicInstance The instance to use as `this` context.
   * @param {?function} callback Called after state is updated.
   * @internal
   */
  enqueueCallback: function(publicInstance, callback) {
    invariant(
      typeof callback === 'function',
      'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
      '`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
      'isn\'t callable.'
    );
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);

    // Previously we would throw an error if we didn't have an internal
    // instance. Since we want to make it a no-op instead, we mirror the same
    // behavior we have in other enqueue* methods.
    // We also need to ignore callbacks in componentWillMount. See
    // enqueueUpdates.
    if (!internalInstance) {
      return null;
    }

    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    // TODO: The callback here is ignored when setState is called from
    // componentWillMount. Either fix it or disallow doing so completely in
    // favor of getInitialState. Alternatively, we can disallow
    // componentWillMount during server-side rendering.
    enqueueUpdate(internalInstance);
  },

  enqueueCallbackInternal: function(internalInstance, callback) {
    invariant(
      typeof callback === 'function',
      'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
      '`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
      'isn\'t callable.'
    );
    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    enqueueUpdate(internalInstance);
  },

  /**
   * Forces an update. This should only be invoked when it is known with
   * certainty that we are **not** in a DOM transaction.
   *
   * You may want to call this when you know that some deeper aspect of the
   * component's state has changed but `setState` was not called.
   *
   * This will not invoke `shouldComponentUpdate`, but it will invoke
   * `componentWillUpdate` and `componentDidUpdate`.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @internal
   */
  enqueueForceUpdate: function(publicInstance) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'forceUpdate'
    );

    if (!internalInstance) {
      return;
    }

    internalInstance._pendingForceUpdate = true;

    enqueueUpdate(internalInstance);
  },

  /**
   * Replaces all of the state. Always use this or `setState` to mutate state.
   * You should treat `this.state` as immutable.
   *
   * There is no guarantee that `this.state` will be immediately updated, so
   * accessing `this.state` after calling this method may return the old value.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} completeState Next state.
   * @internal
   */
  enqueueReplaceState: function(publicInstance, completeState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'replaceState'
    );

    if (!internalInstance) {
      return;
    }

    internalInstance._pendingStateQueue = [completeState];
    internalInstance._pendingReplaceState = true;

    enqueueUpdate(internalInstance);
  },

  /**
   * Sets a subset of the state. This only exists because _pendingState is
   * internal. This provides a merging strategy that is not available to deep
   * properties which is confusing. TODO: Expose pendingState or don't use it
   * during the merge.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} partialState Next partial state to be merged with state.
   * @internal
   */
  enqueueSetState: function(publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState'
    );

    if (!internalInstance) {
      return;
    }

    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
  },

  /**
   * Sets a subset of the props.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} partialProps Subset of the next props.
   * @internal
   */
  enqueueSetProps: function(publicInstance, partialProps) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setProps'
    );
    if (!internalInstance) {
      return;
    }
    ReactUpdateQueue.enqueueSetPropsInternal(internalInstance, partialProps);
  },

  enqueueSetPropsInternal: function(internalInstance, partialProps) {
    var topLevelWrapper = internalInstance._topLevelWrapper;
    invariant(
      topLevelWrapper,
      'setProps(...): You called `setProps` on a ' +
      'component with a parent. This is an anti-pattern since props will ' +
      'get reactively updated when rendered. Instead, change the owner\'s ' +
      '`render` method to pass the correct value as props to the component ' +
      'where it is created.'
    );

    // Merge with the pending element if it exists, otherwise with existing
    // element props.
    var wrapElement = topLevelWrapper._pendingElement ||
                      topLevelWrapper._currentElement;
    var element = wrapElement.props;
    var props = assign({}, element.props, partialProps);
    topLevelWrapper._pendingElement = ReactElement.cloneAndReplaceProps(
      wrapElement,
      ReactElement.cloneAndReplaceProps(element, props)
    );

    enqueueUpdate(topLevelWrapper);
  },

  /**
   * Replaces all of the props.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} props New props.
   * @internal
   */
  enqueueReplaceProps: function(publicInstance, props) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'replaceProps'
    );
    if (!internalInstance) {
      return;
    }
    ReactUpdateQueue.enqueueReplacePropsInternal(internalInstance, props);
  },

  enqueueReplacePropsInternal: function(internalInstance, props) {
    var topLevelWrapper = internalInstance._topLevelWrapper;
    invariant(
      topLevelWrapper,
      'replaceProps(...): You called `replaceProps` on a ' +
      'component with a parent. This is an anti-pattern since props will ' +
      'get reactively updated when rendered. Instead, change the owner\'s ' +
      '`render` method to pass the correct value as props to the component ' +
      'where it is created.'
    );

    // Merge with the pending element if it exists, otherwise with existing
    // element props.
    var wrapElement = topLevelWrapper._pendingElement ||
                      topLevelWrapper._currentElement;
    var element = wrapElement.props;
    topLevelWrapper._pendingElement = ReactElement.cloneAndReplaceProps(
      wrapElement,
      ReactElement.cloneAndReplaceProps(element, props)
    );

    enqueueUpdate(topLevelWrapper);
  },

  enqueueElementInternal: function(internalInstance, newElement) {
    internalInstance._pendingElement = newElement;
    enqueueUpdate(internalInstance);
  },

};

module.exports = ReactUpdateQueue;
<commit_msg>update enqueueCallback invariant to more specifically explain what caused error<commit_after>/**
 * Copyright 2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ReactUpdateQueue
 */

'use strict';

var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactElement = require('ReactElement');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactUpdates = require('ReactUpdates');

var assign = require('Object.assign');
var invariant = require('invariant');
var warning = require('warning');

function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}

function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
  var internalInstance = ReactInstanceMap.get(publicInstance);
  if (!internalInstance) {
    if (__DEV__) {
      // Only warn when we have a callerName. Otherwise we should be silent.
      // We're probably calling from enqueueCallback. We don't want to warn
      // there because we already warned for the corresponding lifecycle method.
      warning(
        !callerName,
        '%s(...): Can only update a mounted or mounting component. ' +
        'This usually means you called %s() on an unmounted component. ' +
        'This is a no-op. Please check the code for the %s component.',
        callerName,
        callerName,
        publicInstance.constructor.displayName
      );
    }
    return null;
  }

  if (__DEV__) {
    warning(
      ReactCurrentOwner.current == null,
      '%s(...): Cannot update during an existing state transition ' +
      '(such as within `render`). Render methods should be a pure function ' +
      'of props and state.',
      callerName
    );
  }

  return internalInstance;
}

/**
 * ReactUpdateQueue allows for state updates to be scheduled into a later
 * reconciliation step.
 */
var ReactUpdateQueue = {

  /**
   * Checks whether or not this composite component is mounted.
   * @param {ReactClass} publicInstance The instance we want to test.
   * @return {boolean} True if mounted, false otherwise.
   * @protected
   * @final
   */
  isMounted: function(publicInstance) {
    if (__DEV__) {
      var owner = ReactCurrentOwner.current;
      if (owner !== null) {
        warning(
          owner._warnedAboutRefsInRender,
          '%s is accessing isMounted inside its render() function. ' +
          'render() should be a pure function of props and state. It should ' +
          'never access something that requires stale data from the previous ' +
          'render, such as refs. Move this logic to componentDidMount and ' +
          'componentDidUpdate instead.',
          owner.getName() || 'A component'
        );
        owner._warnedAboutRefsInRender = true;
      }
    }
    var internalInstance = ReactInstanceMap.get(publicInstance);
    if (internalInstance) {
      // During componentWillMount and render this will still be null but after
      // that will always render to something. At least for now. So we can use
      // this hack.
      return !!internalInstance._renderedComponent;
    } else {
      return false;
    }
  },

  /**
   * Enqueue a callback that will be executed after all the pending updates
   * have processed.
   *
   * @param {ReactClass} publicInstance The instance to use as `this` context.
   * @param {?function} callback Called after state is updated.
   * @internal
   */
  enqueueCallback: function(publicInstance, callback) {
    invariant(
      typeof callback === 'function',
      'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
      '`setState`, `replaceState`, or `forceUpdate` with a callback of type ' +
      '%s. A function is expected',
      typeof callback === 'object' && Object.keys(callback).length && Object.keys(callback).length < 20 ?
        typeof callback + ' (keys: ' + Object.keys(callback) + ')' : typeof callback
    );
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);

    // Previously we would throw an error if we didn't have an internal
    // instance. Since we want to make it a no-op instead, we mirror the same
    // behavior we have in other enqueue* methods.
    // We also need to ignore callbacks in componentWillMount. See
    // enqueueUpdates.
    if (!internalInstance) {
      return null;
    }

    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    // TODO: The callback here is ignored when setState is called from
    // componentWillMount. Either fix it or disallow doing so completely in
    // favor of getInitialState. Alternatively, we can disallow
    // componentWillMount during server-side rendering.
    enqueueUpdate(internalInstance);
  },

  enqueueCallbackInternal: function(internalInstance, callback) {
    invariant(
      typeof callback === 'function',
      'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
      '`setState`, `replaceState`, or `forceUpdate` with a callback of type ' +
      '%s. A function is expected',
      typeof callback === 'object' && Object.keys(callback).length && Object.keys(callback).length < 20 ?
        typeof callback + ' (keys: ' + Object.keys(callback) + ')' : typeof callback
    );
    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    enqueueUpdate(internalInstance);
  },

  /**
   * Forces an update. This should only be invoked when it is known with
   * certainty that we are **not** in a DOM transaction.
   *
   * You may want to call this when you know that some deeper aspect of the
   * component's state has changed but `setState` was not called.
   *
   * This will not invoke `shouldComponentUpdate`, but it will invoke
   * `componentWillUpdate` and `componentDidUpdate`.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @internal
   */
  enqueueForceUpdate: function(publicInstance) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'forceUpdate'
    );

    if (!internalInstance) {
      return;
    }

    internalInstance._pendingForceUpdate = true;

    enqueueUpdate(internalInstance);
  },

  /**
   * Replaces all of the state. Always use this or `setState` to mutate state.
   * You should treat `this.state` as immutable.
   *
   * There is no guarantee that `this.state` will be immediately updated, so
   * accessing `this.state` after calling this method may return the old value.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} completeState Next state.
   * @internal
   */
  enqueueReplaceState: function(publicInstance, completeState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'replaceState'
    );

    if (!internalInstance) {
      return;
    }

    internalInstance._pendingStateQueue = [completeState];
    internalInstance._pendingReplaceState = true;

    enqueueUpdate(internalInstance);
  },

  /**
   * Sets a subset of the state. This only exists because _pendingState is
   * internal. This provides a merging strategy that is not available to deep
   * properties which is confusing. TODO: Expose pendingState or don't use it
   * during the merge.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} partialState Next partial state to be merged with state.
   * @internal
   */
  enqueueSetState: function(publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState'
    );

    if (!internalInstance) {
      return;
    }

    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
  },

  /**
   * Sets a subset of the props.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} partialProps Subset of the next props.
   * @internal
   */
  enqueueSetProps: function(publicInstance, partialProps) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setProps'
    );
    if (!internalInstance) {
      return;
    }
    ReactUpdateQueue.enqueueSetPropsInternal(internalInstance, partialProps);
  },

  enqueueSetPropsInternal: function(internalInstance, partialProps) {
    var topLevelWrapper = internalInstance._topLevelWrapper;
    invariant(
      topLevelWrapper,
      'setProps(...): You called `setProps` on a ' +
      'component with a parent. This is an anti-pattern since props will ' +
      'get reactively updated when rendered. Instead, change the owner\'s ' +
      '`render` method to pass the correct value as props to the component ' +
      'where it is created.'
    );

    // Merge with the pending element if it exists, otherwise with existing
    // element props.
    var wrapElement = topLevelWrapper._pendingElement ||
                      topLevelWrapper._currentElement;
    var element = wrapElement.props;
    var props = assign({}, element.props, partialProps);
    topLevelWrapper._pendingElement = ReactElement.cloneAndReplaceProps(
      wrapElement,
      ReactElement.cloneAndReplaceProps(element, props)
    );

    enqueueUpdate(topLevelWrapper);
  },

  /**
   * Replaces all of the props.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} props New props.
   * @internal
   */
  enqueueReplaceProps: function(publicInstance, props) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'replaceProps'
    );
    if (!internalInstance) {
      return;
    }
    ReactUpdateQueue.enqueueReplacePropsInternal(internalInstance, props);
  },

  enqueueReplacePropsInternal: function(internalInstance, props) {
    var topLevelWrapper = internalInstance._topLevelWrapper;
    invariant(
      topLevelWrapper,
      'replaceProps(...): You called `replaceProps` on a ' +
      'component with a parent. This is an anti-pattern since props will ' +
      'get reactively updated when rendered. Instead, change the owner\'s ' +
      '`render` method to pass the correct value as props to the component ' +
      'where it is created.'
    );

    // Merge with the pending element if it exists, otherwise with existing
    // element props.
    var wrapElement = topLevelWrapper._pendingElement ||
                      topLevelWrapper._currentElement;
    var element = wrapElement.props;
    topLevelWrapper._pendingElement = ReactElement.cloneAndReplaceProps(
      wrapElement,
      ReactElement.cloneAndReplaceProps(element, props)
    );

    enqueueUpdate(topLevelWrapper);
  },

  enqueueElementInternal: function(internalInstance, newElement) {
    internalInstance._pendingElement = newElement;
    enqueueUpdate(internalInstance);
  },

};

module.exports = ReactUpdateQueue;
<|endoftext|>


<commit_before>/*
options = {
    onPlayerConnect: function()
    onPlayerDisconnect: function (),
    onPlayerReconnect: function (),
    onPlayerUpdate: function (state),
    onPlayerRemove: function (id),
    onGameState: function (state),
    onPing: function (pingDelay),
    onUpdatePlayerPhysics: function (id, state, inputs, deltaTime),
    onUpdate: function (),
    onInterpolation: function(id, previousState, targetState, amount)
    logging: true,
    clientSidePrediction: true,
    interpolation: true,
    interpolationDelay: 100,
    pingInterval: 2000
}
*/

window.GarageServerIO = (function (window, socketio) {

    function StateController() {
        this.state = {};
        this.clientTime;
        this.renderTime;
        this.physicsDelta;
        this.physicsIntervalId;
        this.currentTime;
        this.accumulator = 0.0;
        this.playerId;
        this.pingDelay = 100;
        this.interpolationDelay = 100;
    }
    StateController.prototype = {
        setTime: function (serverTime) {
            this.clientTime = serverTime;
            this.renderTime = this.clientTime - this.interpolationDelay;
        },
        accumulate: function () {
            var newTime = new Date().getTime(),
                frameTime = newTime - this.currentTime;
            if (frameTime  > 250) {
                frameTime = 250;
            }
            this.currentTime = newTime;
            this.accumulator += frameTime;
        }
    };

    function Input(input, seq) {
        this.input = input;
        this.seq = seq;
    }

    function InputController() {
        this.inputs = [];
        this.sequenceNumber = 1;
    }
    InputController.prototype = {
        any: function () {
            return this.inputs.length > 0;
        },
        addInput: function (input) {
            this.sequenceNumber += 1;
            this.inputs.push(new Input(input, this.sequenceNumber));
        },
        removeUpToSequence: function (seq) {
            for (var i = 0; i < this.inputs.length; i ++) {
                if (this.inputs[i].seq === seq) {
                    this.inputs.splice(0, i + 1);
                    break;
                }
            }
        }
    };

    function Update(state, seq, time) {
        this.state = state;
        this.seq = seq;
        this.time = time;
    }

    function Entity(id) {
        this.updates = [];
        this.id = id;
        this.currentState = {};
    }
    Entity.prototype = {
        anyUpdates: function () {
            return this.updates.length > 0;
        },
        addUpate: function (state, seq, time) {
            var newUpdate = new Update(state, seq, time);
            if (this.updates.length === 0) {
                this.currentState = newUpdate.state;
            }
            this.updates.push(newUpdate);
            if (this.updates.length > 120) {
                this.updates.splice(0, 1);
            }
        },
        getLatestUpdate: function () {
            return this.updates[this.updates.length - 1];
        },
        processState: function (state, seq, time) {
            var updateFound = false;
            this.updates.some(function (update) {
               if (update.seq === seq) {
                   update.state = state;
                   updateFound = true;
                   return true;
               }
            });
            if (!updateFound) {
                this.addUpate(state, seq, time);
            }
        },
        getSurroundingPositions: function (time) {
            var positions = {};
            for (var i = 0; i < this.updates.length; i ++) {
                var previous = this.updates[i];
                var target = this.updates[i + 1];

                if(previous && target && time > previous.time && time < target.time) {
                    positions.previous = previous;
                    positions.target = target;
                    break;
                }
            }
            return positions;
        }
    };

    function Player(id) {
        this.updates = [];
        this.id = id;
        this.currentState = {};
    }
    Player.prototype = Object.create(Entity.prototype);

    function PlayerController() {
        this.players = [];
    }
    PlayerController.prototype = {
        addPlayer: function (id) {
            var player = new Player(id);
            this.players.push(player);
            return player;
        },
        removePlayer: function (id) {
            for (var i = 0; i < this.players.length; i ++) {
                this.players.splice(i, 1);
                return;
            }
        }
    };

    var _io = socketio,
        _socket = null,
        _options = null,
        _stateController = new StateController(),
        _inputController = new InputController(),
        _playerController = new PlayerController(),

        initializeGarageServer = function (path, opts) {
            _options = opts;
            _socket = _io.connect(path + '/garageserver.io');
            registerSocketEvents();
            registerPinger();
        },

        registerSocketEvents = function () {
            _socket.on('connect', function () {
                _stateController.playerId = _socket.id;
                _stateController.interpolationDelay = _options.interpolationDelay ? _options.interpolationDelay : 100;
                if (_options.onPlayerConnect) {
                    _options.onPlayerConnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket connect');
                }
            });
            _socket.on('state', function(data) {
                if (_options.onGameState) {
                    _options.onGameState(data);
                }
                _stateController.physicsDelta = data.physicsDelta;
            });
            _socket.on('disconnect', function () {
                if (_options.onPlayerDisconnect) {
                    _options.onPlayerDisconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket disconnect');
                }
            });
            _socket.on('reconnect', function () {
                if (_options.onPlayerReconnect) {
                    _options.onPlayerReconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket reconnect');
                }
            });
            _socket.on('update', function(data) {
                updateState(data);
            });
            _socket.on('ping', function(data) {
                _stateController.pingDelay = new Date().getTime() - data;
                if (_options.onPing) {
                    _options.onPing(_stateController.pingDelay);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket ping delay ' + _stateController.pingDelay);
                }
            });
            _socket.on('removePlayer', function(id) {
                removePlayer(id);
                if (_options.onPlayerRemove) {
                    _options.onPlayerRemove(id);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket removePlayer ' + id);
                }
            });
        },

        registerPinger = function () {
            var interval = 2000;
            if (_options.pingInterval) {
                interval = _options.pingInterval;
            }
            setInterval(function (){
                _socket.emit('ping', new Date().getTime());
            }, interval);
        },

        start = function () {
            var self = this;
            _stateController.currentTime = new Date().getTime();
            _stateController.physicsIntervalId = setInterval(function () { self.update(); }, this.physicsDelta * 1000);
        },

        getPlayerId = function () {
            return _stateController.playerId;
        },

        setPlayerState = function (state) {
            _socket.emit('playerState', state);
        },

        removePlayer = function (id) {
            _playerController.removePlayer(id);
        },

        update = function () {
            if (_options.onUpdate) {
                _stateController.accumulate();
                while (_stateController.accumulator >= (_stateController.physicsDelta * 1000))
                {
                    _options.onUpdate();
                    _stateController.accumulator -= (_stateController.physicsDelta * 1000);
                }
            }
        },

        addPlayerInput = function (clientInput) {
            _inputController.addInput(clientInput);
            if (_options.clientSidePrediction && _options.onUpdatePlayerPhysics) {
                _stateController.state = _options.onUpdatePlayerPhysics(_stateController.playerId, _stateController.state, [{ input: clientInput }], _stateController.physicsDelta);
            }
            _socket.emit('input', [ clientInput, _inputController.sequenceNumber, _stateController.renderTime ]);
        },

        updateState = function (data) {
            _stateController.setTime(data.time);

            updatePlayersState(data);
            updateEntitiesState(data);
        },

        updatePlayersState = function (data) {
            data.playerStates.forEach(function (playerState) {
                if (_socket.socket.sessionid === playerState[0]) {
                    updatePlayerState(playerState);
                } else {
                    updateOtherPlayersState(playerState, data.time);
                }

                if (_options.onPlayerUpdate) {
                    _options.onPlayerUpdate(playerState[1]);
                }
            });
        },

        updatePlayerState = function (playerState) {
            _stateController.state = playerState[1];
            _inputController.removeUpToSequence(playerState[2]);

            if (_options.clientSidePrediction && _inputController.any()) {
                _stateController.state = _options.onUpdatePlayerPhysics(_stateController.playerId, _stateController.state, _inputController.inputs, _stateController.physicsDelta);
            }
        },

        updateOtherPlayersState = function (playerState, time) {
            var playerFound = false;
            _playerController.players.some(function (player) {
                if (player.id === playerState[0]) {
                    playerFound = true;
                    player.processState(playerState[1], playerState[2], time);
                    return true;
                }
            });
            if (!playerFound) {
                var newPlayer = _playerController.addPlayer(playerState[0]);
                newPlayer.addUpate(playerState[1], playerState[2], time);
            }
        },

        updateEntitiesState = function (data) {

        },

        getPlayerStates = function (stateCallback) {
            if(_options.interpolation && _options.onInterpolation) {
                getPlayerStatesInterpolated(stateCallback);
            }
            else {
                getPlayerStatesCurrent(stateCallback);
            }
            stateCallback(_stateController.state);
        },

        getPlayerStatesCurrent = function (stateCallback) {
            _playerController.players.forEach(function (player) {
                if (player.anyUpdates()) {
                    stateCallback(player.getLatestUpdate().state);
                }
            });
        },

        getPlayerStatesInterpolated = function (stateCallback) {
            var positions, amount, newState;
            _playerController.players.forEach(function (player) {
                if (player.anyUpdates()) {
                    positions = player.getSurroundingPositions(_stateController.renderTime);
                    if (positions.previous && positions.target) {
                        amount = getInterpolatedAmount(positions.previous.time, positions.target.time);
                        newState = _options.onInterpolation(player.id, positions.previous.state, positions.target.state, amount);
                        player.currentState = newState = _options.onInterpolation(player.id, player.currentState, newState, _stateController.physicsDelta * 20);
                        stateCallback(player.currentState);
                    }
                    else {
                        stateCallback(player.currentState);
                    }
                }
            });
        },

        getInterpolatedAmount = function (previousTime, targetTime) {
            var range = targetTime - previousTime,
                difference = _stateController.renderTime - previousTime,
                amount = parseFloat((difference / range).toFixed(3));

            return amount;
        };

    return {
        initializeGarageServer: initializeGarageServer,
        start: start,
        update: update,
        addPlayerInput: addPlayerInput,
        getPlayerStates: getPlayerStates,
        getPlayerId: getPlayerId,
        setPlayerState: setPlayerState
    };

}) (window, io);<commit_msg>Progress: #3<commit_after>/*
options = {
    onPlayerConnect: function()
    onPlayerDisconnect: function (),
    onPlayerReconnect: function (),
    onPlayerUpdate: function (state),
    onPlayerRemove: function (id),
    onGameState: function (state),
    onPing: function (pingDelay),
    onUpdatePlayerPhysics: function (id, state, inputs, deltaTime),
    onUpdate: function (),
    onInterpolation: function(id, previousState, targetState, amount)
    logging: true,
    clientSidePrediction: true,
    interpolation: true,
    interpolationDelay: 100,
    pingInterval: 2000
}
*/

window.GarageServerIO = (function (window, socketio) {

    function StateController() {
        this.state = {};
        this.clientTime;
        this.renderTime;
        this.physicsDelta;
        this.physicsIntervalId;
        this.currentTime;
        this.accumulator = 0.0;
        this.playerId;
        this.pingDelay = 100;
        this.interpolationDelay = 100;
    }
    StateController.prototype = {
        setTime: function (serverTime) {
            this.clientTime = serverTime;
            this.renderTime = this.clientTime - this.interpolationDelay;
        },
        accumulate: function () {
            var newTime = new Date().getTime(),
                frameTime = newTime - this.currentTime;
            if (frameTime  > 250) {
                frameTime = 250;
            }
            this.currentTime = newTime;
            this.accumulator += frameTime;
        }
    };

    function Input(input, seq) {
        this.input = input;
        this.seq = seq;
    }

    function InputController() {
        this.inputs = [];
        this.sequenceNumber = 1;
    }
    InputController.prototype = {
        any: function () {
            return this.inputs.length > 0;
        },
        addInput: function (input) {
            this.sequenceNumber += 1;
            this.inputs.push(new Input(input, this.sequenceNumber));
        },
        removeUpToSequence: function (seq) {
            for (var i = 0; i < this.inputs.length; i ++) {
                if (this.inputs[i].seq === seq) {
                    this.inputs.splice(0, i + 1);
                    break;
                }
            }
        }
    };

    function Update(state, seq, time) {
        this.state = state;
        this.seq = seq;
        this.time = time;
    }

    function Entity() {
        this.updates = [];
        this.id;
        this.currentState = {};
    }
    Entity.prototype = {
        anyUpdates: function () {
            return this.updates.length > 0;
        },
        addUpate: function (state, seq, time) {
            var newUpdate = new Update(state, seq, time);
            if (this.updates.length === 0) {
                this.currentState = newUpdate.state;
            }
            this.updates.push(newUpdate);
            if (this.updates.length > 120) {
                this.updates.splice(0, 1);
            }
        },
        getLatestUpdate: function () {
            return this.updates[this.updates.length - 1];
        },
        processState: function (state, seq, time) {
            var updateFound = false;
            this.updates.some(function (update) {
               if (update.seq === seq) {
                   update.state = state;
                   updateFound = true;
                   return true;
               }
            });
            if (!updateFound) {
                this.addUpate(state, seq, time);
            }
        },
        getSurroundingPositions: function (time) {
            var positions = {};
            for (var i = 0; i < this.updates.length; i ++) {
                var previous = this.updates[i];
                var target = this.updates[i + 1];

                if(previous && target && time > previous.time && time < target.time) {
                    positions.previous = previous;
                    positions.target = target;
                    break;
                }
            }
            return positions;
        }
    };

    function Player(id) {
        Entity.call(this);
        this.id = id;
    }
    Player.prototype = Object.create(Entity.prototype);

    function PlayerController() {
        this.players = [];
    }
    PlayerController.prototype = {
        addPlayer: function (id) {
            var player = new Player(id);
            this.players.push(player);
            return player;
        },
        removePlayer: function (id) {
            for (var i = 0; i < this.players.length; i ++) {
                this.players.splice(i, 1);
                return;
            }
        }
    };

    var _io = socketio,
        _socket = null,
        _options = null,
        _stateController = new StateController(),
        _inputController = new InputController(),
        _playerController = new PlayerController(),

        initializeGarageServer = function (path, opts) {
            _options = opts;
            _socket = _io.connect(path + '/garageserver.io');
            registerSocketEvents();
            registerPinger();
        },

        registerSocketEvents = function () {
            _socket.on('connect', function () {
                _stateController.playerId = _socket.id;
                _stateController.interpolationDelay = _options.interpolationDelay ? _options.interpolationDelay : 100;
                if (_options.onPlayerConnect) {
                    _options.onPlayerConnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket connect');
                }
            });
            _socket.on('state', function(data) {
                if (_options.onGameState) {
                    _options.onGameState(data);
                }
                _stateController.physicsDelta = data.physicsDelta;
            });
            _socket.on('disconnect', function () {
                if (_options.onPlayerDisconnect) {
                    _options.onPlayerDisconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket disconnect');
                }
            });
            _socket.on('reconnect', function () {
                if (_options.onPlayerReconnect) {
                    _options.onPlayerReconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket reconnect');
                }
            });
            _socket.on('update', function(data) {
                updateState(data);
            });
            _socket.on('ping', function(data) {
                _stateController.pingDelay = new Date().getTime() - data;
                if (_options.onPing) {
                    _options.onPing(_stateController.pingDelay);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket ping delay ' + _stateController.pingDelay);
                }
            });
            _socket.on('removePlayer', function(id) {
                removePlayer(id);
                if (_options.onPlayerRemove) {
                    _options.onPlayerRemove(id);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket removePlayer ' + id);
                }
            });
        },

        registerPinger = function () {
            var interval = 2000;
            if (_options.pingInterval) {
                interval = _options.pingInterval;
            }
            setInterval(function (){
                _socket.emit('ping', new Date().getTime());
            }, interval);
        },

        start = function () {
            var self = this;
            _stateController.currentTime = new Date().getTime();
            _stateController.physicsIntervalId = setInterval(function () { self.update(); }, this.physicsDelta * 1000);
        },

        getPlayerId = function () {
            return _stateController.playerId;
        },

        setPlayerState = function (state) {
            _socket.emit('playerState', state);
        },

        removePlayer = function (id) {
            _playerController.removePlayer(id);
        },

        update = function () {
            if (_options.onUpdate) {
                _stateController.accumulate();
                while (_stateController.accumulator >= (_stateController.physicsDelta * 1000))
                {
                    _options.onUpdate();
                    _stateController.accumulator -= (_stateController.physicsDelta * 1000);
                }
            }
        },

        addPlayerInput = function (clientInput) {
            _inputController.addInput(clientInput);
            if (_options.clientSidePrediction && _options.onUpdatePlayerPhysics) {
                _stateController.state = _options.onUpdatePlayerPhysics(_stateController.playerId, _stateController.state, [{ input: clientInput }], _stateController.physicsDelta);
            }
            _socket.emit('input', [ clientInput, _inputController.sequenceNumber, _stateController.renderTime ]);
        },

        updateState = function (data) {
            _stateController.setTime(data.time);

            updatePlayersState(data);
            updateEntitiesState(data);
        },

        updatePlayersState = function (data) {
            data.playerStates.forEach(function (playerState) {
                if (_socket.socket.sessionid === playerState[0]) {
                    updatePlayerState(playerState);
                } else {
                    updateOtherPlayersState(playerState, data.time);
                }

                if (_options.onPlayerUpdate) {
                    _options.onPlayerUpdate(playerState[1]);
                }
            });
        },

        updatePlayerState = function (playerState) {
            _stateController.state = playerState[1];
            _inputController.removeUpToSequence(playerState[2]);

            if (_options.clientSidePrediction && _inputController.any()) {
                _stateController.state = _options.onUpdatePlayerPhysics(_stateController.playerId, _stateController.state, _inputController.inputs, _stateController.physicsDelta);
            }
        },

        updateOtherPlayersState = function (playerState, time) {
            var playerFound = false;
            _playerController.players.some(function (player) {
                if (player.id === playerState[0]) {
                    playerFound = true;
                    player.processState(playerState[1], playerState[2], time);
                    return true;
                }
            });
            if (!playerFound) {
                var newPlayer = _playerController.addPlayer(playerState[0]);
                newPlayer.addUpate(playerState[1], playerState[2], time);
            }
        },

        updateEntitiesState = function (data) {

        },

        getPlayerStates = function (stateCallback) {
            if(_options.interpolation && _options.onInterpolation) {
                getPlayerStatesInterpolated(stateCallback);
            }
            else {
                getPlayerStatesCurrent(stateCallback);
            }
            stateCallback(_stateController.state);
        },

        getPlayerStatesCurrent = function (stateCallback) {
            _playerController.players.forEach(function (player) {
                if (player.anyUpdates()) {
                    stateCallback(player.getLatestUpdate().state);
                }
            });
        },

        getPlayerStatesInterpolated = function (stateCallback) {
            var positions, amount, newState;
            _playerController.players.forEach(function (player) {
                if (player.anyUpdates()) {
                    positions = player.getSurroundingPositions(_stateController.renderTime);
                    if (positions.previous && positions.target) {
                        amount = getInterpolatedAmount(positions.previous.time, positions.target.time);
                        newState = _options.onInterpolation(player.id, positions.previous.state, positions.target.state, amount);
                        player.currentState = newState = _options.onInterpolation(player.id, player.currentState, newState, _stateController.physicsDelta * 20);
                        stateCallback(player.currentState);
                    }
                    else {
                        stateCallback(player.currentState);
                    }
                }
            });
        },

        getInterpolatedAmount = function (previousTime, targetTime) {
            var range = targetTime - previousTime,
                difference = _stateController.renderTime - previousTime,
                amount = parseFloat((difference / range).toFixed(3));

            return amount;
        };

    return {
        initializeGarageServer: initializeGarageServer,
        start: start,
        update: update,
        addPlayerInput: addPlayerInput,
        getPlayerStates: getPlayerStates,
        getPlayerId: getPlayerId,
        setPlayerState: setPlayerState
    };

}) (window, io);<|endoftext|>


<commit_before>import mdc from '../src/index-browser'
import $ from 'jquery'

const request = new window.XMLHttpRequest()
request.onload = function () {
  mdc.init(this.response, false)
  window.addEventListener('hashchange', () => { window.scrollBy(0, -8) }) // a little gap to top
  if (window.location.hash.length > 0) {
    $('html,body').scrollTop($(window.location.hash).offset().top - 8) // scroll to hash element
  }
}
request.open('GET', 'sample.md')
request.send()
<commit_msg>No adjust to anchor scrolling<commit_after>import mdc from '../src/index-browser'
import $ from 'jquery'

const request = new window.XMLHttpRequest()
request.onload = function () {
  mdc.init(this.response, false)
  if (window.location.hash.length > 0) {
    $('html,body').scrollTop($(window.location.hash).offset().top - 8) // scroll to hash element
  }
}
request.open('GET', 'sample.md')
request.send()
<|endoftext|>


<commit_before>/* global app */

/* Polyfills */
import '6to5/polyfill';
import 'isomorphic-fetch';

/* Logging */
try {
  localStorage.debug = ['localhost', 'dev'].indexOf(document.domain) !== -1 ? true : false;
} catch(e) {
  console.info(e);
}
import * as bows from 'bows';
var log = bows('App');

import * as domReady from 'domready';
import * as AmpersandState from 'ampersand-state';

import Satellites from './models/satellites';
import User from './models/user';

import MainView from './pages/home';

var app = {

  inititalize: function () {

    window.app = this;

    // window.onerror = function (err) {
    //   console.error(err);
    //   console.error(err.stack);
    // }

    this.user = new User();
    this.satellites = new Satellites();

    domReady( () => {

      this.mainView = new MainView({
        el: document.querySelector('div.cont')
      });

      this.mainView.render();

    });

    Promise.all([
      this.user.load(),
      this.satellites.load.call(this.satellites)
    ])
    .then( (result) => {

      var ephemerides = result[1];

      this.satellites.reset(ephemerides.satellites);
      log.info('Added ephemerides');

      this.satellites.save();
      this.user.getUserLocation();

    })
    .catch(function (err) {
      throw err;
    });

  }

}

app.inititalize();

export default app;
<commit_msg>Fix hiding debugging logs<commit_after>/* global app */

/* Polyfills */
import '6to5/polyfill';
import 'isomorphic-fetch';

/* Logging */
try {

  if ( ['localhost', 'dev'].indexOf(document.domain) !== -1 ) {
    localStorage.setItem('debug', true);
  } else {
    localStorage.removeItem('debug')
  }

} catch(e) {
  console.info(e);
}
import * as bows from 'bows';
var log = bows('App');

import * as domReady from 'domready';
import * as AmpersandState from 'ampersand-state';

import Satellites from './models/satellites';
import User from './models/user';

import MainView from './pages/home';

var app = {

  inititalize: function () {

    window.app = this;

    // window.onerror = function (err) {
    //   console.error(err);
    //   console.error(err.stack);
    // }

    this.user = new User();
    this.satellites = new Satellites();

    domReady( () => {

      this.mainView = new MainView({
        el: document.querySelector('div.cont')
      });

      this.mainView.render();

    });

    Promise.all([
      this.user.load(),
      this.satellites.load.call(this.satellites)
    ])
    .then( (result) => {

      var ephemerides = result[1];

      this.satellites.reset(ephemerides.satellites);
      log.info('Added ephemerides');

      this.satellites.save();
      this.user.getUserLocation();

    })
    .catch(function (err) {
      throw err;
    });

  }

}

app.inititalize();

export default app;
<|endoftext|>


<commit_before>angular.module("leaflet-directive", []).directive('leaflet', function ($q, leafletData, leafletMapDefaults, leafletHelpers, leafletEvents) {
    var _leafletMap;
    return {
        restrict: "EA",
        replace: true,
        scope: {
            center: '=center',
            defaults: '=defaults',
            maxbounds: '=maxbounds',
            bounds: '=bounds',
            markers: '=markers',
            legend: '=legend',
            geojson: '=geojson',
            paths: '=paths',
            tiles: '=tiles',
            layers: '=layers',
            controls: '=controls',
            decorations: '=decorations',
            eventBroadcast: '=eventBroadcast'
        },
        transclude: true,
        template: '

',
        controller: function ($scope) {
            _leafletMap = $q.defer();
            this.getMap = function () {
                return _leafletMap.promise;
            };

            this.getLeafletScope = function() {
                return $scope;
            };
        },

        link: function(scope, element, attrs) {
            var isDefined = leafletHelpers.isDefined,
                defaults = leafletMapDefaults.setDefaults(scope.defaults, attrs.id),
                genDispatchMapEvent = leafletEvents.genDispatchMapEvent,
                mapEvents = leafletEvents.getAvailableMapEvents();

            // Set width and height if they are defined
            if (isDefined(attrs.width)) {
                if (isNaN(attrs.width)) {
                    element.css('width', attrs.width);
                } else {
                    element.css('width', attrs.width + 'px');
                }
            }
            if (isDefined(attrs.height)) {
                if (isNaN(attrs.height)) {
                    element.css('height', attrs.height);
                } else {
                    element.css('height', attrs.height + 'px');
                }
            }

            // Create the Leaflet Map Object with the options
            var map = new L.Map(element[0], leafletMapDefaults.getMapCreationDefaults(attrs.id));
            _leafletMap.resolve(map);

            if (!isDefined(attrs.center)) {
                map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
            }

            // If no layers nor tiles defined, set the default tileLayer
            if (!isDefined(attrs.tiles) && (!isDefined(attrs.layers))) {
                var tileLayerObj = L.tileLayer(defaults.tileLayer, defaults.tileLayerOptions);
                tileLayerObj.addTo(map);
                leafletData.setTiles(tileLayerObj, attrs.id);
            }

            // Set zoom control configuration
            if (isDefined(map.zoomControl) && isDefined(defaults.zoomControlPosition)) {
                map.zoomControl.setPosition(defaults.zoomControlPosition);
            }

            if(isDefined(map.zoomControl) && defaults.zoomControl===false) {
                map.zoomControl.removeFrom(map);
            }

            if(isDefined(map.zoomsliderControl) && isDefined(defaults.zoomsliderControl) && defaults.zoomsliderControl===false) {
                map.zoomsliderControl.removeFrom(map);
            }

            // if no event-broadcast attribute, all events are broadcasted
            if (!isDefined(attrs.eventBroadcast)) {
                var logic = "broadcast";
                for (var i = 0; i < mapEvents.length; i++) {
                    var eventName = mapEvents[i];
                    map.on(eventName, genDispatchMapEvent(scope, eventName, logic), {
                        eventName: eventName
                    });
                }
            }

            // Resolve the map object to the promises
            map.whenReady(function() {
                leafletData.setMap(map, attrs.id);
            });

            scope.$on('$destroy', function () {
                leafletData.unresolveMap(attrs.id);
            });
        }
    };
});
<commit_msg>Add map.remove on scope $destroy<commit_after>angular.module("leaflet-directive", []).directive('leaflet', function ($q, leafletData, leafletMapDefaults, leafletHelpers, leafletEvents) {
    var _leafletMap;
    return {
        restrict: "EA",
        replace: true,
        scope: {
            center: '=center',
            defaults: '=defaults',
            maxbounds: '=maxbounds',
            bounds: '=bounds',
            markers: '=markers',
            legend: '=legend',
            geojson: '=geojson',
            paths: '=paths',
            tiles: '=tiles',
            layers: '=layers',
            controls: '=controls',
            decorations: '=decorations',
            eventBroadcast: '=eventBroadcast'
        },
        transclude: true,
        template: '

',
        controller: function ($scope) {
            _leafletMap = $q.defer();
            this.getMap = function () {
                return _leafletMap.promise;
            };

            this.getLeafletScope = function() {
                return $scope;
            };
        },

        link: function(scope, element, attrs) {
            var isDefined = leafletHelpers.isDefined,
                defaults = leafletMapDefaults.setDefaults(scope.defaults, attrs.id),
                genDispatchMapEvent = leafletEvents.genDispatchMapEvent,
                mapEvents = leafletEvents.getAvailableMapEvents();

            // Set width and height if they are defined
            if (isDefined(attrs.width)) {
                if (isNaN(attrs.width)) {
                    element.css('width', attrs.width);
                } else {
                    element.css('width', attrs.width + 'px');
                }
            }
            if (isDefined(attrs.height)) {
                if (isNaN(attrs.height)) {
                    element.css('height', attrs.height);
                } else {
                    element.css('height', attrs.height + 'px');
                }
            }

            // Create the Leaflet Map Object with the options
            var map = new L.Map(element[0], leafletMapDefaults.getMapCreationDefaults(attrs.id));
            _leafletMap.resolve(map);

            if (!isDefined(attrs.center)) {
                map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
            }

            // If no layers nor tiles defined, set the default tileLayer
            if (!isDefined(attrs.tiles) && (!isDefined(attrs.layers))) {
                var tileLayerObj = L.tileLayer(defaults.tileLayer, defaults.tileLayerOptions);
                tileLayerObj.addTo(map);
                leafletData.setTiles(tileLayerObj, attrs.id);
            }

            // Set zoom control configuration
            if (isDefined(map.zoomControl) && isDefined(defaults.zoomControlPosition)) {
                map.zoomControl.setPosition(defaults.zoomControlPosition);
            }

            if(isDefined(map.zoomControl) && defaults.zoomControl===false) {
                map.zoomControl.removeFrom(map);
            }

            if(isDefined(map.zoomsliderControl) && isDefined(defaults.zoomsliderControl) && defaults.zoomsliderControl===false) {
                map.zoomsliderControl.removeFrom(map);
            }

            // if no event-broadcast attribute, all events are broadcasted
            if (!isDefined(attrs.eventBroadcast)) {
                var logic = "broadcast";
                for (var i = 0; i < mapEvents.length; i++) {
                    var eventName = mapEvents[i];
                    map.on(eventName, genDispatchMapEvent(scope, eventName, logic), {
                        eventName: eventName
                    });
                }
            }

            // Resolve the map object to the promises
            map.whenReady(function() {
                leafletData.setMap(map, attrs.id);
            });

            scope.$on('$destroy', function () {
                leafletData.getMap().then(function(map) {
                    map.remove();
                });
                leafletData.unresolveMap(attrs.id);
            });
        }
    };
});
<|endoftext|>


<commit_before>import { mount } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import Table from '@/components/Table'
import Column from '@/components/Table/Column'

describe('components/Table', () => {
  it('should select the specified fields.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'haha',
                field2: 11
              },
              {
                field1: 'heihei',
                field2: 22
              },
              {
                field1: 'heihei111',
                field2: 33
              },
              {
                field1: 'heihei1112333',
                field2: 44
              }
            ],
            selected: []
          }
        },
        template: `
        <veui-table :data="data" keys="field2" selectable :selected.sync="selected">
          <veui-table-column field="field1"></veui-table-column>
        </veui-table>`
      },
      {
        sync: false
      }
    )

    let boxes = wrapper.findAll('td input[type="checkbox"]')
    boxes.at(0).trigger('change')
    boxes.at(1).trigger('change')

    let { vm } = wrapper
    await vm.$nextTick()

    expect(vm.selected).to.deep.equal(['11', '22'])

    wrapper.destroy()
  })

  it('should not fire change event if selected value is not changed.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'haha',
                field2: 11
              },
              {
                field1: 'heihei',
                field2: 22
              },
              {
                field1: 'heihei111',
                field2: 33
              },
              {
                field1: 'heihei1112333',
                field2: 44
              }
            ],
            selected: [],
            counter: 0
          }
        },
        methods: {
          handleSelected () {
            this.counter++
          }
        },
        template: `
        <veui-table :data="data" selectable :selected.sync="selected" @update:selected="handleSelected">
          <veui-table-column field="field1"></veui-table-column>
        </veui-table>`
      },
      {
        sync: false
      }
    )

    let { vm } = wrapper
    let boxes = wrapper.findAll('td input[type="checkbox"]')

    await vm.$nextTick()
    boxes.at(0).trigger('change')

    await vm.$nextTick()
    boxes.at(2).trigger('change')

    await vm.$nextTick()
    expect(vm.counter).to.equal(2)

    vm.data = cloneDeep(vm.data)

    await vm.$nextTick()
    expect(vm.counter).to.equal(2)

    wrapper.destroy()
  })

  it('should emit `select` event before `update:selected` event.', done => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'haha',
                field2: 11
              }
            ],
            isSelectEmitted: false
          }
        },
        methods: {
          handleSelect () {
            this.isSelectEmitted = true
          },
          handleUpdateSelected () {
            expect(this.isSelectEmitted).to.equal(true)

            wrapper.destroy()
            done()
          }
        },
        template: `
        <veui-table :data="data" selectable @select="handleSelect" @update:selected="handleUpdateSelected">
          <veui-table-column field="field1"></veui-table-column>
        </veui-table>`
      },
      {
        sync: false
      }
    )

    wrapper
      .findAll('td input[type="checkbox"]')
      .at(0)
      .trigger('change')
  })

  it('should expand the sub rows correctly.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'apple',
                field2: 11
              },
              {
                field1: 'banana',
                field2: 22,
                children: [
                  {
                    field3: 'red',
                    field4: 222
                  }
                ]
              }
            ]
          }
        },
        template: `
          <veui-table :data="data" expandable>
            <veui-table-column field="field1" title="field1">
              <template slot="sub-row" slot-scope="{ field3 }">{{ field3 }}</template>
            </veui-table-column>
            <veui-table-column field="field2" title="field2">
              <template slot="sub-row" slot-scope="{ field4 }">{{ field4 }}</template>
            </veui-table-column>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    let { vm } = wrapper
    let rows = wrapper.findAll('tbody tr')
    expect(rows.length).to.equal(2)

    wrapper.find('td button').trigger('click')

    await vm.$nextTick()

    let totalRows = wrapper.findAll('tbody tr')
    expect(totalRows.length).to.equal(3)

    wrapper.destroy()
  })

  it('should sort by key field properly.', done => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'apple',
                field2: 11
              },
              {
                field1: 'pineapple',
                field2: 33
              },
              {
                field1: 'banana',
                field2: 22
              }
            ],
            keyField: 'field1',
            order: 'desc',
            orderBy: 'field1'
          }
        },
        methods: {
          handleSort (orderBy, order) {
            expect(order).to.equal('asc')
            expect(orderBy).to.equal('field1')

            wrapper.destroy()
            done()
          }
        },
        template: `
          <veui-table :data="data" :key-field="keyField" :order="order" :order-by="orderBy" @sort="handleSort">
            <veui-table-column field="field1" title="field1" sortable/>
            <veui-table-column field="field2" title="field2"/>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    let active = wrapper.findAll('svg.veui-sorter-active.veui-sorter-icon-desc')
    expect(active.length).to.be.equal(1)

    wrapper.find('svg.veui-sorter').trigger('click')
  })

  it('should filter columns correctly.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'apple',
                field2: 11,
                field3: true
              },
              {
                field1: 'banana',
                field2: 22,
                field3: false
              },
              {
                field1: 'pineapple',
                field2: 33,
                field3: true
              }
            ],
            columns: ['field1']
          }
        },
        template: `
          <veui-table :data="data" :column-filter="columns">
            <veui-table-column field="field1" title="field1"/>
            <veui-table-column field="field2" title="field2"/>
            <veui-table-column field="field3" title="field3"/>
            <template slot="foot">An awesome table foot!</template>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    let { vm } = wrapper
    expect(wrapper.findAll('thead th').length).to.equal(1)

    wrapper.vm.columns.push('field2')

    await vm.$nextTick()
    expect(wrapper.findAll('thead th').length).to.equal(2)

    wrapper.vm.columns.splice(1)

    await vm.$nextTick()
    expect(wrapper.findAll('thead th').length).to.equal(1)

    wrapper.destroy()
  })

  it('should select all and cancel all selection correctly.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'apple',
                field2: 11,
                field3: true
              },
              {
                field1: 'banana',
                field2: 22,
                field3: false
              },
              {
                field1: 'pineapple',
                field2: 33,
                field3: true
              }
            ],
            selected: []
          }
        },
        template: `
          <veui-table :data="data" keys="field2" selectable :selected.sync="selected">
            <veui-table-column field="field1" title="field1"/>
            <veui-table-column field="field2" title="field2" align="left"/>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    wrapper.find('th input[type="checkbox"]').trigger('change')

    let { vm } = wrapper
    await vm.$nextTick()

    expect(vm.selected).to.deep.equal(['11', '22', '33'])

    wrapper.find('th input[type="checkbox"]').trigger('change')

    await vm.$nextTick()
    expect(vm.selected).to.have.lengthOf(0)

    wrapper.destroy()
  })

  it('should select rows correctly when the select mode is single.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'apple',
                field2: 11
              },
              {
                field1: 'banana',
                field2: 22
              },
              {
                field1: 'pineapple',
                field2: 33
              }
            ],
            selected: null
          }
        },
        template: `
          <veui-table :data="data" keys="field2" selectable :selected.sync="selected" select-mode="single">
            <veui-table-column field="field1" title="field1"/>
            <veui-table-column field="field2" title="field2" align="left">
              <template slot="foot">总计</template>
            </veui-table-column>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    let { vm } = wrapper
    let list = wrapper.findAll('td input[type="radio"]')

    list.at(0).element.checked = true
    list.at(0).trigger('change')

    await vm.$nextTick()

    expect(wrapper.vm.selected).to.equal('11')

    list.at(1).element.checked = true
    list.at(1).trigger('change')

    await vm.$nextTick()

    expect(wrapper.vm.selected).to.equal('22')

    wrapper.destroy()
  })

  it('should present correctly when data is not provided.', () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        template: `
          <veui-table>
            <veui-table-column field="field1" title="field1"/>
            <veui-table-column field="field2" title="field2"/>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    expect(wrapper.findAll('tbody tr').length).to.equal(1)
    expect(wrapper.findAll('td.veui-table-no-data').length).to.equal(1)

    wrapper.destroy()
  })

  it('should merge cells correctly.', () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                id: 1,
                type: 'fruits',
                name: 'apple'
              },
              {
                id: 2,
                type: 'fruits',
                name: 'cherry'
              },
              {
                id: 3,
                type: 'veggie',
                name: 'tomato'
              },
              {
                id: 4,
                type: 'veggie',
                name: 'potato'
              }
            ],
            groupSpan (i) {
              return {
                row: i % 2 ? 0 : 2
              }
            }
          }
        },
        template: `
          <veui-table :data="data">
            <veui-table-column field="id" title="id"/>
            <veui-table-column field="type" title="type" :span="groupSpan"/>
            <veui-table-column field="name" title="name"/>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    expect(
      wrapper
        .findAll('tbody td')
        .at(1)
        .attributes('rowspan')
    ).to.equal('2')
    wrapper.destroy()
  })
})
<commit_msg>test: add test case for wrapping table column<commit_after>import { mount } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import Table from '@/components/Table'
import Column from '@/components/Table/Column'

describe('components/Table', () => {
  it('should select the specified fields.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'haha',
                field2: 11
              },
              {
                field1: 'heihei',
                field2: 22
              },
              {
                field1: 'heihei111',
                field2: 33
              },
              {
                field1: 'heihei1112333',
                field2: 44
              }
            ],
            selected: []
          }
        },
        template: `
        <veui-table :data="data" keys="field2" selectable :selected.sync="selected">
          <veui-table-column field="field1"></veui-table-column>
        </veui-table>`
      },
      {
        sync: false
      }
    )

    let boxes = wrapper.findAll('td input[type="checkbox"]')
    boxes.at(0).trigger('change')
    boxes.at(1).trigger('change')

    let { vm } = wrapper
    await vm.$nextTick()

    expect(vm.selected).to.deep.equal(['11', '22'])

    wrapper.destroy()
  })

  it('should not fire change event if selected value is not changed.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'haha',
                field2: 11
              },
              {
                field1: 'heihei',
                field2: 22
              },
              {
                field1: 'heihei111',
                field2: 33
              },
              {
                field1: 'heihei1112333',
                field2: 44
              }
            ],
            selected: [],
            counter: 0
          }
        },
        methods: {
          handleSelected () {
            this.counter++
          }
        },
        template: `
        <veui-table :data="data" selectable :selected.sync="selected" @update:selected="handleSelected">
          <veui-table-column field="field1"></veui-table-column>
        </veui-table>`
      },
      {
        sync: false
      }
    )

    let { vm } = wrapper
    let boxes = wrapper.findAll('td input[type="checkbox"]')

    await vm.$nextTick()
    boxes.at(0).trigger('change')

    await vm.$nextTick()
    boxes.at(2).trigger('change')

    await vm.$nextTick()
    expect(vm.counter).to.equal(2)

    vm.data = cloneDeep(vm.data)

    await vm.$nextTick()
    expect(vm.counter).to.equal(2)

    wrapper.destroy()
  })

  it('should emit `select` event before `update:selected` event.', done => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'haha',
                field2: 11
              }
            ],
            isSelectEmitted: false
          }
        },
        methods: {
          handleSelect () {
            this.isSelectEmitted = true
          },
          handleUpdateSelected () {
            expect(this.isSelectEmitted).to.equal(true)

            wrapper.destroy()
            done()
          }
        },
        template: `
        <veui-table :data="data" selectable @select="handleSelect" @update:selected="handleUpdateSelected">
          <veui-table-column field="field1"></veui-table-column>
        </veui-table>`
      },
      {
        sync: false
      }
    )

    wrapper
      .findAll('td input[type="checkbox"]')
      .at(0)
      .trigger('change')
  })

  it('should expand the sub rows correctly.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'apple',
                field2: 11
              },
              {
                field1: 'banana',
                field2: 22,
                children: [
                  {
                    field3: 'red',
                    field4: 222
                  }
                ]
              }
            ]
          }
        },
        template: `
          <veui-table :data="data" expandable>
            <veui-table-column field="field1" title="field1">
              <template slot="sub-row" slot-scope="{ field3 }">{{ field3 }}</template>
            </veui-table-column>
            <veui-table-column field="field2" title="field2">
              <template slot="sub-row" slot-scope="{ field4 }">{{ field4 }}</template>
            </veui-table-column>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    let { vm } = wrapper
    let rows = wrapper.findAll('tbody tr')
    expect(rows.length).to.equal(2)

    wrapper.find('td button').trigger('click')

    await vm.$nextTick()

    let totalRows = wrapper.findAll('tbody tr')
    expect(totalRows.length).to.equal(3)

    wrapper.destroy()
  })

  it('should sort by key field properly.', done => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'apple',
                field2: 11
              },
              {
                field1: 'pineapple',
                field2: 33
              },
              {
                field1: 'banana',
                field2: 22
              }
            ],
            keyField: 'field1',
            order: 'desc',
            orderBy: 'field1'
          }
        },
        methods: {
          handleSort (orderBy, order) {
            expect(order).to.equal('asc')
            expect(orderBy).to.equal('field1')

            wrapper.destroy()
            done()
          }
        },
        template: `
          <veui-table :data="data" :key-field="keyField" :order="order" :order-by="orderBy" @sort="handleSort">
            <veui-table-column field="field1" title="field1" sortable/>
            <veui-table-column field="field2" title="field2"/>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    let active = wrapper.findAll('svg.veui-sorter-active.veui-sorter-icon-desc')
    expect(active.length).to.be.equal(1)

    wrapper.find('svg.veui-sorter').trigger('click')
  })

  it('should filter columns correctly.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'apple',
                field2: 11,
                field3: true
              },
              {
                field1: 'banana',
                field2: 22,
                field3: false
              },
              {
                field1: 'pineapple',
                field2: 33,
                field3: true
              }
            ],
            columns: ['field1']
          }
        },
        template: `
          <veui-table :data="data" :column-filter="columns">
            <veui-table-column field="field1" title="field1"/>
            <veui-table-column field="field2" title="field2"/>
            <veui-table-column field="field3" title="field3"/>
            <template slot="foot">An awesome table foot!</template>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    let { vm } = wrapper
    expect(wrapper.findAll('thead th').length).to.equal(1)

    wrapper.vm.columns.push('field2')

    await vm.$nextTick()
    expect(wrapper.findAll('thead th').length).to.equal(2)

    wrapper.vm.columns.splice(1)

    await vm.$nextTick()
    expect(wrapper.findAll('thead th').length).to.equal(1)

    wrapper.destroy()
  })

  it('should select all and cancel all selection correctly.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'apple',
                field2: 11,
                field3: true
              },
              {
                field1: 'banana',
                field2: 22,
                field3: false
              },
              {
                field1: 'pineapple',
                field2: 33,
                field3: true
              }
            ],
            selected: []
          }
        },
        template: `
          <veui-table :data="data" keys="field2" selectable :selected.sync="selected">
            <veui-table-column field="field1" title="field1"/>
            <veui-table-column field="field2" title="field2" align="left"/>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    wrapper.find('th input[type="checkbox"]').trigger('change')

    let { vm } = wrapper
    await vm.$nextTick()

    expect(vm.selected).to.deep.equal(['11', '22', '33'])

    wrapper.find('th input[type="checkbox"]').trigger('change')

    await vm.$nextTick()
    expect(vm.selected).to.have.lengthOf(0)

    wrapper.destroy()
  })

  it('should select rows correctly when the select mode is single.', async () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                field1: 'apple',
                field2: 11
              },
              {
                field1: 'banana',
                field2: 22
              },
              {
                field1: 'pineapple',
                field2: 33
              }
            ],
            selected: null
          }
        },
        template: `
          <veui-table :data="data" keys="field2" selectable :selected.sync="selected" select-mode="single">
            <veui-table-column field="field1" title="field1"/>
            <veui-table-column field="field2" title="field2" align="left">
              <template slot="foot">总计</template>
            </veui-table-column>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    let { vm } = wrapper
    let list = wrapper.findAll('td input[type="radio"]')

    list.at(0).element.checked = true
    list.at(0).trigger('change')

    await vm.$nextTick()

    expect(wrapper.vm.selected).to.equal('11')

    list.at(1).element.checked = true
    list.at(1).trigger('change')

    await vm.$nextTick()

    expect(wrapper.vm.selected).to.equal('22')

    wrapper.destroy()
  })

  it('should present correctly when data is not provided.', () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        template: `
          <veui-table>
            <veui-table-column field="field1" title="field1"/>
            <veui-table-column field="field2" title="field2"/>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    expect(wrapper.findAll('tbody tr').length).to.equal(1)
    expect(wrapper.findAll('td.veui-table-no-data').length).to.equal(1)

    wrapper.destroy()
  })

  it('should merge cells correctly.', () => {
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column
        },
        data () {
          return {
            data: [
              {
                id: 1,
                type: 'fruits',
                name: 'apple'
              },
              {
                id: 2,
                type: 'fruits',
                name: 'cherry'
              },
              {
                id: 3,
                type: 'veggie',
                name: 'tomato'
              },
              {
                id: 4,
                type: 'veggie',
                name: 'potato'
              }
            ],
            groupSpan (i) {
              return {
                row: i % 2 ? 0 : 2
              }
            }
          }
        },
        template: `
          <veui-table :data="data">
            <veui-table-column field="id" title="id"/>
            <veui-table-column field="type" title="type" :span="groupSpan"/>
            <veui-table-column field="name" title="name"/>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    expect(
      wrapper
        .findAll('tbody td')
        .at(1)
        .attributes('rowspan')
    ).to.equal('2')
    wrapper.destroy()
  })

  it('should support wrapping column component', () => {
    let AwesomeColumn = {
      components: {
        'veui-table-column': Column
      },
      props: {
        field: String,
        title: String
      },
      template: `
        <veui-table-column :field="field" :title="title" align="center">
          <template slot-scope="{ name }">
            <b>{{ name }}</b>
          </template>
        </veui-table-column>`
    }
    let wrapper = mount(
      {
        components: {
          'veui-table': Table,
          'veui-table-column': Column,
          AwesomeColumn
        },
        data () {
          return {
            data: [
              {
                id: 1,
                type: 'fruits',
                name: 'apple'
              },
              {
                id: 2,
                type: 'fruits',
                name: 'cherry'
              },
              {
                id: 3,
                type: 'veggie',
                name: 'tomato'
              },
              {
                id: 4,
                type: 'veggie',
                name: 'potato'
              }
            ],
            groupSpan (i) {
              return {
                row: i % 2 ? 0 : 2
              }
            }
          }
        },
        template: `
          <veui-table :data="data">
            <veui-table-column field="id" title="id"/>
            <awesome-column field="type" title="type"/>
            <veui-table-column field="name" title="name"/>
          </veui-table>
        `
      },
      {
        sync: false
      }
    )

    let td = wrapper.findAll('tbody td').at(1)
    expect(td.classes()).to.include('veui-table-column-center')
    expect(td.find('.veui-table-cell').html()).to.include('<b>apple</b>')

    wrapper.destroy()
  })
})
<|endoftext|>


<commit_before>/**
 * @copyright Copyright (c) 2019 Marco Ambrosini <marcoambrosini@pm.me>
 *
 * @author Marco Ambrosini <marcoambrosini@pm.me>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */
import Vue from 'vue'

const state = {
	conversations: {
	},
	conversationsNames: {
	}
}

const getters = {
	conversations: state => state.conversations,
	conversationsList: state => Object.values(state.conversations)
}

const mutations = {
	/**
	 * Adds a conversation to the store.
	 *
	 * @param {object} state current store state;
	 * @param {object} conversation the conversation;
	 */
	addConversation(state, conversation) {
		Vue.set(state.conversations, conversation.id, conversation)
	},
	/**
	 * Creates a key-value pair with conversation id and name
	 * respectively.
	 *
	 * @param {object} state current state object;
	 * @param {object} object destructuring object;
	 * @param {int} object.id conversation id;
	 * @param {string} object.displayName conversation name;
	 */
	indexConversationName(state, { id, displayName }) {
		Vue.set(state.conversationsNames, id, displayName)
	},
	/**
	 * Deletes a conversation from the store.
	 * @param {object} state current store state;
	 * @param {object} conversation the message;
	 */
	deleteConversation(state, conversation) {
		Vue.delete(state.conversations, conversation.id)
		Vue.delete(state.conversationsNames, conversation.id)
	}
}

const actions = {
	/**
	 * Add a conversation to the store and index the displayname.
	 *
	 * @param {object} context default store context;
	 * @param {object} conversation the conversation;
	 */
	addConversation(context, conversation) {
		context.commit('addConversation', conversation)
		context.commit('indexConversationName', conversation)
	},

	/**
	 * Delete a object
	 *
	 * @param {object} context default store context;
	 * @param {object} conversation the conversation to be deleted;
	 */
	deleteConversation(context, conversation) {
		context.commit('deleteConversation', conversation)
	}
}

export default { state, mutations, getters, actions }
<commit_msg>Move to token based identifiers<commit_after>/**
 * @copyright Copyright (c) 2019 Marco Ambrosini <marcoambrosini@pm.me>
 *
 * @author Marco Ambrosini <marcoambrosini@pm.me>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */
import Vue from 'vue'

const state = {
	conversations: {
	},
	conversationsNames: {
	}
}

const getters = {
	conversations: state => state.conversations,
	conversationsList: state => Object.values(state.conversations)
}

const mutations = {
	/**
	 * Adds a conversation to the store.
	 *
	 * @param {object} state current store state;
	 * @param {object} conversation the conversation;
	 */
	addConversation(state, conversation) {
		Vue.set(state.conversations, conversation.token, conversation)
	},
	/**
	 * Creates a key-value pair with conversation id and name
	 * respectively.
	 *
	 * @param {object} state current state object;
	 * @param {object} object destructuring object;
	 * @param {string} object.token conversation token;
	 * @param {string} object.displayName conversation name;
	 */
	indexConversationName(state, { token, displayName }) {
		Vue.set(state.conversationsNames, token, displayName)
	},
	/**
	 * Deletes a conversation from the store.
	 * @param {object} state current store state;
	 * @param {object} conversation the message;
	 */
	deleteConversation(state, conversation) {
		Vue.delete(state.conversations, conversation.token)
		Vue.delete(state.conversationsNames, conversation.token)
	}
}

const actions = {
	/**
	 * Add a conversation to the store and index the displayname.
	 *
	 * @param {object} context default store context;
	 * @param {object} conversation the conversation;
	 */
	addConversation(context, conversation) {
		context.commit('addConversation', conversation)
		context.commit('indexConversationName', conversation)
	},

	/**
	 * Delete a object
	 *
	 * @param {object} context default store context;
	 * @param {object} conversation the conversation to be deleted;
	 */
	deleteConversation(context, conversation) {
		context.commit('deleteConversation', conversation)
	}
}

export default { state, mutations, getters, actions }
<|endoftext|>


<commit_before>import { createLogic } from 'redux-logic';
import { mapRefocus } from 'fm3/actions/mapActions';
import { startProgress, stopProgress } from 'fm3/actions/mainActions';
import { routePlannerSetResult } from 'fm3/actions/routePlannerActions';
import { getMapLeafletElement } from 'fm3/leafletElementHolder';

export const findRouteLogic = createLogic({
  type: [
    'ROUTE_PLANNER_SET_START',
    'ROUTE_PLANNER_SET_FINISH',
    'ROUTE_PLANNER_ADD_MIDPOINT',
    'ROUTE_PLANNER_SET_MIDPOINT',
    'ROUTE_PLANNER_REMOVE_MIDPOINT',
    'ROUTE_PLANNER_SET_TRANSPORT_TYPE',
  ],
  process({ getState }, dispatch, done) {
    const { start, finish, midpoints, transportType } = getState().routePlanner;
    if (!start || !finish) {
      done();
      return;
    }

    const allPoints = [
      [start.lat, start.lon].join(','),
      ...midpoints.map(mp => [mp.lat, mp.lon].join(',')),
      [finish.lat, finish.lon].join(','),
    ].join(',');

    dispatch(startProgress());
    const url = `//www.freemap.sk/api/0.3/route-planner/${allPoints}?transport_type=${transportType}`;
    fetch(url)
      .then(res => res.json())
      .then(({ route: { properties: { distance_in_km, time_in_minutes, itinerary }, geometry: { coordinates } } }) => {
        const routeLatLons = coordinates.map(lonlat => lonlat.reverse());
        const betterItinerary = itinerary.map(step => ({ lat: step.point[1], lon: step.point[0], desc: step.desc, km: step.distance_from_start_in_km }));
        dispatch(routePlannerSetResult(routeLatLons, betterItinerary, distance_in_km, time_in_minutes));
      })
      .catch(() => {
        // TODO display toast with error
      })
      .then(() => {
        dispatch(stopProgress());
        done();
      });
  },
});

export const refocusMapOnSetStartOrFinishPoint = createLogic({
  type: [
    'ROUTE_PLANNER_SET_START',
    'ROUTE_PLANNER_SET_FINISH',
  ],
  process({ getState, action }, dispatch) {
    const { routePlanner: { start, finish } } = getState();
    let focusPoint;
    if (action.type === 'ROUTE_PLANNER_SET_START') {
      focusPoint = start;
    } else if (action.type === 'ROUTE_PLANNER_SET_FINISH') {
      focusPoint = finish;
    }

    if (!getMapLeafletElement().getBounds().contains(L.latLng(focusPoint.lat, focusPoint.lon))) {
      dispatch(mapRefocus({ lat: focusPoint.lat, lon: focusPoint.lon }));
    }
  },
});

export default [
  findRouteLogic,
  refocusMapOnSetStartOrFinishPoint,
];
<commit_msg>updated logic lifecycle<commit_after>import { createLogic } from 'redux-logic';
import { mapRefocus } from 'fm3/actions/mapActions';
import { startProgress, stopProgress } from 'fm3/actions/mainActions';
import { routePlannerSetResult } from 'fm3/actions/routePlannerActions';
import { getMapLeafletElement } from 'fm3/leafletElementHolder';

export const findRouteLogic = createLogic({
  type: [
    'ROUTE_PLANNER_SET_START',
    'ROUTE_PLANNER_SET_FINISH',
    'ROUTE_PLANNER_ADD_MIDPOINT',
    'ROUTE_PLANNER_SET_MIDPOINT',
    'ROUTE_PLANNER_REMOVE_MIDPOINT',
    'ROUTE_PLANNER_SET_TRANSPORT_TYPE',
  ],
  process({ getState }, dispatch, done) {
    const { start, finish, midpoints, transportType } = getState().routePlanner;
    if (!start || !finish) {
      done();
      return;
    }

    const allPoints = [
      [start.lat, start.lon].join(','),
      ...midpoints.map(mp => [mp.lat, mp.lon].join(',')),
      [finish.lat, finish.lon].join(','),
    ].join(',');

    dispatch(startProgress());
    const url = `//www.freemap.sk/api/0.3/route-planner/${allPoints}?transport_type=${transportType}`;
    fetch(url)
      .then(res => res.json())
      .then(({ route: { properties: { distance_in_km, time_in_minutes, itinerary }, geometry: { coordinates } } }) => {
        const routeLatLons = coordinates.map(lonlat => lonlat.reverse());
        const betterItinerary = itinerary.map(step => ({ lat: step.point[1], lon: step.point[0], desc: step.desc, km: step.distance_from_start_in_km }));
        dispatch(routePlannerSetResult(routeLatLons, betterItinerary, distance_in_km, time_in_minutes));
      })
      .catch(() => {
        // TODO display toast with error
      })
      .then(() => {
        dispatch(stopProgress());
        done();
      });
  },
});

export const refocusMapOnSetStartOrFinishPoint = createLogic({
  type: [
    'ROUTE_PLANNER_SET_START',
    'ROUTE_PLANNER_SET_FINISH',
  ],
  process({ getState, action }, dispatch, done) {
    const { routePlanner: { start, finish } } = getState();
    let focusPoint;
    if (action.type === 'ROUTE_PLANNER_SET_START') {
      focusPoint = start;
    } else if (action.type === 'ROUTE_PLANNER_SET_FINISH') {
      focusPoint = finish;
    }

    if (!getMapLeafletElement().getBounds().contains(L.latLng(focusPoint.lat, focusPoint.lon))) {
      dispatch(mapRefocus({ lat: focusPoint.lat, lon: focusPoint.lon }));
    }

    done();
  },
});

export default [
  findRouteLogic,
  refocusMapOnSetStartOrFinishPoint,
];
<|endoftext|>