<commit_before>/**
 * Copyright 2013 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @providesModule ReactInputSelection
 */

"use strict";

// It is not safe to read the document.activeElement property in IE if there's
// nothing focused.
function getActiveElement() {
  try {
    return document.activeElement;
  } catch (e) {
  }
}

function isInDocument(node) {
  return document.documentElement.contains(node);
}

/**
 * @ReactInputSelection: React input selection module. Based on Selection.js,
 * but modified to be suitable for react and has a couple of bug fixes (doesn't
 * assume buttons have range selections allowed).
 * Input selection module for React.
 */
var ReactInputSelection = {

  hasSelectionCapabilities: function(elem) {
    return elem && (
      (elem.nodeName === 'INPUT' && elem.type === 'text') ||
      elem.nodeName === 'TEXTAREA' ||
      elem.contentEditable === 'true'
    );
  },

  getSelectionInformation: function() {
    var focusedElem = getActiveElement();
    return {
      focusedElem: focusedElem,
      selectionRange:
          ReactInputSelection.hasSelectionCapabilities(focusedElem) ?
          ReactInputSelection.getSelection(focusedElem) :
          null
    };
  },

  /**
   * @restoreSelection: If any selection information was potentially lost,
   * restore it. This is useful when performing operations that could remove dom
   * nodes and place them back in, resulting in focus being lost.
   */
  restoreSelection: function(priorSelectionInformation) {
    var curFocusedElem = getActiveElement();
    var priorFocusedElem = priorSelectionInformation.focusedElem;
    var priorSelectionRange = priorSelectionInformation.selectionRange;
    if (curFocusedElem !== priorFocusedElem &&
        isInDocument(priorFocusedElem)) {
      if (ReactInputSelection.hasSelectionCapabilities(priorFocusedElem)) {
        ReactInputSelection.setSelection(
          priorFocusedElem,
          priorSelectionRange
        );
      }
      priorFocusedElem.focus();
    }
  },

  /**
   * @getSelection: Gets the selection bounds of a textarea or input.
   * -@input: Look up selection bounds of this input or textarea
   * -@return {start: selectionStart, end: selectionEnd}
   */
  getSelection: function(input) {
    var range;
    if (input.contentEditable === 'true' && window.getSelection) {
      range = window.getSelection().getRangeAt(0);
      var commonAncestor = range.commonAncestorContainer;
      if (commonAncestor && commonAncestor.nodeType === 3) {
        commonAncestor = commonAncestor.parentNode;
      }
      if (commonAncestor !== input) {
        return {start: 0, end: 0};
      } else {
        return {start: range.startOffset, end: range.endOffset};
      }
    }

    if (!document.selection) {
      // Mozilla, Safari, etc.
      return {start: input.selectionStart, end: input.selectionEnd};
    }

    range = document.selection.createRange();
    if (range.parentElement() !== input) {
      // There can only be one selection per document in IE, so if the
      // containing element of the document's selection isn't our text field,
      // our text field must have no selection.
      return {start: 0, end: 0};
    }

    var length = input.value.length;

    if (input.nodeName === 'INPUT') {
      return {
        start: -range.moveStart('character', -length),
        end: -range.moveEnd('character', -length)
      };
    } else {
      var range2 = range.duplicate();
      range2.moveToElementText(input);
      range2.setEndPoint('StartToEnd', range);
      var end = length - range2.text.length;
      range2.setEndPoint('StartToStart', range);
      return {
        start: length - range2.text.length,
        end:   end
      };
    }
  },

  /**
   * @setSelection: Sets the selection bounds of a textarea or input and focuses
   * the input.
   * -@input     Set selection bounds of this input or textarea
   * -@rangeObj Object of same form that is returned from get*
   */
  setSelection: function(input, rangeObj) {
    var range;
    var start = rangeObj.start;
    var end = rangeObj.end;
    if (typeof end === 'undefined') {
      end = start;
    }
    if (document.selection) {
      // IE is inconsistent about character offsets when it comes to carriage
      // returns, so we need to manually take them into account
      if (input.tagName === 'TEXTAREA') {
        var cr_before =
          (input.value.slice(0, start).match(/\r/g) || []).length;
        var cr_inside =
          (input.value.slice(start, end).match(/\r/g) || []).length;
        start -= cr_before;
        end -= cr_before + cr_inside;
      }
      range = input.createTextRange();
      range.collapse(true);
      range.moveStart('character', start);
      range.moveEnd('character', end - start);
      range.select();
    } else {
      if (input.contentEditable === 'true') {
        if (input.childNodes.length === 1) {
          range = document.createRange();
          range.setStart(input.childNodes[0], start);
          range.setEnd(input.childNodes[0], end);
          var sel = window.getSelection();
          sel.removeAllRanges();
          sel.addRange(range);
        }
      } else {
        input.selectionStart = start;
        input.selectionEnd = Math.min(end, input.value.length);
        input.focus();
      }
    }
  }

};

module.exports = ReactInputSelection;
<commit_msg>Ensure selection range exists<commit_after>/**
 * Copyright 2013 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @providesModule ReactInputSelection
 */

"use strict";

// It is not safe to read the document.activeElement property in IE if there's
// nothing focused.
function getActiveElement() {
  try {
    return document.activeElement;
  } catch (e) {
  }
}

function isInDocument(node) {
  return document.documentElement.contains(node);
}

/**
 * @ReactInputSelection: React input selection module. Based on Selection.js,
 * but modified to be suitable for react and has a couple of bug fixes (doesn't
 * assume buttons have range selections allowed).
 * Input selection module for React.
 */
var ReactInputSelection = {

  hasSelectionCapabilities: function(elem) {
    return elem && (
      (elem.nodeName === 'INPUT' && elem.type === 'text') ||
      elem.nodeName === 'TEXTAREA' ||
      elem.contentEditable === 'true'
    );
  },

  getSelectionInformation: function() {
    var focusedElem = getActiveElement();
    return {
      focusedElem: focusedElem,
      selectionRange:
          ReactInputSelection.hasSelectionCapabilities(focusedElem) ?
          ReactInputSelection.getSelection(focusedElem) :
          null
    };
  },

  /**
   * @restoreSelection: If any selection information was potentially lost,
   * restore it. This is useful when performing operations that could remove dom
   * nodes and place them back in, resulting in focus being lost.
   */
  restoreSelection: function(priorSelectionInformation) {
    var curFocusedElem = getActiveElement();
    var priorFocusedElem = priorSelectionInformation.focusedElem;
    var priorSelectionRange = priorSelectionInformation.selectionRange;
    if (curFocusedElem !== priorFocusedElem &&
        isInDocument(priorFocusedElem)) {
      if (ReactInputSelection.hasSelectionCapabilities(priorFocusedElem)) {
        ReactInputSelection.setSelection(
          priorFocusedElem,
          priorSelectionRange
        );
      }
      priorFocusedElem.focus();
    }
  },

  /**
   * @getSelection: Gets the selection bounds of a textarea or input.
   * -@input: Look up selection bounds of this input or textarea
   * -@return {start: selectionStart, end: selectionEnd}
   */
  getSelection: function(input) {
    var range;
    if (input.contentEditable === 'true' && window.getSelection) {
      var selection = window.getSelection();
      if (selection.rangeCount > 0) {
        range = selection.getRangeAt(0);
        var commonAncestor = range.commonAncestorContainer;
        if (commonAncestor && commonAncestor.nodeType === 3) {
          commonAncestor = commonAncestor.parentNode;
        }
        if (commonAncestor === input) {
          return {start: range.startOffset, end: range.endOffset};
        }
      }
      return {start: 0, end: 0};
    }

    if (!document.selection) {
      // Mozilla, Safari, etc.
      return {start: input.selectionStart, end: input.selectionEnd};
    }

    range = document.selection.createRange();
    if (range.parentElement() !== input) {
      // There can only be one selection per document in IE, so if the
      // containing element of the document's selection isn't our text field,
      // our text field must have no selection.
      return {start: 0, end: 0};
    }

    var length = input.value.length;

    if (input.nodeName === 'INPUT') {
      return {
        start: -range.moveStart('character', -length),
        end: -range.moveEnd('character', -length)
      };
    } else {
      var range2 = range.duplicate();
      range2.moveToElementText(input);
      range2.setEndPoint('StartToEnd', range);
      var end = length - range2.text.length;
      range2.setEndPoint('StartToStart', range);
      return {
        start: length - range2.text.length,
        end:   end
      };
    }
  },

  /**
   * @setSelection: Sets the selection bounds of a textarea or input and focuses
   * the input.
   * -@input     Set selection bounds of this input or textarea
   * -@rangeObj Object of same form that is returned from get*
   */
  setSelection: function(input, rangeObj) {
    var range;
    var start = rangeObj.start;
    var end = rangeObj.end;
    if (typeof end === 'undefined') {
      end = start;
    }
    if (document.selection) {
      // IE is inconsistent about character offsets when it comes to carriage
      // returns, so we need to manually take them into account
      if (input.tagName === 'TEXTAREA') {
        var cr_before =
          (input.value.slice(0, start).match(/\r/g) || []).length;
        var cr_inside =
          (input.value.slice(start, end).match(/\r/g) || []).length;
        start -= cr_before;
        end -= cr_before + cr_inside;
      }
      range = input.createTextRange();
      range.collapse(true);
      range.moveStart('character', start);
      range.moveEnd('character', end - start);
      range.select();
    } else {
      if (input.contentEditable === 'true') {
        if (input.childNodes.length === 1) {
          range = document.createRange();
          range.setStart(input.childNodes[0], start);
          range.setEnd(input.childNodes[0], end);
          var sel = window.getSelection();
          sel.removeAllRanges();
          sel.addRange(range);
        }
      } else {
        input.selectionStart = start;
        input.selectionEnd = Math.min(end, input.value.length);
        input.focus();
      }
    }
  }

};

module.exports = ReactInputSelection;
<|endoftext|>


<commit_before>(function (window) {
    'use strict';

    function AudioManager(soundDescriptors) {
        this.sounds = {};
        this.soundDescriptors = soundDescriptors || {};
        this.mute = false;

        try {
            window.AudioContext = window.AudioContext || window.webkitAudioContext;
            this.audioContext = new window.AudioContext();
        } catch (e) {
            window.alert('API Audio non supportée.');
        }
    }

    AudioManager.prototype.loadSounds = function (soundDescriptors) {

        var promises = soundDescriptors.map(function (soundDescriptor) {
            var sound = this.sounds[soundDescriptor.title];

            if (!sound) {
                this.sounds[soundDescriptor.title] = {
                    title: soundDescriptor.title,
                    url: soundDescriptor.url,
                    initialGain: soundDescriptor.initialGain || 1
                };
                return this.loadSound(this.sounds[soundDescriptor.title]);
            }
            return sound;
        }.bind(this));

        return Promise.all(promises);
    };

    AudioManager.prototype.loadSound = function (sound) {

        return new Promise(function (resolve, reject) {

            if (sound.buffer) {
                resolve(sound);
            } else {
                var request = new XMLHttpRequest();
                request.open('GET', sound.url, true);
                request.responseType = 'arraybuffer';

                request.onload = function () {
                    this.audioContext.decodeAudioData(
                        request.response,
                        function (buffer) {
                            sound.buffer = buffer;
                            resolve(sound);
                        }.bind(this),
                        function (error) {
                            console.error('Fail to decodeAudioData [%s]', sound.url, error);
                            reject(sound);
                        }
                    );
                }.bind(this);

                request.onerror = function (event) {
                    reject(new Error("Erreur XMLHttpRequest", event));
                };

                request.send();
            }

        }.bind(this));
    };

    AudioManager.prototype.load = function () {
        return this.loadSounds(this.soundDescriptors);
    };

    AudioManager.prototype.play = function (sound, loop, loopStart, loopEnd) {
        return new Promise(function (resolve) {
            var source = this.audioContext.createBufferSource();
            sound.source = source;

            source.buffer = sound.buffer;

            // Controle du volume
            var gainNode = this.audioContext.createGain ? this.audioContext.createGain() : this.audioContext.createGainNode();
            source.connect(gainNode);
            gainNode.connect(this.audioContext.destination);
            sound.gainNode = gainNode;
            gainNode.gain.value = this.mute ? -1 : sound.initialGain;

            // Gestion des boucles sonores
            source.loop = loop;
            if (loop) {
                source.loopStart = loopStart;
                source.loopEnd = loopEnd;

                resolve(source);
            }

            source.onended = function () {
                if (!loop) {
                    resolve(source);
                }
            };

            source.connect(this.audioContext.destination);
            source.start(0);
        }.bind(this));
    };

    AudioManager.prototype.muteSound = function (sound) {
        if (sound.source) {
            sound.gainNode.gain.value = -1;
        }
    };
    AudioManager.prototype.resumeSound = function (sound) {
        if (sound.source) {
            sound.gainNode.gain.value = sound.initialGain;
        }
    };

    AudioManager.prototype.stopSound = function (sound) {
        if (sound.source) {
            sound.source.stop(0);
        }
    };

    /*
     * Méthodes de lecture spécifique des sons
     */

    AudioManager.prototype.stageBgm = function () {
        return this.play(this.sounds.stage, true, 0, 0);
    };

    AudioManager.prototype.laser = function () {
        return this.play(this.sounds.laser);
    };

    AudioManager.prototype.boom = function () {
        return this.play(this.sounds.boom);
    };

    AudioManager.prototype.foom = function () {
        return this.play(this.sounds.foom);
    };

    AudioManager.prototype.toogleMute = function () {
        this.mute = !this.mute;
        if (this.mute) {
            this.muteSound(this.sounds.stage);
        } else {
            this.resumeSound(this.sounds.stage);
        }
        return this.mute;
    };

    window.AudioManager = AudioManager;

})(window);
<commit_msg>feat(audio): constructeur permettant de mettre mute directement<commit_after>(function (window) {
    'use strict';

    function AudioManager(soundDescriptors, mute) {
        this.sounds = {};
        this.soundDescriptors = soundDescriptors || {};
        this.mute = mute;

        try {
            window.AudioContext = window.AudioContext || window.webkitAudioContext;
            this.audioContext = new window.AudioContext();
        } catch (e) {
            window.alert('API Audio non supportée.');
        }
    }

    AudioManager.prototype.loadSounds = function (soundDescriptors) {

        var promises = soundDescriptors.map(function (soundDescriptor) {
            var sound = this.sounds[soundDescriptor.title];

            if (!sound) {
                this.sounds[soundDescriptor.title] = {
                    title: soundDescriptor.title,
                    url: soundDescriptor.url,
                    initialGain: soundDescriptor.initialGain || 1
                };
                return this.loadSound(this.sounds[soundDescriptor.title]);
            }
            return sound;
        }.bind(this));

        return Promise.all(promises);
    };

    AudioManager.prototype.loadSound = function (sound) {

        return new Promise(function (resolve, reject) {

            if (sound.buffer) {
                resolve(sound);
            } else {
                var request = new XMLHttpRequest();
                request.open('GET', sound.url, true);
                request.responseType = 'arraybuffer';

                request.onload = function () {
                    this.audioContext.decodeAudioData(
                        request.response,
                        function (buffer) {
                            sound.buffer = buffer;
                            resolve(sound);
                        }.bind(this),
                        function (error) {
                            console.error('Fail to decodeAudioData [%s]', sound.url, error);
                            reject(sound);
                        }
                    );
                }.bind(this);

                request.onerror = function (event) {
                    reject(new Error("Erreur XMLHttpRequest", event));
                };

                request.send();
            }

        }.bind(this));
    };

    AudioManager.prototype.load = function () {
        return this.loadSounds(this.soundDescriptors);
    };

    AudioManager.prototype.play = function (sound, loop, loopStart, loopEnd) {
        return new Promise(function (resolve) {
            var source = this.audioContext.createBufferSource();
            sound.source = source;

            source.buffer = sound.buffer;

            // Controle du volume
            var gainNode = this.audioContext.createGain ? this.audioContext.createGain() : this.audioContext.createGainNode();
            source.connect(gainNode);
            gainNode.connect(this.audioContext.destination);
            sound.gainNode = gainNode;
            gainNode.gain.value = this.mute ? -1 : sound.initialGain;

            // Gestion des boucles sonores
            source.loop = loop;
            if (loop) {
                source.loopStart = loopStart;
                source.loopEnd = loopEnd;

                resolve(source);
            }

            source.onended = function () {
                if (!loop) {
                    resolve(source);
                }
            };

            source.connect(this.audioContext.destination);
            source.start(0);
        }.bind(this));
    };

    AudioManager.prototype.muteSound = function (sound) {
        if (sound.source) {
            sound.gainNode.gain.value = -1;
        }
    };
    AudioManager.prototype.resumeSound = function (sound) {
        if (sound.source) {
            sound.gainNode.gain.value = sound.initialGain;
        }
    };

    AudioManager.prototype.stopSound = function (sound) {
        if (sound.source) {
            sound.source.stop(0);
        }
    };

    /*
     * Méthodes de lecture spécifique des sons
     */

    AudioManager.prototype.stageBgm = function () {
        return this.play(this.sounds.stage, true, 0, 0);
    };

    AudioManager.prototype.laser = function () {
        return this.play(this.sounds.laser);
    };

    AudioManager.prototype.boom = function () {
        return this.play(this.sounds.boom);
    };

    AudioManager.prototype.foom = function () {
        return this.play(this.sounds.foom);
    };

    AudioManager.prototype.toogleMute = function () {
        this.mute = !this.mute;
        if (this.mute) {
            this.muteSound(this.sounds.stage);
        } else {
            this.resumeSound(this.sounds.stage);
        }
        return this.mute;
    };

    window.AudioManager = AudioManager;

})(window);
<|endoftext|>


<commit_before>/**
 * Accumulates render batches for rendering
 * @param {function} [sorting] - An optional function for sorting the collected render batches
 * @property {Array.<RenderBatch>} batches
 * @property {number} count - How many batch array elements will be processed
 * @property {function} _sortMethod - the stored sorting function
 * @constructor
 */
function Tw2BatchAccumulator(sorting)
{
    this.batches = [];
    this.count = 0;
    this._sortMethod = (sorting) ? sorting : undefined;
}

/**
 * Commits a batch to accumulation
 * @param {RenderBatch} batch
 * @prototype
 */
Tw2BatchAccumulator.prototype.Commit = function(batch)
{
    this.batches[this.count++] = batch;
};

/**
 * Clears any accumulated render batches
 * @prototype
 */
Tw2BatchAccumulator.prototype.Clear = function()
{
    this.count = 0;
    this.batches = [];
};

/**
 * Renders the accumulated render batches
 * - If a sorting function has been defined the render batches will be sorted before rendering
 * @param {Tw2Effect} [overrideEffect]
 * @prototype
 */
Tw2BatchAccumulator.prototype.Render = function(overrideEffect)
{
    if (typeof(this._sortMethod) != 'undefined')
    {
        this.batches.sort(this._sortMethod);
    }
    for (var i = 0; i < this.count; ++i)
    {
        if (this.batches[i].renderMode != device.RM_ANY)
        {
            device.SetStandardStates(this.batches[i].renderMode);
        }
        device.perObjectData = this.batches[i].perObjectData;
        this.batches[i].Commit(overrideEffect);
    }
};

/**
 * A standard render batch
 * @property {RenderMode} renderMode
 * @property {Tw2PerObjectData} perObjectData
 * @constructor
 */
function Tw2RenderBatch()
{
    this.renderMode = device.RM_ANY;
    this.perObjectData = null;
}

/**
 * A render batch that uses geometry provided from an external source
 * @property {GeometryProvider} geometryProvider
 * @inherits Tw2RenderBatch
 * @constructor
 */
function Tw2ForwardingRenderBatch()
{
    this.geometryProvider = null;
}

/**
 * Commits the batch for rendering
 * @param {Tw2Effect} [overrideEffect]
 * @prototype
 */
Tw2ForwardingRenderBatch.prototype.Commit = function(overrideEffect)
{
    if (this.geometryProvider)
    {
        this.geometryProvider.Render(this, overrideEffect);
    }
};

Inherit(Tw2ForwardingRenderBatch, Tw2RenderBatch);
<commit_msg>Added Jsdopc @typedef RenderBatch<commit_after>/**
 * Render Batches
 * @typedef {(Tw2RenderBatch|Tw2ForwardingRenderBatch|Tw2GeometryBatch|Tw2GeometryLineBatch|Tw2InstancedMeshBatch|EvePlaneSetBatch)} RenderBatch
 */

/**
 * Accumulates render batches for rendering
 * @param {function} [sorting] - An optional function for sorting the collected render batches
 * @property {Array.<RenderBatch>} batches
 * @property {number} count - How many batch array elements will be processed
 * @property {function} _sortMethod - the stored sorting function
 * @constructor
 */
function Tw2BatchAccumulator(sorting)
{
    this.batches = [];
    this.count = 0;
    this._sortMethod = (sorting) ? sorting : undefined;
}

/**
 * Commits a batch to accumulation
 * @param {RenderBatch} batch
 * @prototype
 */
Tw2BatchAccumulator.prototype.Commit = function(batch)
{
    this.batches[this.count++] = batch;
};

/**
 * Clears any accumulated render batches
 * @prototype
 */
Tw2BatchAccumulator.prototype.Clear = function()
{
    this.count = 0;
    this.batches = [];
};

/**
 * Renders the accumulated render batches
 * - If a sorting function has been defined the render batches will be sorted before rendering
 * @param {Tw2Effect} [overrideEffect]
 * @prototype
 */
Tw2BatchAccumulator.prototype.Render = function(overrideEffect)
{
    if (typeof(this._sortMethod) != 'undefined')
    {
        this.batches.sort(this._sortMethod);
    }
    for (var i = 0; i < this.count; ++i)
    {
        if (this.batches[i].renderMode != device.RM_ANY)
        {
            device.SetStandardStates(this.batches[i].renderMode);
        }
        device.perObjectData = this.batches[i].perObjectData;
        this.batches[i].Commit(overrideEffect);
    }
};

/**
 * A standard render batch
 * @property {RenderMode} renderMode
 * @property {Tw2PerObjectData} perObjectData
 * @constructor
 */
function Tw2RenderBatch()
{
    this.renderMode = device.RM_ANY;
    this.perObjectData = null;
}

/**
 * A render batch that uses geometry provided from an external source
 * @property {GeometryProvider} geometryProvider
 * @inherits Tw2RenderBatch
 * @constructor
 */
function Tw2ForwardingRenderBatch()
{
    this.geometryProvider = null;
}

/**
 * Commits the batch for rendering
 * @param {Tw2Effect} [overrideEffect]
 * @prototype
 */
Tw2ForwardingRenderBatch.prototype.Commit = function(overrideEffect)
{
    if (this.geometryProvider)
    {
        this.geometryProvider.Render(this, overrideEffect);
    }
};

Inherit(Tw2ForwardingRenderBatch, Tw2RenderBatch);
<|endoftext|>


<commit_before>var roleClaimer = {
  /**
   * @param {Creep} creep
   */
  run: function(creep) {
    var target = creep.memory.targetRoom;
    var room = creep.room.name;
    if (!target) {
      var exits = Game.map.describeExits(room);
      for (var dir in exits) {
        target = exits[dir];
        creep.memory.targetRoom = target;
        break;
      }
    }

    if (target && room != target) {
      // not in the right place yet
      creep.say('to ' + target);
      var route = Game.map.findRoute(room, target);
      if (route.length > 0) {
        var exit = creep.pos.findClosestByRange(route[0].exit);
        creep.moveTo(exit, {visualizePathStyle: {stroke: '#ffaa00'}});
      }
    }
    else if (room == target) {
      // we made it! go claim the new controller
      var controller = creep.room.controller;
      if (controller && !controller.my) {
        creep.say('claiming');
        if (creep.claimController(controller) == ERR_NOT_IN_RANGE) {
          creep.moveTo(controller, {visualizePathStyle: {stroke: '#ffaa00'}});
        }
      }
      else {
        // room is claimed, move on to a new room
        creep.say('onwards');
        creep.memory.targetRoom = null;
      }
    }
  },

  shouldExpand: function(thisRoom) {
    if (Game.gcl.level == 1 || thisRoom.controller.level < 5) {
      return false;
    }
    var myControllers = 0;
    for (var name in Game.rooms) {
      var room = Game.rooms[name];
      if (room.controller.my) {
        myControllers += 1;
      }
    }
    return myControllers < Game.gcl.level;
  },

  spawnExpander: function() {
    console.log('Spawning an expander creep');
    spawn.createCreep([WORK,CARRY,CLAIM,MOVE,MOVE,MOVE], undefined, {role: 'claimer'});
  }
};

module.exports = roleClaimer;
<commit_msg>fix two explosions in claimer code<commit_after>var roleClaimer = {
  /**
   * @param {Creep} creep
   */
  run: function(creep) {
    var target = creep.memory.targetRoom;
    var room = creep.room.name;
    if (!target) {
      var exits = Game.map.describeExits(room);
      for (var dir in exits) {
        target = exits[dir];
        creep.memory.targetRoom = target;
        break;
      }
    }

    if (target && room != target) {
      // not in the right place yet
      creep.say('to ' + target);
      var route = Game.map.findRoute(room, target);
      if (route.length > 0) {
        var exit = creep.pos.findClosestByRange(route[0].exit);
        creep.moveTo(exit, {visualizePathStyle: {stroke: '#ffaa00'}});
      }
    }
    else if (room == target) {
      // we made it! go claim the new controller
      var controller = creep.room.controller;
      if (controller && !controller.my) {
        creep.say('claiming');
        if (creep.claimController(controller) == ERR_NOT_IN_RANGE) {
          creep.moveTo(controller, {visualizePathStyle: {stroke: '#ffaa00'}});
        }
      }
      else {
        // room is claimed, move on to a new room
        creep.say('onwards');
        creep.memory.targetRoom = null;
      }
    }
  },

  shouldExpand: function(thisRoom) {
    if (Game.gcl.level == 1 || thisRoom.controller.level < 5) {
      return false;
    }
    var myControllers = 0;
    for (var name in Game.rooms) {
      var room = Game.rooms[name];
      if (room.controller && room.controller.my) {
        myControllers += 1;
      }
    }
    return myControllers < Game.gcl.level;
  },

  spawnExpander: function(spawn) {
    console.log('Spawning an expander creep');
    spawn.createCreep([WORK,CARRY,CLAIM,MOVE,MOVE,MOVE], undefined, {role: 'claimer'});
  }
};

module.exports = roleClaimer;
<|endoftext|>


<commit_before>'use strict';
var defaults = {
	options: {
		optimizationLevel: 3,
		progressive: true,
		interlaced: true
	}
};

/**
 * Ingredients:
 *
 * gulp-flatten
 * https://github.com/armed/gulp-flatten
 *
 * gulp-imagemin
 * https://github.com/sindresorhus/gulp-imagemin
 *
 * gulp-newer
 * https://github.com/tschaub/gulp-newer
 */
function imagesTask(gulp, config, stream, done) {
	// lazy loading required modules.
	var flatten = require('gulp-flatten'),
		imagemin = require('gulp-imagemin'),
		newer = require('gulp-newer');

	if (!stream) {
		stream = gulp.src(config.src.globs, config.src.options);
	}

	if (config.flatten) {
		stream = stream.pipe(flatten());
	}

	stream = stream.pipe(newer(config.dest));

	if (!config.debug) {
		stream = stream.pipe(imagemin(config.options));
	}

	return stream
		.pipe(gulp.dest(config.dest.path, config.dest.options));
}

function imagesTaskV2(gulp, config, stream, done) {
	// lazy loading required modules.
	var contents = require('file-contents'),
		flatten = require('gulp-flatten'),
		imagemin = require('gulp-imagemin'),
		newer = require('gulp-newer'),
		path = require('path'),
		_ = require('lodash');

	var newerOptions;

	// we don't want waste time to read unchanged files.
	if (!stream) {
		stream = gulp.src(config.src.globs, _.defaults({
			read: false
		}, config.src.options));
	}

	// so, must first filter newer files.
	newerOptions = {
		dest: config.dest
	};
	if (config.flatten) {
		newerOptions.map = function(relativePath) {
			console.log('relativePath=' + relativePath);
			return path.basename(relativePath);
		};
	}
	stream = stream.pipe(newer(newerOptions));

	// and then read their contents.
	stream = stream.pipe(contents());

	// and can finally flatten their filenames.
	if (config.flatten) {
		stream = stream.pipe(flatten());
	}

	if (!config.debug) {
		stream = stream.pipe(imagemin(config.options));
	}

	return stream
		.pipe(gulp.dest(config.dest.path, config.dest.options));
}

imagesTask.description = 'Optimize images.';
imagesTask.consumes = ['dest', 'flatten', 'options', 'src'];
imagesTask.defaults = defaults;

module.exports = imagesTaskV2;
<commit_msg>add schema for images<commit_after>'use strict';
var defaults = {
	options: {
		optimizationLevel: 3,
		progressive: true,
		interlaced: true
	}
};

/**
 * Ingredients:
 *
 * gulp-flatten
 * https://github.com/armed/gulp-flatten
 *
 * gulp-imagemin
 * https://github.com/sindresorhus/gulp-imagemin
 *
 * gulp-newer
 * https://github.com/tschaub/gulp-newer
 */
function imagesTask(gulp, config, stream, done) {
	// lazy loading required modules.
	var flatten = require('gulp-flatten'),
		imagemin = require('gulp-imagemin'),
		newer = require('gulp-newer');

	if (!stream) {
		stream = gulp.src(config.src.globs, config.src.options);
	}

	if (config.flatten) {
		stream = stream.pipe(flatten());
	}

	stream = stream.pipe(newer(config.dest));

	if (!config.debug) {
		stream = stream.pipe(imagemin(config.options));
	}

	return stream
		.pipe(gulp.dest(config.dest.path, config.dest.options));
}

function imagesTaskV2(gulp, config, stream, done) {
	// lazy loading required modules.
	var contents = require('file-contents'),
		flatten = require('gulp-flatten'),
		imagemin = require('gulp-imagemin'),
		newer = require('gulp-newer'),
		path = require('path'),
		_ = require('lodash');

	var newerOptions;

	// we don't want waste time to read unchanged files.
	if (!stream) {
		stream = gulp.src(config.src.globs, _.defaults({
			read: false
		}, config.src.options));
	}

	// so, must first filter newer files.
	newerOptions = {
		dest: config.dest
	};
	if (config.flatten) {
		newerOptions.map = function(relativePath) {
			console.log('relativePath=' + relativePath);
			return path.basename(relativePath);
		};
	}
	stream = stream.pipe(newer(newerOptions));

	// and then read their contents.
	stream = stream.pipe(contents());

	// and can finally flatten their filenames.
	if (config.flatten) {
		stream = stream.pipe(flatten());
	}

	if (!config.debug) {
		stream = stream.pipe(imagemin(config.options));
	}

	return stream
		.pipe(gulp.dest(config.dest.path, config.dest.options));
}

imagesTaskV2.displayName = 'images';
imagesTaskV2.description = 'Optimize images.';
imagesTaskV2.consumes = ['dest', 'flatten', 'options', 'src'];
imagesTaskV2.schema = {
	"properties": {
		"src": {
			"description": ""
		},
		"dest": {
			"description": ""
		},
		"flatten": {
			"description": "",
			"type": "boolean",
			"default": false
		},
		"options": {
			"description": "",
			"properties": {
				"optimizationLevel": {
					"description": "",
					"type": "integer",
					"default": 3
				},
				"progressive": {
					"description": "",
					"type": "boolean",
					"default": true
				},
				"interlaced": {
					"description": "",
					"type": "boolean",
					"default": true
				}
			}
		}
	},
	"required": ["src", "dest"]
};

module.exports = imagesTaskV2;
<|endoftext|>


<commit_before>// @flow
import { Lbryio } from 'lbryinc';
import ReactGA from 'react-ga';
import { history } from './store';
// @if TARGET='app'
import Native from 'native';
import ElectronCookies from '@exponent/electron-cookies';
// @endif

const isProduction = process.env.NODE_ENV === 'production';
const devInternalApis = process.env.LBRY_API_URL;
const LBRY_TV_MINUS_PIRATE_BAY_UA_ID = 'UA-60403362-16';
const LBRY_TV_UA_ID = 'UA-60403362-12';
const DESKTOP_UA_ID = 'UA-60403362-13';

// @if TARGET='app'
ElectronCookies.enable({
  origin: 'https://lbry.tv',
});
// @endif

type Analytics = {
  pageView: string => void,
  setUser: Object => void,
  toggle: (boolean, ?boolean) => void,
  apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>,
  apiLogPublish: (ChannelClaim | StreamClaim) => void,
  tagFollowEvent: (string, boolean, string) => void,
  emailProvidedEvent: () => void,
  emailVerifiedEvent: () => void,
  rewardEligibleEvent: () => void,
  startupEvent: () => void,
  readyEvent: number => void,
  openUrlEvent: string => void,
};

type LogPublishParams = {
  uri: string,
  claim_id: string,
  outpoint: string,
  channel_claim_id?: string,
};

let analyticsEnabled: boolean = true;
const analytics: Analytics = {
  pageView: path => {
    if (analyticsEnabled) {
      ReactGA.pageview(path);
    }
  },
  setUser: userId => {
    if (analyticsEnabled && userId) {
      ReactGA.set({
        userId,
      });

      // @if TARGET='app'
      Native.getAppVersionInfo().then(({ localVersion }) => {
        sendGaEvent('Desktop-Version', localVersion);
      });
      // @endif
    }
  },
  toggle: (enabled: boolean): void => {
    // Always collect analytics on lbry.tv
    // @if TARGET='app'
    analyticsEnabled = enabled;
    // @endif
  },
  apiLogView: (uri, outpoint, claimId, timeToStart) => {
    return new Promise((resolve, reject) => {
      if (analyticsEnabled && (isProduction || devInternalApis)) {
        const params: {
          uri: string,
          outpoint: string,
          claim_id: string,
          time_to_start?: number,
        } = {
          uri,
          outpoint,
          claim_id: claimId,
        };

        // lbry.tv streams from AWS so we don't care about the time to start
        if (timeToStart && !IS_WEB) {
          params.time_to_start = timeToStart;
        }

        resolve(Lbryio.call('file', 'view', params));
      } else {
        resolve();
      }
    });
  },
  apiLogSearch: () => {
    if (analyticsEnabled && isProduction) {
      Lbryio.call('event', 'search');
    }
  },
  apiLogPublish: (claimResult: ChannelClaim | StreamClaim) => {
    if (analyticsEnabled && isProduction) {
      const { permanent_url: uri, claim_id: claimId, txid, nout, signing_channel: signingChannel } = claimResult;
      let channelClaimId;
      if (signingChannel) {
        channelClaimId = signingChannel.claim_id;
      }
      const outpoint = `${txid}:${nout}`;
      const params: LogPublishParams = { uri, claim_id: claimId, outpoint };
      if (channelClaimId) {
        params['channel_claim_id'] = channelClaimId;
      }
      Lbryio.call('event', 'publish', params);
    }
  },

  apiSearchFeedback: (query, vote) => {
    if (isProduction) {
      // We don't need to worry about analytics enabled here because users manually click on the button to provide feedback
      Lbryio.call('feedback', 'search', { query, vote });
    }
  },
  tagFollowEvent: (tag, following, location) => {
    sendGaEvent(following ? 'Tag-Follow' : 'Tag-Unfollow', tag);
  },
  channelBlockEvent: (uri, blocked, location) => {
    sendGaEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri);
  },
  emailProvidedEvent: () => {
    sendGaEvent('Engagement', 'Email-Provided');
  },
  emailVerifiedEvent: () => {
    sendGaEvent('Engagement', 'Email-Verified');
  },
  rewardEligibleEvent: () => {
    sendGaEvent('Engagement', 'Reward-Eligible');
  },
  openUrlEvent: (url: string) => {
    sendGaEvent('Engagement', 'Open-Url', url);
  },
  startupEvent: () => {
    sendGaEvent('Startup', 'Startup');
  },
  readyEvent: (timeToReady: number) => {
    sendGaEvent('Startup', 'App-Ready');
    sendGaTimingEvent('Startup', 'App-Ready', timeToReady);
  },
};

function sendGaEvent(category, action, label) {
  if (analyticsEnabled && isProduction) {
    ReactGA.event({
      category,
      action,
      ...(label ? { label } : {}),
    });
  }
}

function sendGaTimingEvent(category: string, action: string, timeInMs: number) {
  if (analyticsEnabled && isProduction) {
    ReactGA.timing({
      category,
      variable: action,
      value: timeInMs,
    });
  }
}

let gaTrackers = [];

if (!IS_WEB) {
  gaTrackers.push({
    trackingId: DESKTOP_UA_ID,
  });
} else {
  gaTrackers.push({
    trackingId: LBRY_TV_UA_ID,
  });

  const { search } = window.location;
  const urlParams = new URLSearchParams(search);
  const isPirateBayUser = urlParams.get('utm_source') === 'PB';

  if (!isPirateBayUser) {
    gaTrackers.push({
      trackingId: LBRY_TV_MINUS_PIRATE_BAY_UA_ID,
    });
  }
}

ReactGA.initialize(gaTrackers, {
  testMode: process.env.NODE_ENV !== 'production',
  cookieDomain: 'auto',
  siteSpeedSampleRate: 100,
  // un-comment to see events as they are sent to google
  // debug: true,
});

// Manually call the first page view
// React Router doesn't include this on `history.listen`
// @if TARGET='web'
analytics.pageView(window.location.pathname + window.location.search);
// @endif

// @if TARGET='app'
ReactGA.set({ checkProtocolTask: null });
ReactGA.set({ location: 'https://lbry.tv' });
analytics.pageView(window.location.pathname.split('.html')[1] + window.location.search || '/');
// @endif;

// Listen for url changes and report
// This will include search queries
history.listen(location => {
  const { pathname, search } = location;

  const page = `${pathname}${search}`;
  analytics.pageView(page);
});

export default analytics;
<commit_msg>fix for lbrytv analytics minus pb<commit_after>// @flow
import { Lbryio } from 'lbryinc';
import ReactGA from 'react-ga';
import { history } from './store';
// @if TARGET='app'
import Native from 'native';
import ElectronCookies from '@exponent/electron-cookies';
// @endif

const isProduction = process.env.NODE_ENV === 'production';
const devInternalApis = process.env.LBRY_API_URL;
const LBRY_TV_MINUS_PIRATE_BAY_UA_ID = 'UA-60403362-16';
const LBRY_TV_UA_ID = 'UA-60403362-12';
const DESKTOP_UA_ID = 'UA-60403362-13';
const SECOND_TRACKER_NAME = 'tracker2';

// @if TARGET='app'
ElectronCookies.enable({
  origin: 'https://lbry.tv',
});
// @endif

type Analytics = {
  pageView: string => void,
  setUser: Object => void,
  toggle: (boolean, ?boolean) => void,
  apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>,
  apiLogPublish: (ChannelClaim | StreamClaim) => void,
  tagFollowEvent: (string, boolean, string) => void,
  emailProvidedEvent: () => void,
  emailVerifiedEvent: () => void,
  rewardEligibleEvent: () => void,
  startupEvent: () => void,
  readyEvent: number => void,
  openUrlEvent: string => void,
};

type LogPublishParams = {
  uri: string,
  claim_id: string,
  outpoint: string,
  channel_claim_id?: string,
};

let analyticsEnabled: boolean = true;
const analytics: Analytics = {
  pageView: path => {
    if (analyticsEnabled) {
      ReactGA.pageview(path, [SECOND_TRACKER_NAME]);
    }
  },
  setUser: userId => {
    if (analyticsEnabled && userId) {
      ReactGA.set({
        userId,
      });

      // @if TARGET='app'
      Native.getAppVersionInfo().then(({ localVersion }) => {
        sendGaEvent('Desktop-Version', localVersion);
      });
      // @endif
    }
  },
  toggle: (enabled: boolean): void => {
    // Always collect analytics on lbry.tv
    // @if TARGET='app'
    analyticsEnabled = enabled;
    // @endif
  },
  apiLogView: (uri, outpoint, claimId, timeToStart) => {
    return new Promise((resolve, reject) => {
      if (analyticsEnabled && (isProduction || devInternalApis)) {
        const params: {
          uri: string,
          outpoint: string,
          claim_id: string,
          time_to_start?: number,
        } = {
          uri,
          outpoint,
          claim_id: claimId,
        };

        // lbry.tv streams from AWS so we don't care about the time to start
        if (timeToStart && !IS_WEB) {
          params.time_to_start = timeToStart;
        }

        resolve(Lbryio.call('file', 'view', params));
      } else {
        resolve();
      }
    });
  },
  apiLogSearch: () => {
    if (analyticsEnabled && isProduction) {
      Lbryio.call('event', 'search');
    }
  },
  apiLogPublish: (claimResult: ChannelClaim | StreamClaim) => {
    if (analyticsEnabled && isProduction) {
      const { permanent_url: uri, claim_id: claimId, txid, nout, signing_channel: signingChannel } = claimResult;
      let channelClaimId;
      if (signingChannel) {
        channelClaimId = signingChannel.claim_id;
      }
      const outpoint = `${txid}:${nout}`;
      const params: LogPublishParams = { uri, claim_id: claimId, outpoint };
      if (channelClaimId) {
        params['channel_claim_id'] = channelClaimId;
      }
      Lbryio.call('event', 'publish', params);
    }
  },

  apiSearchFeedback: (query, vote) => {
    if (isProduction) {
      // We don't need to worry about analytics enabled here because users manually click on the button to provide feedback
      Lbryio.call('feedback', 'search', { query, vote });
    }
  },
  tagFollowEvent: (tag, following, location) => {
    sendGaEvent(following ? 'Tag-Follow' : 'Tag-Unfollow', tag);
  },
  channelBlockEvent: (uri, blocked, location) => {
    sendGaEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri);
  },
  emailProvidedEvent: () => {
    sendGaEvent('Engagement', 'Email-Provided');
  },
  emailVerifiedEvent: () => {
    sendGaEvent('Engagement', 'Email-Verified');
  },
  rewardEligibleEvent: () => {
    sendGaEvent('Engagement', 'Reward-Eligible');
  },
  openUrlEvent: (url: string) => {
    sendGaEvent('Engagement', 'Open-Url', url);
  },
  startupEvent: () => {
    sendGaEvent('Startup', 'Startup');
  },
  readyEvent: (timeToReady: number) => {
    sendGaEvent('Startup', 'App-Ready');
    sendGaTimingEvent('Startup', 'App-Ready', timeToReady);
  },
};

function sendGaEvent(category, action, label) {
  if (analyticsEnabled && isProduction) {
    ReactGA.event(
      {
        category,
        action,
        ...(label ? { label } : {}),
      },
      [SECOND_TRACKER_NAME]
    );
  }
}

function sendGaTimingEvent(category: string, action: string, timeInMs: number) {
  if (analyticsEnabled && isProduction) {
    ReactGA.timing(
      {
        category,
        variable: action,
        value: timeInMs,
      },
      [SECOND_TRACKER_NAME]
    );
  }
}

let gaTrackers = [];

if (!IS_WEB) {
  gaTrackers.push({
    trackingId: DESKTOP_UA_ID,
  });
} else {
  gaTrackers.push({
    trackingId: LBRY_TV_UA_ID,
  });

  const { search } = window.location;
  const urlParams = new URLSearchParams(search);
  const isPirateBayUser = urlParams.get('utm_source') === 'PB';

  if (!isPirateBayUser) {
    gaTrackers.push({
      trackingId: LBRY_TV_MINUS_PIRATE_BAY_UA_ID,
      gaOptions: {
        name: SECOND_TRACKER_NAME,
      },
    });
  }
}

ReactGA.initialize(gaTrackers, {
  testMode: process.env.NODE_ENV !== 'production',
  cookieDomain: 'auto',
  siteSpeedSampleRate: 100,
  // un-comment to see events as they are sent to google
  // debug: true,
});

// Manually call the first page view
// React Router doesn't include this on `history.listen`
// @if TARGET='web'
analytics.pageView(window.location.pathname + window.location.search);
// @endif

// @if TARGET='app'
ReactGA.set({ checkProtocolTask: null });
ReactGA.set({ location: 'https://lbry.tv' });
analytics.pageView(window.location.pathname.split('.html')[1] + window.location.search || '/');
// @endif;

// Listen for url changes and report
// This will include search queries
history.listen(location => {
  const { pathname, search } = location;

  const page = `${pathname}${search}`;
  analytics.pageView(page);
});

export default analytics;
<|endoftext|>


<commit_before>import _ from 'underscore';

class Mover {
  constructor(positionCalculator) {
    this.positionCalculator = positionCalculator;
  }

  move(rowRepository, registry, draggedItem, x, y) {
    const fromColumnId = draggedItem.columnId();
    const columns = rowRepository.columns();
    const columnAtPosition = this.positionCalculator.columnAtPosition(columns, x, y);
    if (!columnAtPosition) {
      return;
    }

    const toColumnId = columnAtPosition.id();
    let items = rowRepository.visibleItems(toColumnId);
    const itemAtPosition = this.positionCalculator.itemAtPosition(items, toColumnId, x, y, draggedItem);
    if (!itemAtPosition) {
      return columnAtPosition;
    }

    const draggedId = draggedItem.id();
    const itemAtPositionId = itemAtPosition.id();

    if (draggedItem.id() == itemAtPosition.id()) {
      return columnAtPosition;
    }

    if (toColumnId != fromColumnId) {
      this.moveToOtherColumn(rowRepository, registry, fromColumnId, toColumnId, draggedItem);
    }

    this.switchItemsBetween(rowRepository, draggedItem, itemAtPosition, toColumnId);

    return columnAtPosition;
  }

  moveToOtherColumn(rowRepository, registry, fromColumnId, toColumnId, item) {
    const fromItems = rowRepository.items(fromColumnId);
    for (const i of _.range(fromItems.length - 1, item.index(), -1)) {
      let fromItem = fromItems[i];
      fromItem.setIndex(fromItem.index() - 1);
      const newY = fromItems[i - 1].layout().y;
      fromItem.setLayout(Object.assign(fromItem.layout(), { y: newY }));
    }
    registry.move(fromColumnId, toColumnId, item);
    rowRepository.notify(fromColumnId, 'reload');

    item.setVisible(true);
    item.setIndex(-1);
    const items = rowRepository.items(toColumnId);
    for (const item of items) {
      item.setIndex(item.index() + 1);
    }

    const visibleItems = rowRepository.visibleItems(toColumnId);
    for (const i of _.range(0, visibleItems.length - 1)) {
      visibleItems[i].setLayout(Object.assign({}, visibleItems[i + 1].layout()));
    }
    const lastItem = visibleItems[visibleItems.length - 1];
    const lastLayout = lastItem.layout();
    const newLastY = lastLayout.y + lastLayout.height;
    lastItem.setLayout(Object.assign(lastLayout, { y: newLastY }));
  }

  switchItemsBetween(rowRepository, draggedItem, itemAtPosition, toColumnId) {
    draggedItem.setVisible(true);

    let items = rowRepository.visibleItems(toColumnId);
    const draggedItemI = _(items).findIndex((item) => item.id() == draggedItem.id());
    const itemAtPositionI = _(items).findIndex((item) => item.id() == itemAtPosition.id());
    let range;
    if (draggedItem.index() < itemAtPosition.index()) {
      range = _.range(draggedItemI, itemAtPositionI);
    } else {
      range = _.range(itemAtPositionI, draggedItemI);
    }

    for (const i of range) {
      const firstItem = items[i];
      const secondItem = items[i + 1];
      this.switchItems(toColumnId, firstItem, secondItem);
      items = rowRepository.visibleItems(toColumnId);
    }
    rowRepository.notify(toColumnId, 'reload');
  }

  switchItems(columnId, firstItem, secondItem) {
    if (!firstItem || !secondItem) {
      return;
    }

    let firstId = firstItem.id();
    let secondId = secondItem.id();
    let firstIndex = firstItem.index();
    let secondIndex = secondItem.index();
    let firstY = firstItem.layout().y;
    let secondHeight = secondItem.layout().height;
    let firstRef = firstItem.ref();
    let secondRef = secondItem.ref();

    firstItem.setIndex(secondIndex);
    secondItem.setIndex(firstIndex);

    firstItem.setLayout(Object.assign(firstItem.layout(), { y: firstY + secondHeight }));
    secondItem.setLayout(Object.assign(secondItem.layout(), { y: firstY }));

    firstItem.setRef(secondRef);
    secondItem.setRef(firstRef);
  }
};

export default Mover;
<commit_msg>Fixed moving item to the column where there are no items<commit_after>import _ from 'underscore';

class Mover {
  constructor(positionCalculator) {
    this.positionCalculator = positionCalculator;
  }

  move(rowRepository, registry, draggedItem, x, y) {
    const fromColumnId = draggedItem.columnId();
    const columns = rowRepository.columns();
    const columnAtPosition = this.positionCalculator.columnAtPosition(columns, x, y);
    if (!columnAtPosition) {
      return;
    }

    const toColumnId = columnAtPosition.id();
    if (toColumnId != fromColumnId) {
      this.moveToOtherColumn(rowRepository, registry, fromColumnId, toColumnId, draggedItem);
    }

    let items = rowRepository.visibleItems(toColumnId);
    const itemAtPosition = this.positionCalculator.itemAtPosition(items, toColumnId, x, y, draggedItem);
    if (!itemAtPosition) {
      return columnAtPosition;
    }

    const draggedId = draggedItem.id();
    const itemAtPositionId = itemAtPosition.id();

    if (draggedItem.id() == itemAtPosition.id()) {
      return columnAtPosition;
    }

    this.switchItemsBetween(rowRepository, draggedItem, itemAtPosition, toColumnId);

    return columnAtPosition;
  }

  moveToOtherColumn(rowRepository, registry, fromColumnId, toColumnId, item) {
    const fromItems = rowRepository.items(fromColumnId);
    for (const i of _.range(fromItems.length - 1, item.index(), -1)) {
      let fromItem = fromItems[i];
      fromItem.setIndex(fromItem.index() - 1);
      const newY = fromItems[i - 1].layout().y;
      fromItem.setLayout(Object.assign(fromItem.layout(), { y: newY }));
    }
    registry.move(fromColumnId, toColumnId, item);
    rowRepository.notify(fromColumnId, 'reload');

    item.setVisible(true);
    item.setIndex(-1);
    const items = rowRepository.items(toColumnId);
    for (const item of items) {
      item.setIndex(item.index() + 1);
    }

    const visibleItems = rowRepository.visibleItems(toColumnId);
    for (const i of _.range(0, visibleItems.length - 1)) {
      visibleItems[i].setLayout(Object.assign({}, visibleItems[i + 1].layout()));
    }
    const lastItem = visibleItems[visibleItems.length - 1];
    const lastLayout = lastItem.layout();
    const newLastY = lastLayout.y + lastLayout.height;
    lastItem.setLayout(Object.assign(lastLayout, { y: newLastY }));
  }

  switchItemsBetween(rowRepository, draggedItem, itemAtPosition, toColumnId) {
    draggedItem.setVisible(true);

    let items = rowRepository.visibleItems(toColumnId);
    const draggedItemI = _(items).findIndex((item) => item.id() == draggedItem.id());
    const itemAtPositionI = _(items).findIndex((item) => item.id() == itemAtPosition.id());
    let range;
    if (draggedItem.index() < itemAtPosition.index()) {
      range = _.range(draggedItemI, itemAtPositionI);
    } else {
      range = _.range(itemAtPositionI, draggedItemI);
    }

    for (const i of range) {
      const firstItem = items[i];
      const secondItem = items[i + 1];
      this.switchItems(toColumnId, firstItem, secondItem);
      items = rowRepository.visibleItems(toColumnId);
    }
    rowRepository.notify(toColumnId, 'reload');
  }

  switchItems(columnId, firstItem, secondItem) {
    if (!firstItem || !secondItem) {
      return;
    }

    let firstId = firstItem.id();
    let secondId = secondItem.id();
    let firstIndex = firstItem.index();
    let secondIndex = secondItem.index();
    let firstY = firstItem.layout().y;
    let secondHeight = secondItem.layout().height;
    let firstRef = firstItem.ref();
    let secondRef = secondItem.ref();

    firstItem.setIndex(secondIndex);
    secondItem.setIndex(firstIndex);

    firstItem.setLayout(Object.assign(firstItem.layout(), { y: firstY + secondHeight }));
    secondItem.setLayout(Object.assign(secondItem.layout(), { y: firstY }));

    firstItem.setRef(secondRef);
    secondItem.setRef(firstRef);
  }
};

export default Mover;
<|endoftext|>


<commit_before>/**
 * boot
 */

const riot = require('riot');
require('velocity-animate');
const api = require('./common/scripts/api');
const signout = require('./common/scripts/signout');
const generateDefaultUserdata = require('./common/scripts/generate-default-userdata');
const mixins = require('./common/mixins');
const checkForUpdate = require('./common/scripts/check-for-update');
require('./common/tags');

/**
 * MISSKEY ENTORY POINT!
 */

"use strict";

const CONFIG = require('./common/scripts/config');

document.domain = CONFIG.host;

// Set global configration
riot.mixin({
	CONFIG
});

// ↓ iOS待ちPolyfill (SEE: http://caniuse.com/#feat=fetch)
require('whatwg-fetch');

// ↓ NodeList、HTMLCollection、FileListで forEach を使えるようにする
if (NodeList.prototype.forEach === undefined) {
	NodeList.prototype.forEach = Array.prototype.forEach;
}
if (HTMLCollection.prototype.forEach === undefined) {
	HTMLCollection.prototype.forEach = Array.prototype.forEach;
}
if (FileList.prototype.forEach === undefined) {
	FileList.prototype.forEach = Array.prototype.forEach;
}

// ↓ iOSでプライベートモードだとlocalStorageが使えないので既存のメソッドを上書きする
try {
	localStorage.setItem('kyoppie', 'yuppie');
} catch (e) {
	Storage.prototype.setItem = () => { }; // noop
}

// Check for Update
checkForUpdate();

// ユーザーをフェッチしてコールバックする
module.exports = callback => {
	// Get cached account data
	let cachedMe = JSON.parse(localStorage.getItem('me'));

	if (cachedMe && cachedMe.data && cachedMe.data.cache) {
		fetched(cachedMe);

		// 後から新鮮なデータをフェッチ
		fetchme(cachedMe.token, freshData => {
			Object.assign(cachedMe, freshData);
			cachedMe.trigger('updated');
		});
	} else {
		// キャッシュ無効なのにキャッシュが残ってたら掃除
		if (cachedMe) {
			localStorage.removeItem('me');
		}

		// Get token from cookie
		const i = (document.cookie.match(/i=(!\w+)/) || [null, null])[1];

		fetchme(i, fetched);
	}

	function fetched(me) {
		if (me) {
			riot.observable(me);

			me.update = data => {
				if (data) Object.assign(me, data);
				me.trigger('updated');
			};

			if (me.data.cache) {
				localStorage.setItem('me', JSON.stringify(me));

				me.on('updated', () => {
					// キャッシュ更新
					localStorage.setItem('me', JSON.stringify(me));
				});
			}
		}

		mixins(me);

		const ini = document.getElementById('ini');
		ini.parentNode.removeChild(ini);

		const app = document.createElement('div');
		app.setAttribute('id', 'app');
		document.body.appendChild(app);

		try {
			callback(me);
		} catch (e) {
			panic(e);
		}
	}
};

// ユーザーをフェッチしてコールバックする
function fetchme(token, cb) {
	let me = null;

	// Return when not signed in
	if (token == null) {
		return done();
	}

	// Fetch user
	fetch(CONFIG.apiUrl + '/i', {
		method: 'POST',
		body: JSON.stringify({
			i: token
		})
	}).then(res => {
		// When failed to authenticate user
		if (res.status !== 200) {
			return signout();
		}

		res.json().then(i => {
			me = i;
			me.token = token;

			// initialize it if user data is empty
			me.data ? done() : init();
		});
	}, () => {
		riot.mount(document.body.appendChild(document.createElement('mk-core-error')), {
			retry: () => {
				fetchme(token, cb);
			}
		});
	});

	function done() {
		if (cb) cb(me);
	}

	function init() {
		const data = generateDefaultUserdata();
		api(token, 'i/appdata/set', {
			data: JSON.stringify(data)
		}).then(() => {
			me.data = data;
			done();
		});
	}
}

function panic(e) {
	console.error(e);
	document.body.innerHTML = '

致命的な問題が発生しました。

';
	// TODO: Report the bug
}
<commit_msg>[Client] Fix bug<commit_after>/**
 * boot
 */

const riot = require('riot');
require('velocity-animate');
const api = require('./common/scripts/api');
const signout = require('./common/scripts/signout');
const generateDefaultUserdata = require('./common/scripts/generate-default-userdata');
const mixins = require('./common/mixins');
const checkForUpdate = require('./common/scripts/check-for-update');
require('./common/tags');

/**
 * MISSKEY ENTORY POINT!
 */

"use strict";

const CONFIG = require('./common/scripts/config');

document.domain = CONFIG.host;

// Set global configration
riot.mixin({
	CONFIG
});

// ↓ iOS待ちPolyfill (SEE: http://caniuse.com/#feat=fetch)
require('whatwg-fetch');

// ↓ NodeList、HTMLCollection、FileList、DataTransferItemListで forEach を使えるようにする
if (NodeList.prototype.forEach === undefined) {
	NodeList.prototype.forEach = Array.prototype.forEach;
}
if (HTMLCollection.prototype.forEach === undefined) {
	HTMLCollection.prototype.forEach = Array.prototype.forEach;
}
if (FileList.prototype.forEach === undefined) {
	FileList.prototype.forEach = Array.prototype.forEach;
}
if (DataTransferItemList.prototype.forEach === undefined) {
	DataTransferItemList.prototype.forEach = Array.prototype.forEach;
}

// ↓ iOSでプライベートモードだとlocalStorageが使えないので既存のメソッドを上書きする
try {
	localStorage.setItem('kyoppie', 'yuppie');
} catch (e) {
	Storage.prototype.setItem = () => { }; // noop
}

// Check for Update
checkForUpdate();

// ユーザーをフェッチしてコールバックする
module.exports = callback => {
	// Get cached account data
	let cachedMe = JSON.parse(localStorage.getItem('me'));

	if (cachedMe && cachedMe.data && cachedMe.data.cache) {
		fetched(cachedMe);

		// 後から新鮮なデータをフェッチ
		fetchme(cachedMe.token, freshData => {
			Object.assign(cachedMe, freshData);
			cachedMe.trigger('updated');
		});
	} else {
		// キャッシュ無効なのにキャッシュが残ってたら掃除
		if (cachedMe) {
			localStorage.removeItem('me');
		}

		// Get token from cookie
		const i = (document.cookie.match(/i=(!\w+)/) || [null, null])[1];

		fetchme(i, fetched);
	}

	function fetched(me) {
		if (me) {
			riot.observable(me);

			me.update = data => {
				if (data) Object.assign(me, data);
				me.trigger('updated');
			};

			if (me.data.cache) {
				localStorage.setItem('me', JSON.stringify(me));

				me.on('updated', () => {
					// キャッシュ更新
					localStorage.setItem('me', JSON.stringify(me));
				});
			}
		}

		mixins(me);

		const ini = document.getElementById('ini');
		ini.parentNode.removeChild(ini);

		const app = document.createElement('div');
		app.setAttribute('id', 'app');
		document.body.appendChild(app);

		try {
			callback(me);
		} catch (e) {
			panic(e);
		}
	}
};

// ユーザーをフェッチしてコールバックする
function fetchme(token, cb) {
	let me = null;

	// Return when not signed in
	if (token == null) {
		return done();
	}

	// Fetch user
	fetch(CONFIG.apiUrl + '/i', {
		method: 'POST',
		body: JSON.stringify({
			i: token
		})
	}).then(res => {
		// When failed to authenticate user
		if (res.status !== 200) {
			return signout();
		}

		res.json().then(i => {
			me = i;
			me.token = token;

			// initialize it if user data is empty
			me.data ? done() : init();
		});
	}, () => {
		riot.mount(document.body.appendChild(document.createElement('mk-core-error')), {
			retry: () => {
				fetchme(token, cb);
			}
		});
	});

	function done() {
		if (cb) cb(me);
	}

	function init() {
		const data = generateDefaultUserdata();
		api(token, 'i/appdata/set', {
			data: JSON.stringify(data)
		}).then(() => {
			me.data = data;
			done();
		});
	}
}

function panic(e) {
	console.error(e);
	document.body.innerHTML = '

致命的な問題が発生しました。

';
	// TODO: Report the bug
}
<|endoftext|>


<commit_before>//@flow

export default [
  "Akkadian",
  "Bainvegni",
  "Batak",
  "Be kher hati",
  "Benvignût",
  "Benvnuwe",
  "Bi xêr bî",
  "Bienbenidu",
  "Bienllegau, -á",
  "Bienvenida",
  "Bienvenius",
  "Boolkhent",
  "Buen binidu",
  "Bula",
  "Bures boahtin",
  "B’a’ntulena",
  "Ceud mìle fàilte",
  "Cimbrian",
  "Eguahé porá",
  "Emedi",
  "Ennidos",
  "Guarani",
  "Horas",
  "Kale ah maruhabaa",
  "Kíimak 'oolal",
  "Mabuhay",
  "Malipayong pag-abot",
  "Maliu mai",
  "Mauri",
  "Mauya",
  "Mayad-ayad nga pad-abot",
  "Mazanderani",
  "Merhbe",
  "Merħba",
  "Mi'kmaq",
  "Mir se vjên",
  "Miro peicak",
  "Mu amuhezwi",
  "Murakaza neza",
  "Märr-ŋamathirri",
  "N bèlantre",
  "Naragsak nga Idadanon",
  "Nau mai",
  "Qaimarutin",
  "Romani",
  "Räxim itegez",
  "Salamat datang",
  "Salve",
  "Siya namkela nonke",
  "Soo dhowow",
  "T'aves baxtalo",
  "Talitali fiefia",
  "Tere tulõmast",
  "Tikilluaritsi",
  "Vælkomin",
  "Walcome",
  "Walloon",
  "Welkam",
  "Weltasualuleg",
  "Wilkomme",
  "Wilkóm",
  "Witajće k nam",
  "Witakśo k nam",
  "Wolkom",
  "Wäljkiimen",
  "Ximopanōltih",
  "Xoş gəlmişsiniz",
  "Zoo siab txais tos",
  "fi'i",
  "nno; dalụ",
  "ǁKhoreǁhare.",
  "लसकुस",
  "ཕེབས་པར་དགའ་བསུ་ཞུ།",
  "Ẹ ku abọ",
]
<commit_msg>Remove something<commit_after>//@flow

/*******************************************************************************
 * Public API
 *******************************************************************************/

export default [
  "Akkadian",
  "Bainvegni",
  "Batak",
  "Be kher hati",
  "Benvignût",
  "Benvnuwe",
  "Bi xêr bî",
  "Bienbenidu",
  "Bienllegau",
  "Bienvenida",
  "Bienvenius",
  "Boolkhent",
  "Buen binidu",
  "Bula",
  "Bures boahtin",
  "B’a’ntulena",
  "Ceud mìle fàilte",
  "Cimbrian",
  "Eguahé porá",
  "Emedi",
  "Ennidos",
  "Guarani",
  "Horas",
  "Kale ah maruhabaa",
  "Kíimak 'oolal",
  "Mabuhay",
  "Malipayong pag-abot",
  "Maliu mai",
  "Mauri",
  "Mauya",
  "Mayad-ayad nga pad-abot",
  "Mazanderani",
  "Merhbe",
  "Merħba",
  "Mi'kmaq",
  "Mir se vjên",
  "Miro peicak",
  "Mu amuhezwi",
  "Murakaza neza",
  "Märr-ŋamathirri",
  "N bèlantre",
  "Naragsak nga Idadanon",
  "Nau mai",
  "Qaimarutin",
  "Romani",
  "Räxim itegez",
  "Salamat datang",
  "Salve",
  "Siya namkela nonke",
  "Soo dhowow",
  "T'aves baxtalo",
  "Talitali fiefia",
  "Tere tulõmast",
  "Tikilluaritsi",
  "Vælkomin",
  "Walcome",
  "Walloon",
  "Welkam",
  "Weltasualuleg",
  "Wilkomme",
  "Wilkóm",
  "Witajće k nam",
  "Witakśo k nam",
  "Wolkom",
  "Wäljkiimen",
  "Ximopanōltih",
  "Xoş gəlmişsiniz",
  "Zoo siab txais tos",
  "fi'i",
  "nno; dalụ",
  "ǁKhoreǁhare.",
  "लसकुस",
  "ཕེབས་པར་དགའ་བསུ་ཞུ།",
  "Ẹ ku abọ",
]
<|endoftext|>


<commit_before>import os from "os"
import _ from "lodash"
import StatsD from "hot-shots"
import config from "config"
import { error } from "./loggers"

const { NODE_ENV, ENABLE_METRICS, STATSD_HOST, STATSD_PORT } = config

const isTest = NODE_ENV === "test"
const enableMetrics = ENABLE_METRICS === "true"
const appMetricsDisable = [
  "http",
  "http-outbound",
  "mongo",
  "socketio",
  "mqlight",
  "postgresql",
  "mqtt",
  "mysql",
  "redis",
  "riak",
  "memcached",
  "oracledb",
  "oracle",
  "strong-oracle",
]

export const statsClient = new StatsD({
  host: STATSD_HOST,
  port: STATSD_PORT,
  globalTags: { service: "metaphysics", hostname: os.hostname() },
  mock: isTest,
  errorHandler: function(err) {
    error(`Statsd client error ${err}`)
  },
})

if (enableMetrics && !isTest) {
  const appmetrics = require("appmetrics")
  appmetrics.configure({
    mqtt: "off",
  })
  const monitoring = appmetrics.monitor()
  _.forEach(appMetricsDisable, (val, idx) => {
    appmetrics.disable(val)
  })

  monitoring.on("loop", loopMetrics => {
    statsClient.timing("loop.count_per_five_seconds", loopMetrics.count)
    statsClient.timing("loop.minimum_loop_duration", loopMetrics.minimum)
    statsClient.timing("loop.maximum_loop_duration", loopMetrics.maximum)
    statsClient.timing("loop.cpu_usage_in_userland", loopMetrics.cpu_user)
    statsClient.timing("loop.cpu_usage_in_system", loopMetrics.cpu_system)
  })

  monitoring.on("eventloop", eventloopMetrics => {
    statsClient.timing("eventloop.latency.min", eventloopMetrics.latency.min)
    statsClient.timing("eventloop.latency.max", eventloopMetrics.latency.max)
    statsClient.timing("eventloop.latency.avg", eventloopMetrics.latency.avg)
  })

  monitoring.on("memory", memoryMetrics => {
    statsClient.gauge("memory.physical", memoryMetrics.physical)
    statsClient.gauge("memory.virtual", memoryMetrics.virtual)
  })

  monitoring.on("gc", gcMetrics => {
    statsClient.gauge("gc.heap_size", gcMetrics.size)
    statsClient.gauge("gc.heap_used", gcMetrics.used)
    statsClient.timing("gc.sweep_duration", gcMetrics.duration, {sweep_type: gcMetrics.type})
  })
}
<commit_msg>don't send metrics in development - tag with pod_name rather than hostname to match other k8s metrics<commit_after>import os from "os"
import _ from "lodash"
import StatsD from "hot-shots"
import config from "config"
import { error } from "./loggers"

const { NODE_ENV, ENABLE_METRICS, STATSD_HOST, STATSD_PORT } = config

const isTest = NODE_ENV === "test"
const isDev = NODE_ENV === "development"
const enableMetrics = ENABLE_METRICS === "true"
const appMetricsDisable = [
  "http",
  "http-outbound",
  "mongo",
  "socketio",
  "mqlight",
  "postgresql",
  "mqtt",
  "mysql",
  "redis",
  "riak",
  "memcached",
  "oracledb",
  "oracle",
  "strong-oracle",
]

export const statsClient = new StatsD({
  host: STATSD_HOST,
  port: STATSD_PORT,
  globalTags: { service: "metaphysics", pod_name: os.hostname() },
  mock: isDev || isTest,
  errorHandler: function(err) {
    error(`Statsd client error ${err}`)
  },
})

if (enableMetrics && !isDev && !isTest) {
  const appmetrics = require("appmetrics")
  appmetrics.configure({
    mqtt: "off",
  })
  const monitoring = appmetrics.monitor()
  _.forEach(appMetricsDisable, (val, idx) => {
    appmetrics.disable(val)
  })

  monitoring.on("loop", loopMetrics => {
    statsClient.timing("loop.count_per_five_seconds", loopMetrics.count)
    statsClient.timing("loop.minimum_loop_duration", loopMetrics.minimum)
    statsClient.timing("loop.maximum_loop_duration", loopMetrics.maximum)
    statsClient.timing("loop.cpu_usage_in_userland", loopMetrics.cpu_user)
    statsClient.timing("loop.cpu_usage_in_system", loopMetrics.cpu_system)
  })

  monitoring.on("eventloop", eventloopMetrics => {
    statsClient.timing("eventloop.latency.min", eventloopMetrics.latency.min)
    statsClient.timing("eventloop.latency.max", eventloopMetrics.latency.max)
    statsClient.timing("eventloop.latency.avg", eventloopMetrics.latency.avg)
  })

  monitoring.on("memory", memoryMetrics => {
    statsClient.gauge("memory.physical", memoryMetrics.physical)
    statsClient.gauge("memory.virtual", memoryMetrics.virtual)
  })

  monitoring.on("gc", gcMetrics => {
    statsClient.gauge("gc.heap_size", gcMetrics.size)
    statsClient.gauge("gc.heap_used", gcMetrics.used)
    statsClient.timing("gc.sweep_duration", gcMetrics.duration, {sweep_type: gcMetrics.type})
  })
}
<|endoftext|>


<commit_before>/*global window */
/*global console */

define([
  './util',
  './platform',
  './store/memory',
  './store/localStorage',
  './store/indexedDb'
], function (util, platform, memoryAdapter, localStorageAdapter, indexedDbAdapter) {

  "use strict";

  // Namespace: store
  //
  // The store stores data locally. It treats all data as raw nodes, that have *metadata* and *payload*.
  // Metadata and payload are stored under separate keys.

  var logger = util.getLogger('store');

  // foreign nodes are prefixed with a user address
  var userAddressRE = /^[^@]+@[^:]+:\//;

  var events = util.getEventEmitter('error', 'change', 'foreign-change');

  var dataStore;

  // Method: setAdapter
  // Set the storage adapter. See <StorageAdapter> for a description of
  // the required interface.
  function setAdapter(adapter) {
    dataStore = adapter;
    // forward changes from data store (e.g. made in other tabs)
    dataStore.on('change', function(event) {
      if(! util.isDir(event.path)) {
        fireChange('device', event.path, event.oldValue);
      }
    });
  }

  (function() {
    if(typeof(window) !== 'undefined') {
      var idb = indexedDbAdapter.detect();
      if(idb) {
        setAdapter(indexedDbAdapter(idb));
      } else if(typeof(window.localStorage !== 'undefined')) {
        setAdapter(localStorageAdapter(window.localStorage));
      } else {
        throw "Running in browser, but no storage adapter supported!";
      }
    } else {
      console.error("WARNING: falling back to in-memory storage");
      setAdapter(memoryAdapter());
    }
  })();

  //
  // Type: Node
  //
  // Represents a node within the local store.
  //
  // Properties:
  //   startAccess    - either "r" or "rw". Flag means, that this node has been claimed access on (see <remoteStorage.claimAccess>) (default: null)
  //   startForce     - boolean flag to indicate that this node shall always be synced. (see <BaseClient.use> and <BaseClient.release>) (default: null)
  //   startForceTree - boolean flag that all directory children of this node shall be synced.
  //   timestamp      - last time this node was (apparently) updated (default: 0)
  //   lastUpdatedAt  - Last time this node was upated from remotestorage
  //   mimeType       - MIME media type
  //   diff           - (directories only) marks children that have been modified.
  //   data           - Actual data of the node. A String, a JSON-Object or an ArrayBuffer.
  //   binary         - boolean indicating if this node is binary. If true, 'data' is an ArrayBuffer.
  //

  // Event: change
  // See <BaseClient.Events>

  function fireChange(origin, path, oldValue) {
    return getNode(path).
      get('data', 'timestamp').
      then(function(newValue, timestamp) {
        events.emit('change', {
          path: path,
          origin: origin,
          oldValue: oldValue,
          newValue: newValue,
          timestamp: timestamp
        });
      });
  }

  // Event: foreign-change
  // Fired when a foreign node is updated.

  function fireForeignChange(path, oldValue) {
    return getNode(path).
      get('data', 'timestamp').
      then(function(newValue, timestamp) {
        events.emit('foreign-change', {
          path: path,
          oldValue: oldValue,
          newValue: newValue,
          timestamp: timestamp
        });
      });
  }

  //
  // Event: error
  // See <BaseClient.Events>

  //
  // Method: on
  //
  // Install an event handler
  // See <util.EventEmitter.on> for documentation.

  // Method: getNode
  // Get a node.
  //
  // Parameters:
  //   path - absolute path
  //
  // Returns:
  //   a node object. If no node is found at the given path, a new empty
  //   node object is constructed instead.
  function getNode(path) {
    logger.info('getNode', path);
    if(! path) {
      // FIXME: fail returned promise instead.
      throw new Error("No path given!");
    }
    validPath(path);
    return dataStore.get(path).then(function(node) {
      if(! node) {
        node = {//this is what an empty node looks like
          startAccess: null,
          startForce: null,
          startForceTree: null,
          timestamp: 0,
          lastUpdatedAt: 0,
          mimeType: "application/json"
        };
        if(util.isDir(path)) {
          node.diff = {};
          node.data = {};
        }
      }
      return node;
    });
  }

  // Method: forget
  // Forget node at given path
  //
  // Parameters:
  //   path - absolute path
  function forget(path) {
    validPath(path);
    return dataStore.remove(path);
  }

  // Method: forgetAll
  // Forget all data stored by <store>.
  //
  function forgetAll() {
    return dataStore.forgetAll();
  }

  // Function: setNodeData
  //
  // update a node's payload
  //
  // Parameters:
  //   path      - absolute path from the storage root
  //   data      - node data to set, or undefined to delete the node
  //   outgoing  - boolean, whether this update is to be propagated
  //   timestamp - timestamp to set for the update
  //   mimeType  - MIME media type of the node's data
  //
  // Fires:
  //   change w/ origin=remote - unless this is an outgoing change
  //
  function setNodeData(path, data, outgoing, timestamp, mimeType) {
    logger.debug('PUT', path, { data: data, mimeType: mimeType });
    return getNode(path).then(function(node) {

      var oldValue = node.data;

      node.data = data;

      if(! outgoing) {
        if(typeof(timestamp) !== 'number') {
          throw "Attempted to set non-number timestamp in incoming change: " + timestamp + ' (' + typeof(timestamp) + ')';
        }
        node.lastUpdatedAt = timestamp;
      }

      if(! mimeType) {
        mimeType = 'application/json';
      }
      node.mimeType = mimeType;

      return updateNode(path, (node.data ? node : undefined), outgoing, false, timestamp, oldValue);
    });
  }

  function removeNode(path, timestamp) {
    return setNodeData(path, undefined, false, timestamp || getCurrTimestamp());
  }

  function updateMetadata(path, attributes, node) {
    function doUpdate(node) {
      util.extend(node, attributes);
      return updateNode(path, node, false, true);
    }
    if(node) {
      return doUpdate(node);
    } else {
      return getNode(path).then(doUpdate);
    }
  }

  // Method: setNodeAccess
  //
  // Set startAccess flag on a node.
  //
  // Parameters:
  //   path  - absolute path to the node
  //   claim - claim to set. Either "r" or "rw"
  //
  function setNodeAccess(path, claim) {
    logger.debug('setNodeAccess', path, claim);
    return getNode(path).then(function(node) {
      if((claim !== node.startAccess) &&
         (claim === 'rw' || node.startAccess === null)) {
        return updateMetadata(path, {
          startAccess: claim
        }, node);
      }
    });
  }

  // Method: setNodeForce
  //
  // Set startForce and startForceTree flags on a node.
  //
  // Parameters:
  //   path      - absolute path to the node
  //   dataFlag  - whether to sync data
  //   treeFlag  - whether to sync the tree
  //
  function setNodeForce(path, dataFlag, treeFlag) {
    logger.debug('setNodeForce', path, dataFlag, treeFlag);
    return updateMetadata(path, {
      startForce: dataFlag,
      startForceTree: treeFlag
    });
  }

  // Method: clearDiff
  //
  // Clear diff flag of given node on it's parent.
  //
  // Recurses upwards, when the parent's diff becomes empty.
  //
  // Clearing the diff is usually done, once the changes have been
  // propagated through sync.
  //
  // Parameters:
  //   path      - absolute path to the node
  //   timestamp - new timestamp (received from remote) to set on the node.
  //
  function clearDiff(path, timestamp) {
    logger.debug('clearDiff', path);
    return getNode(path).then(function(node) {

      function clearDiffOnParent() {
        var parentPath = util.containingDir(path);
        if(parentPath) {
          var baseName = util.baseName(path);
          return getNode(parentPath).then(function(parent) {
            delete parent.diff[baseName];
            return updateNode(parentPath, parent, false, true).then(function() {
              if(Object.keys(parent.diff).length === 0) {
                return clearDiff(parentPath, timestamp);
              }
            });
          });
        }
      }

      if(util.isDir(path) && Object.keys(node.data).length === 0 &&
         !(node.startAccess || node.startForce || node.startForceTree)) {
        // remove empty dir
        return updateNode(path, undefined, false, false).then(clearDiffOnParent);
      } else if(timestamp) {
        // set last updated
        node.timestamp = node.lastUpdatedAt = timestamp;
        return updateNode(path, node, false, true).then(clearDiffOnParent);
      } else {
        return clearDiffOnParent();
      }
    });
  }

  // Method: fireInitialEvents
  //
  // Fire a change event with origin=device for each node present in store.
  //
  // This is so apps don't need to add event handlers *and* initially request
  // listings to fill their views.
  //
  function fireInitialEvents() {
    logger.info('fire initial events');

    function iter(path) {
      if(util.isDir(path)) {
        return getNode(path).then(function(node) {
          if(node.data) {
            var keys = Object.keys(node.data);
            var next = function() {
              if(keys.length > 0) {
                return iter(path + keys.shift()).then(next);
              }
            };
            return next();
          }
        });
      } else {
        return fireChange('device', path);
      }
    }

    return iter('/');
  }

  function getFileName(path) {
    var parts = path.split('/');
    if(util.isDir(path)) {
      return parts[parts.length-2]+'/';
    } else {
      return parts[parts.length-1];
    }
  }

  function getCurrTimestamp() {
    return new Date().getTime();
  }

  function validPath(path) {
    if(! (path[0] == '/' || userAddressRE.test(path))) {
      throw new Error("Invalid path: " + path);
    }
  }

  function isForeign(path) {
    return path[0] != '/';
  }

  function determineDirTimestamp(path) {
    return getNode(path).
      get('data').then(function(data) {
        var t = 0;
        if(data) {
          for(var key in data) {
            if(data[key] > t) {
              t = data[key];
            }
          }
        }
        return t > 0 ? t : getCurrTimestamp();
      });
  }

  // FIXME: this argument list is getting too long!!!
  function updateNode(path, node, outgoing, meta, timestamp, oldValue) {
    logger.info('updateNode', path, node, outgoing, meta, timestamp);

    validPath(path);

    function adjustTimestamp() {
      return util.makePromise(function(promise) {
        function setTimestamp(t) {
          if(t) { timestamp = t; }
          if(node && typeof(timestamp) == 'number') {
            node.timestamp = timestamp;
          }
          promise.fulfill();
        }
        if((!meta) && (! timestamp)) {
          if(outgoing) {
            timestamp = getCurrTimestamp();
            setTimestamp();
          } else if(util.isDir(path)) {
            determineDirTimestamp(path).then(setTimestamp);
          } else {
            throw new Error('no timestamp given for node ' + path);
          }
        } else {
          setTimestamp();
        }
      });
    }

    function storeNode() {
      if(node) {
        return dataStore.set(path, node);
      } else {
        return dataStore.remove(path);
      }
    }

    function updateParent() {
      var parentPath = util.containingDir(path);
      var baseName = util.baseName(path);
      if(parentPath) {
        return getNode(parentPath).
          then(function(parent) {
            if(meta) { // META
              if(! parent.data[baseName]) {
                parent.data[baseName] = 0;
                return updateNode(parentPath, parent, false, true, timestamp);
              }
            } else if(outgoing) { // OUTGOING
              if(node) {
                parent.data[baseName] = timestamp;
              } else {
                delete parent.data[baseName];
              }
              parent.diff[baseName] = timestamp;
              return updateNode(parentPath, parent, true, false, timestamp);
            } else { // INCOMING
              if(node) { // add or change
                if((! parent.data[baseName]) || parent.data[baseName] < timestamp) {
                  parent.data[baseName] = timestamp;
                  delete parent.diff[baseName];
                  return updateNode(parentPath, parent, false, false, timestamp);
                }
              } else { // deletion
                delete parent.data[baseName];
                delete parent.diff[baseName];
                return updateNode(parentPath, parent, false, false, timestamp);
              }
            }
          });
      }
    }

    function fireEvents() {
      if((! outgoing) && (! util.isDir(path))) {
        // fire changes
        if(isForeign(path)) {
          return fireForeignChange(path, oldValue);
        } else {
          return fireChange('remote', path, oldValue);
        }
      }
    }

    return adjustTimestamp().
      then(storeNode).
      then(updateParent).
      then(fireEvents);
  }

  return {

    events: events,

    // method         , local              , used by

    getNode           : getNode,          // sync
    setNodeData       : setNodeData,      // sync
    clearDiff         : clearDiff,        // sync
    removeNode        : removeNode,       // sync

    on                : events.on,
    setNodeAccess     : setNodeAccess,
    setNodeForce      : setNodeForce,
    forget            : forget,

    forgetAll         : forgetAll,        // widget
    fireInitialEvents : fireInitialEvents,// widget

    setAdapter        : setAdapter,
    getAdapter        : function() { return dataStore; }
  };

  // Interface: StorageAdapter
  //
  // Backend for the <store>.
  //
  // Currently supported:
  // * memory
  // * localStorage
  // * indexedDB
  //
  // Planned:
  // * WebSQL
  //
  // Method: get(path)
  // Get node from given path
  // Returns a promise.
  //
  // Method: set(path, node)
  // Create / update node at given path. See <Node> for a reference on how nodes look.
  // Returns a promise.
  //
  // Method: remove(path)
  // Remove node from given path
  // Returns a promise.
  //
  // Method: forgetAll()
  // Remove all data.
  // Returns a promise.
  //
  // Method: on(eventName)
  // Install an event handler.
  //
  // Event: change
  // Fired when the store changes from another source (such as another tab / window).
  //

});
<commit_msg>doc: fixed link<commit_after>/*global window */
/*global console */

define([
  './util',
  './platform',
  './store/memory',
  './store/localStorage',
  './store/indexedDb'
], function (util, platform, memoryAdapter, localStorageAdapter, indexedDbAdapter) {

  "use strict";

  // Namespace: store
  //
  // The store stores data locally. It treats all data as raw nodes, that have *metadata* and *payload*.
  // Metadata and payload are stored under separate keys.

  var logger = util.getLogger('store');

  // foreign nodes are prefixed with a user address
  var userAddressRE = /^[^@]+@[^:]+:\//;

  var events = util.getEventEmitter('error', 'change', 'foreign-change');

  var dataStore;

  // Method: setAdapter
  // Set the storage adapter. See <StorageAdapter> for a description of
  // the required interface.
  function setAdapter(adapter) {
    dataStore = adapter;
    // forward changes from data store (e.g. made in other tabs)
    dataStore.on('change', function(event) {
      if(! util.isDir(event.path)) {
        fireChange('device', event.path, event.oldValue);
      }
    });
  }

  (function() {
    if(typeof(window) !== 'undefined') {
      var idb = indexedDbAdapter.detect();
      if(idb) {
        setAdapter(indexedDbAdapter(idb));
      } else if(typeof(window.localStorage !== 'undefined')) {
        setAdapter(localStorageAdapter(window.localStorage));
      } else {
        throw "Running in browser, but no storage adapter supported!";
      }
    } else {
      console.error("WARNING: falling back to in-memory storage");
      setAdapter(memoryAdapter());
    }
  })();

  //
  // Type: Node
  //
  // Represents a node within the local store.
  //
  // Properties:
  //   startAccess    - either "r" or "rw". Flag means, that this node has been claimed access on (see <remoteStorage.claimAccess>) (default: null)
  //   startForce     - boolean flag to indicate that this node shall always be synced. (see <BaseClient.use> and <BaseClient.release>) (default: null)
  //   startForceTree - boolean flag that all directory children of this node shall be synced.
  //   timestamp      - last time this node was (apparently) updated (default: 0)
  //   lastUpdatedAt  - Last time this node was upated from remotestorage
  //   mimeType       - MIME media type
  //   diff           - (directories only) marks children that have been modified.
  //   data           - Actual data of the node. A String, a JSON-Object or an ArrayBuffer.
  //   binary         - boolean indicating if this node is binary. If true, 'data' is an ArrayBuffer.
  //

  // Event: change
  // See <BaseClient.Events>

  function fireChange(origin, path, oldValue) {
    return getNode(path).
      get('data', 'timestamp').
      then(function(newValue, timestamp) {
        events.emit('change', {
          path: path,
          origin: origin,
          oldValue: oldValue,
          newValue: newValue,
          timestamp: timestamp
        });
      });
  }

  // Event: foreign-change
  // Fired when a foreign node is updated.

  function fireForeignChange(path, oldValue) {
    return getNode(path).
      get('data', 'timestamp').
      then(function(newValue, timestamp) {
        events.emit('foreign-change', {
          path: path,
          oldValue: oldValue,
          newValue: newValue,
          timestamp: timestamp
        });
      });
  }

  //
  // Event: error
  // See <BaseClient.Events>

  //
  // Method: on
  //
  // Install an event handler
  // See <util.EventEmitter.on> for documentation.

  // Method: getNode
  // Get a node.
  //
  // Parameters:
  //   path - absolute path
  //
  // Returns:
  //   a node object. If no node is found at the given path, a new empty
  //   node object is constructed instead.
  function getNode(path) {
    logger.info('getNode', path);
    if(! path) {
      // FIXME: fail returned promise instead.
      throw new Error("No path given!");
    }
    validPath(path);
    return dataStore.get(path).then(function(node) {
      if(! node) {
        node = {//this is what an empty node looks like
          startAccess: null,
          startForce: null,
          startForceTree: null,
          timestamp: 0,
          lastUpdatedAt: 0,
          mimeType: "application/json"
        };
        if(util.isDir(path)) {
          node.diff = {};
          node.data = {};
        }
      }
      return node;
    });
  }

  // Method: forget
  // Forget node at given path
  //
  // Parameters:
  //   path - absolute path
  function forget(path) {
    validPath(path);
    return dataStore.remove(path);
  }

  // Method: forgetAll
  // Forget all data stored by <store>.
  //
  function forgetAll() {
    return dataStore.forgetAll();
  }

  // Function: setNodeData
  //
  // update a node's payload
  //
  // Parameters:
  //   path      - absolute path from the storage root
  //   data      - node data to set, or undefined to delete the node
  //   outgoing  - boolean, whether this update is to be propagated
  //   timestamp - timestamp to set for the update
  //   mimeType  - MIME media type of the node's data
  //
  // Fires:
  //   change w/ origin=remote - unless this is an outgoing change
  //
  function setNodeData(path, data, outgoing, timestamp, mimeType) {
    logger.debug('PUT', path, { data: data, mimeType: mimeType });
    return getNode(path).then(function(node) {

      var oldValue = node.data;

      node.data = data;

      if(! outgoing) {
        if(typeof(timestamp) !== 'number') {
          throw "Attempted to set non-number timestamp in incoming change: " + timestamp + ' (' + typeof(timestamp) + ')';
        }
        node.lastUpdatedAt = timestamp;
      }

      if(! mimeType) {
        mimeType = 'application/json';
      }
      node.mimeType = mimeType;

      return updateNode(path, (node.data ? node : undefined), outgoing, false, timestamp, oldValue);
    });
  }

  function removeNode(path, timestamp) {
    return setNodeData(path, undefined, false, timestamp || getCurrTimestamp());
  }

  function updateMetadata(path, attributes, node) {
    function doUpdate(node) {
      util.extend(node, attributes);
      return updateNode(path, node, false, true);
    }
    if(node) {
      return doUpdate(node);
    } else {
      return getNode(path).then(doUpdate);
    }
  }

  // Method: setNodeAccess
  //
  // Set startAccess flag on a node.
  //
  // Parameters:
  //   path  - absolute path to the node
  //   claim - claim to set. Either "r" or "rw"
  //
  function setNodeAccess(path, claim) {
    logger.debug('setNodeAccess', path, claim);
    return getNode(path).then(function(node) {
      if((claim !== node.startAccess) &&
         (claim === 'rw' || node.startAccess === null)) {
        return updateMetadata(path, {
          startAccess: claim
        }, node);
      }
    });
  }

  // Method: setNodeForce
  //
  // Set startForce and startForceTree flags on a node.
  //
  // Parameters:
  //   path      - absolute path to the node
  //   dataFlag  - whether to sync data
  //   treeFlag  - whether to sync the tree
  //
  function setNodeForce(path, dataFlag, treeFlag) {
    logger.debug('setNodeForce', path, dataFlag, treeFlag);
    return updateMetadata(path, {
      startForce: dataFlag,
      startForceTree: treeFlag
    });
  }

  // Method: clearDiff
  //
  // Clear diff flag of given node on it's parent.
  //
  // Recurses upwards, when the parent's diff becomes empty.
  //
  // Clearing the diff is usually done, once the changes have been
  // propagated through sync.
  //
  // Parameters:
  //   path      - absolute path to the node
  //   timestamp - new timestamp (received from remote) to set on the node.
  //
  function clearDiff(path, timestamp) {
    logger.debug('clearDiff', path);
    return getNode(path).then(function(node) {

      function clearDiffOnParent() {
        var parentPath = util.containingDir(path);
        if(parentPath) {
          var baseName = util.baseName(path);
          return getNode(parentPath).then(function(parent) {
            delete parent.diff[baseName];
            return updateNode(parentPath, parent, false, true).then(function() {
              if(Object.keys(parent.diff).length === 0) {
                return clearDiff(parentPath, timestamp);
              }
            });
          });
        }
      }

      if(util.isDir(path) && Object.keys(node.data).length === 0 &&
         !(node.startAccess || node.startForce || node.startForceTree)) {
        // remove empty dir
        return updateNode(path, undefined, false, false).then(clearDiffOnParent);
      } else if(timestamp) {
        // set last updated
        node.timestamp = node.lastUpdatedAt = timestamp;
        return updateNode(path, node, false, true).then(clearDiffOnParent);
      } else {
        return clearDiffOnParent();
      }
    });
  }

  // Method: fireInitialEvents
  //
  // Fire a change event with origin=device for each node present in store.
  //
  // This is so apps don't need to add event handlers *and* initially request
  // listings to fill their views.
  //
  function fireInitialEvents() {
    logger.info('fire initial events');

    function iter(path) {
      if(util.isDir(path)) {
        return getNode(path).then(function(node) {
          if(node.data) {
            var keys = Object.keys(node.data);
            var next = function() {
              if(keys.length > 0) {
                return iter(path + keys.shift()).then(next);
              }
            };
            return next();
          }
        });
      } else {
        return fireChange('device', path);
      }
    }

    return iter('/');
  }

  function getFileName(path) {
    var parts = path.split('/');
    if(util.isDir(path)) {
      return parts[parts.length-2]+'/';
    } else {
      return parts[parts.length-1];
    }
  }

  function getCurrTimestamp() {
    return new Date().getTime();
  }

  function validPath(path) {
    if(! (path[0] == '/' || userAddressRE.test(path))) {
      throw new Error("Invalid path: " + path);
    }
  }

  function isForeign(path) {
    return path[0] != '/';
  }

  function determineDirTimestamp(path) {
    return getNode(path).
      get('data').then(function(data) {
        var t = 0;
        if(data) {
          for(var key in data) {
            if(data[key] > t) {
              t = data[key];
            }
          }
        }
        return t > 0 ? t : getCurrTimestamp();
      });
  }

  // FIXME: this argument list is getting too long!!!
  function updateNode(path, node, outgoing, meta, timestamp, oldValue) {
    logger.info('updateNode', path, node, outgoing, meta, timestamp);

    validPath(path);

    function adjustTimestamp() {
      return util.makePromise(function(promise) {
        function setTimestamp(t) {
          if(t) { timestamp = t; }
          if(node && typeof(timestamp) == 'number') {
            node.timestamp = timestamp;
          }
          promise.fulfill();
        }
        if((!meta) && (! timestamp)) {
          if(outgoing) {
            timestamp = getCurrTimestamp();
            setTimestamp();
          } else if(util.isDir(path)) {
            determineDirTimestamp(path).then(setTimestamp);
          } else {
            throw new Error('no timestamp given for node ' + path);
          }
        } else {
          setTimestamp();
        }
      });
    }

    function storeNode() {
      if(node) {
        return dataStore.set(path, node);
      } else {
        return dataStore.remove(path);
      }
    }

    function updateParent() {
      var parentPath = util.containingDir(path);
      var baseName = util.baseName(path);
      if(parentPath) {
        return getNode(parentPath).
          then(function(parent) {
            if(meta) { // META
              if(! parent.data[baseName]) {
                parent.data[baseName] = 0;
                return updateNode(parentPath, parent, false, true, timestamp);
              }
            } else if(outgoing) { // OUTGOING
              if(node) {
                parent.data[baseName] = timestamp;
              } else {
                delete parent.data[baseName];
              }
              parent.diff[baseName] = timestamp;
              return updateNode(parentPath, parent, true, false, timestamp);
            } else { // INCOMING
              if(node) { // add or change
                if((! parent.data[baseName]) || parent.data[baseName] < timestamp) {
                  parent.data[baseName] = timestamp;
                  delete parent.diff[baseName];
                  return updateNode(parentPath, parent, false, false, timestamp);
                }
              } else { // deletion
                delete parent.data[baseName];
                delete parent.diff[baseName];
                return updateNode(parentPath, parent, false, false, timestamp);
              }
            }
          });
      }
    }

    function fireEvents() {
      if((! outgoing) && (! util.isDir(path))) {
        // fire changes
        if(isForeign(path)) {
          return fireForeignChange(path, oldValue);
        } else {
          return fireChange('remote', path, oldValue);
        }
      }
    }

    return adjustTimestamp().
      then(storeNode).
      then(updateParent).
      then(fireEvents);
  }

  return {

    events: events,

    // method         , local              , used by

    getNode           : getNode,          // sync
    setNodeData       : setNodeData,      // sync
    clearDiff         : clearDiff,        // sync
    removeNode        : removeNode,       // sync

    on                : events.on,
    setNodeAccess     : setNodeAccess,
    setNodeForce      : setNodeForce,
    forget            : forget,

    forgetAll         : forgetAll,        // widget
    fireInitialEvents : fireInitialEvents,// widget

    setAdapter        : setAdapter,
    getAdapter        : function() { return dataStore; }
  };

  // Interface: StorageAdapter
  //
  // Backend for the <store>.
  //
  // Currently supported:
  // * memory
  // * localStorage
  // * indexedDB
  //
  // Planned:
  // * WebSQL
  //
  // Method: get(path)
  // Get node from given path
  // Returns a promise.
  //
  // Method: set(path, node)
  // Create / update node at given path. See <store.Node> for a reference on how nodes look.
  // Returns a promise.
  //
  // Method: remove(path)
  // Remove node from given path
  // Returns a promise.
  //
  // Method: forgetAll()
  // Remove all data.
  // Returns a promise.
  //
  // Method: on(eventName)
  // Install an event handler.
  //
  // Event: change
  // Fired when the store changes from another source (such as another tab / window).
  //

});
<|endoftext|>