<commit_before>/**
 * @fileOverview XMLHttpRequest wrapper module for restartless addons
 * @author       YUKI "Piro" Hiroshi
 * @version      6
 * @description
 *   // get as a text
 *   http.get('http://.....',
 *            { 'X-Response-Content-Type': 'text/plain; charset=EUC-JP' })
 *       .next(function(aResponse) {
 *         console.log(aResponse.responseText);
 *       });
 *
 *   // get as a JSON
 *   http.getAsJSON('http://.....')
 *       .next(function(aResponse) {
 *         var body = aResponse.response;
 *         console.log(function);
 *       });
 *
 *   // get as a binary
 *   http.getAsBinary('http://.....')
 *       .next(function(aResponse) {
 *         // arraybyffer = aResponse.response;
 *         var bodyBytesArray = aResponse.responseText;
 *         var bodyBase64 = btoa(bodyBytesArray);
 *         console.log(bodyBase64);
 *       });
 *   // See also: https://developer.mozilla.org/docs/XMLHttpRequest/Sending_and_Receiving_Binary_Data
 *
 *   http.postAsJSON('http://.....', { a: true, b: 29 })
 *       .next(function(aResponse) {
 *       });
 *
 * @license
 *   The MIT License, Copyright (c) 2014 YUKI "Piro" Hiroshi.
 *   https://github.com/piroor/restartless/blob/master/license.txt
 * @url http://github.com/piroor/restartless
 */

var EXPORTED_SYMBOLS = [
  'get',
  'getAsJSON',
  'getAsBinary',
  'post',
  'postAsJSON',
  'RESPONSE_TYPE',
  'RESPONSE_CONTENT_TYPE'
];

var RESPONSE_TYPE         = 'X-Response-Type';
var RESPONSE_CONTENT_TYPE = 'X-Response-Content-Type';

var PSEUDO_HEADERS = [
  RESPONSE_TYPE,
  RESPONSE_CONTENT_TYPE
];

var Deferred = require('jsdeferred').Deferred;

function clone(aObject) {
  var cloned = {};
  Object.keys(aObject).forEach(function(aKey) {
    cloned[aKey] = aObject[aKey];
  });
  return cloned;
}

function get(aURI, aHeaders) {
  return sendRequest({
    method:  'GET',
    uri:     aURI,
    headers: aHeaders
  });
}

function getAsJSON(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'json';
  return get(aURI, headers);
}

function getAsBinary(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'arraybuffer';
  return get(aURI, headers);
}

function post(aURI, aPostData, aHeaders) {
  return sendRequest({
    method:   'POST',
    uri:      aURI,
    headers:  aHeaders,
    postData: aPostData
  });
}

function postAsJSON(aURI, aPostData, aHeaders) {
  var headers = clone(aHeaders);
  var postData = JSON.stringify(aPostData);
  headers['Content-Type'] = 'application/json';
  return post(aURI, aPostData, headers);
}

function ArrayBufferRespone(aResponse) {
  this._raw = aResponse;
  this._init();
}
ArrayBufferRespone.prototype = {
  _init: function() {
    for (let key in this._raw) {
      if (key in this)
        continue;
      this._bind(key);
    }
  },
  _bind: function(aName) {
    if (typeof this._raw[aName] == 'function') {
      this[aName] = this._raw[aName].bind(this._raw);
    } else {
      this.__defineGetter__(aName, function() {
        return this._raw[aName];
      });
    }
  },
  get responseText() {
    var bytesArray = ''
    var bytes = new Uint8Array(this._raw.response);
    for (let i = 0, maxi = bytes.byteLength; i < maxi; i++) {
      bytesArray += String.fromCharCode(bytes[i]);
    }
    return bytesArrya;
  },
  get responseXML() {
    // accessing to the property raises InvalidStateError, so I have to define this staticly
    return this._raw.responseXML;
  }
};

function sendRequest(aParams) {
  var deferred = new Deferred();

  var method   = (aParams.method || 'GET').toUpperCase();
  var uri      = aParams.uri || null;
  var headers  = aParams.headers || {};
  var postData = aParams.postData || null;
  var responseType = aParams.responseType || '';
  var responseContentType = aParams.responseContentType || null;

  if (headers[RESPONSE_TYPE])
    responseType = headers[RESPONSE_TYPE];
  if (headers[RESPONSE_CONTENT_TYPE])
    responseContentType = headers[RESPONSE_CONTENT_TYPE];

  if (!uri)
    throw new Error('no URL');

  var request;
  var listener = function(aEvent) {
    switch (aEvent.type) {
      case 'load':
      case 'error':
        break;
      default:
        return;
    }
    request.removeEventListener('load', listener, false);
    request.removeEventListener('error', listener, false);

    if (request.status == 200) {
      if (responseType == 'arraybuffer')
        deferred.call(new ArrayBufferRespone(request));
      else
        deferred.call(request);
    } else {
      try {
        deferred.fail(new Error(JSON.stringify({
          statusCode: request.status,
          body:       request.responseText,
          event:      {
            type:   aEvent.type,
            detail: aEvent.detail
          }
        })));
      } catch(error) {
        deferred.fail(error);
      }
    }
  };

  Deferred.next(function() {
    request = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
                .createInstance(Ci.nsIXMLHttpRequest)
                .QueryInterface(Ci.nsIDOMEventTarget);
    request.open(method, uri, true);
    if (responseType)
      request.responseType = responseType;
    if (responseContentType)
      request.overrideMimeType(responseContentType);
    Object.keys(headers).forEach(function(aKey) {
      if (PSEUDO_HEADERS.indexOf(aKey) < 0)
        request.setRequestHeader(aKey, headers[aKey]);
    });
    request.addEventListener('load', listener, false);
    request.addEventListener('error', listener, false);
    request.send(postData);
  }).error(function(aError) {
    deferred.fail(aError);
  });

  return deferred;
}
<commit_msg>Accept null input<commit_after>/**
 * @fileOverview XMLHttpRequest wrapper module for restartless addons
 * @author       YUKI "Piro" Hiroshi
 * @version      6
 * @description
 *   // get as a text
 *   http.get('http://.....',
 *            { 'X-Response-Content-Type': 'text/plain; charset=EUC-JP' })
 *       .next(function(aResponse) {
 *         console.log(aResponse.responseText);
 *       });
 *
 *   // get as a JSON
 *   http.getAsJSON('http://.....')
 *       .next(function(aResponse) {
 *         var body = aResponse.response;
 *         console.log(function);
 *       });
 *
 *   // get as a binary
 *   http.getAsBinary('http://.....')
 *       .next(function(aResponse) {
 *         // arraybyffer = aResponse.response;
 *         var bodyBytesArray = aResponse.responseText;
 *         var bodyBase64 = btoa(bodyBytesArray);
 *         console.log(bodyBase64);
 *       });
 *   // See also: https://developer.mozilla.org/docs/XMLHttpRequest/Sending_and_Receiving_Binary_Data
 *
 *   http.postAsJSON('http://.....', { a: true, b: 29 })
 *       .next(function(aResponse) {
 *       });
 *
 * @license
 *   The MIT License, Copyright (c) 2014 YUKI "Piro" Hiroshi.
 *   https://github.com/piroor/restartless/blob/master/license.txt
 * @url http://github.com/piroor/restartless
 */

var EXPORTED_SYMBOLS = [
  'get',
  'getAsJSON',
  'getAsBinary',
  'post',
  'postAsJSON',
  'RESPONSE_TYPE',
  'RESPONSE_CONTENT_TYPE'
];

var RESPONSE_TYPE         = 'X-Response-Type';
var RESPONSE_CONTENT_TYPE = 'X-Response-Content-Type';

var PSEUDO_HEADERS = [
  RESPONSE_TYPE,
  RESPONSE_CONTENT_TYPE
];

var Deferred = require('jsdeferred').Deferred;

function clone(aObject) {
  aObject = aObject || {};
  var cloned = {};
  Object.keys(aObject).forEach(function(aKey) {
    cloned[aKey] = aObject[aKey];
  });
  return cloned;
}

function get(aURI, aHeaders) {
  return sendRequest({
    method:  'GET',
    uri:     aURI,
    headers: aHeaders
  });
}

function getAsJSON(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'json';
  return get(aURI, headers);
}

function getAsBinary(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'arraybuffer';
  return get(aURI, headers);
}

function post(aURI, aPostData, aHeaders) {
  return sendRequest({
    method:   'POST',
    uri:      aURI,
    headers:  aHeaders,
    postData: aPostData
  });
}

function postAsJSON(aURI, aPostData, aHeaders) {
  var headers = clone(aHeaders);
  var postData = JSON.stringify(aPostData);
  headers['Content-Type'] = 'application/json';
  return post(aURI, aPostData, headers);
}

function ArrayBufferRespone(aResponse) {
  this._raw = aResponse;
  this._init();
}
ArrayBufferRespone.prototype = {
  _init: function() {
    for (let key in this._raw) {
      if (key in this)
        continue;
      this._bind(key);
    }
  },
  _bind: function(aName) {
    if (typeof this._raw[aName] == 'function') {
      this[aName] = this._raw[aName].bind(this._raw);
    } else {
      this.__defineGetter__(aName, function() {
        return this._raw[aName];
      });
    }
  },
  get responseText() {
    var bytesArray = ''
    var bytes = new Uint8Array(this._raw.response);
    for (let i = 0, maxi = bytes.byteLength; i < maxi; i++) {
      bytesArray += String.fromCharCode(bytes[i]);
    }
    return bytesArrya;
  },
  get responseXML() {
    // accessing to the property raises InvalidStateError, so I have to define this staticly
    return this._raw.responseXML;
  }
};

function sendRequest(aParams) {
  var deferred = new Deferred();

  var method   = (aParams.method || 'GET').toUpperCase();
  var uri      = aParams.uri || null;
  var headers  = aParams.headers || {};
  var postData = aParams.postData || null;
  var responseType = aParams.responseType || '';
  var responseContentType = aParams.responseContentType || null;

  if (headers[RESPONSE_TYPE])
    responseType = headers[RESPONSE_TYPE];
  if (headers[RESPONSE_CONTENT_TYPE])
    responseContentType = headers[RESPONSE_CONTENT_TYPE];

  if (!uri)
    throw new Error('no URL');

  var request;
  var listener = function(aEvent) {
    switch (aEvent.type) {
      case 'load':
      case 'error':
        break;
      default:
        return;
    }
    request.removeEventListener('load', listener, false);
    request.removeEventListener('error', listener, false);

    if (request.status == 200) {
      if (responseType == 'arraybuffer')
        deferred.call(new ArrayBufferRespone(request));
      else
        deferred.call(request);
    } else {
      try {
        deferred.fail(new Error(JSON.stringify({
          statusCode: request.status,
          body:       request.responseText,
          event:      {
            type:   aEvent.type,
            detail: aEvent.detail
          }
        })));
      } catch(error) {
        deferred.fail(error);
      }
    }
  };

  Deferred.next(function() {
    request = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
                .createInstance(Ci.nsIXMLHttpRequest)
                .QueryInterface(Ci.nsIDOMEventTarget);
    request.open(method, uri, true);
    if (responseType)
      request.responseType = responseType;
    if (responseContentType)
      request.overrideMimeType(responseContentType);
    Object.keys(headers).forEach(function(aKey) {
      if (PSEUDO_HEADERS.indexOf(aKey) < 0)
        request.setRequestHeader(aKey, headers[aKey]);
    });
    request.addEventListener('load', listener, false);
    request.addEventListener('error', listener, false);
    request.send(postData);
  }).error(function(aError) {
    deferred.fail(aError);
  });

  return deferred;
}
<|endoftext|>


<commit_before>export default class TrackedData {
  /**
   * Keeps track of the last change made to any data that is stored
   * @constructor
   * @param {Object} [validProperties=null] - An object containing valid property names as keys and that property's default value as values. If not provided, no validation will occur on property names
   */
  constructor(validProperties=null) {
    this._data = Object.assign({}, validProperties || {});

    // {changedProperty: oldValue, ...}
    this._changes = {};

    this._validPropertiesNames = null;
    if (validProperties) {
      this._validPropertiesNames = new Set(Object.keys(validProperties));
    }
  }

  /**
   * Gets a copy of the value associated with the given name
   * @param {string} name - The name of the property to retrieve
   * @returns {*} a copy of the value of name
   */
  get(name) {
    this._assertValidProperty(name);

    return this._data[name];
  }

  /**
   * Stores the given value and tracks its old value as changed
   * Even if the same value is stored twice, a change will still be registered
   * @param {string} name - The name of the property to set
   * @param {*} value - The value to store
   */
  set(name, value) {
    this._assertValidProperty(name);

    if (this._data.hasOwnProperty(name)) {
      this._changes[name] = this._data[name];
    }

    this._data[name] = value;
  }

  /**
   * Iterates through the names of the properties that have changed
   * @returns {String[]} - The list of names
   */
  *getChangedPropertyNames() {
    yield* Object.keys(this._changes);
    for (let propName of this._changedTrackedDataProperties()) {
      yield propName;
    }

    return changedNames;
  }

  /**
   * Retrieves an object containing the name and old value
   * of each property that has been changed
   * @returns {Object} - Object where keys are the names of each changed property and values are the previous value of that property
   */
  getChangedOldValues() {
    const changed = Object.assign({}, this._changes);

    for (let propName of this._changedTrackedDataProperties()) {
      if (!changed.hasOwnProperty(propName)) {
        changed[propName] = this.get(propName).getChangedOldValues();
      }
    }

    return changed;
  }

  /**
   * Retrieves an object containing the name and current values
   * of each property that has been changed
   * @returns {Object} - Object where keys are the names of each changed property and the values are the current value of that property
   */
  getChangedCurrentValues() {
    const changed = {};

    const changedPropertyNames = Array.from(this._changedTrackedDataProperties()).concat(Object.keys(this._changes));
    for (let propName of changedPropertyNames) {
      changed[propName] = this.get(propName).getChangedCurrentValues();
    }

    return changed;
  }

  /**
   * Clears out any recorded changes
   */
  clearChanges() {
    this._changes = {};
  }

  *_changedTrackedDataProperties(changedProps) {
    for (let propName of Object.keys(this._data)) {
      const value = this.get(propName);
      if (valid instanceof TrackedData) {
        if (value.getChangedPropertyNames().length > 0) {
          yield propName;
        }
      }
    }
  }

  _assertValidProperty(name) {
    if (this._validPropertiesNames && !this._validPropertiesNames.has(name)) {
      throw new Error("Cannot retrieve property '" + name + "'");
    }
  }
}
<commit_msg>Fixed bugs found through linting in tracked data<commit_after>export default class TrackedData {
  /**
   * Keeps track of the last change made to any data that is stored
   * @constructor
   * @param {Object} [validProperties=null] - An object containing valid property names as keys and that property's default value as values. If not provided, no validation will occur on property names
   */
  constructor(validProperties=null) {
    this._data = Object.assign({}, validProperties || {});

    // {changedProperty: oldValue, ...}
    this._changes = {};

    this._validPropertiesNames = null;
    if (validProperties) {
      this._validPropertiesNames = new Set(Object.keys(validProperties));
    }
  }

  /**
   * Gets a copy of the value associated with the given name
   * @param {string} name - The name of the property to retrieve
   * @returns {*} a copy of the value of name
   */
  get(name) {
    this._assertValidProperty(name);

    return this._data[name];
  }

  /**
   * Stores the given value and tracks its old value as changed
   * Even if the same value is stored twice, a change will still be registered
   * @param {string} name - The name of the property to set
   * @param {*} value - The value to store
   */
  set(name, value) {
    this._assertValidProperty(name);

    if (this._data.hasOwnProperty(name)) {
      this._changes[name] = this._data[name];
    }

    this._data[name] = value;
  }

  /**
   * Iterates through the names of the properties that have changed
   */
  *getChangedPropertyNames() {
    yield* Object.keys(this._changes);
    for (let propName of this._changedTrackedDataProperties()) {
      yield propName;
    }
  }

  /**
   * Retrieves an object containing the name and old value
   * of each property that has been changed
   * @returns {Object} - Object where keys are the names of each changed property and values are the previous value of that property
   */
  getChangedOldValues() {
    const changed = Object.assign({}, this._changes);

    for (let propName of this._changedTrackedDataProperties()) {
      if (!changed.hasOwnProperty(propName)) {
        changed[propName] = this.get(propName).getChangedOldValues();
      }
    }

    return changed;
  }

  /**
   * Retrieves an object containing the name and current values
   * of each property that has been changed
   * @returns {Object} - Object where keys are the names of each changed property and the values are the current value of that property
   */
  getChangedCurrentValues() {
    const changed = {};

    const changedPropertyNames = Array.from(this._changedTrackedDataProperties()).concat(Object.keys(this._changes));
    for (let propName of changedPropertyNames) {
      changed[propName] = this.get(propName).getChangedCurrentValues();
    }

    return changed;
  }

  /**
   * Clears out any recorded changes
   */
  clearChanges() {
    this._changes = {};
  }

  *_changedTrackedDataProperties() {
    for (let propName of Object.keys(this._data)) {
      const value = this.get(propName);
      if (value instanceof TrackedData) {
        if (value.getChangedPropertyNames().length > 0) {
          yield propName;
        }
      }
    }
  }

  _assertValidProperty(name) {
    if (this._validPropertiesNames && !this._validPropertiesNames.has(name)) {
      throw new Error("Cannot retrieve property '" + name + "'");
    }
  }
}
<|endoftext|>


<commit_before>var modehate = function(dbot) {
    var dbot = dbot;

    return {
        'listener': function(data, params) {
            console.log(data.raw[1]);
            if(dbot.db.modehate.include(data.user) && data.raw[0].indexOf('-o') != -1) {
                dbot.instance.send('KICK #42 ' + data.user + ' :gtfo');
            }
        },

        'on': 'MODE'
    };
};

exports.fetch = function(dbot) {
    return modehate(dbot);
};
<commit_msg>debug<commit_after>var modehate = function(dbot) {
    var dbot = dbot;

    return {
        'listener': function(data, params) {
            console.log(data.raw);
            if(dbot.db.modehate.include(data.user) && data.raw[0].indexOf('-o') != -1) {
                dbot.instance.send('KICK #42 ' + data.user + ' :gtfo');
            }
        },

        'on': 'MODE'
    };
};

exports.fetch = function(dbot) {
    return modehate(dbot);
};
<|endoftext|>


<commit_before>var spelling = function(dbot) {
    var dbot = dbot;
    var last = {};

    return {
        'listener': function(data, params) {
            var q = data.message.valMatch(/^\*([\d\w\s]*)/, 2);
            if(q) {
                var correction = q[1];
                var candidates = last[data.channel][data.user].split(' ');
                var winner = false;

                for(int i=0;i<candidates.length;i++) {
                    var distance = String.prototype.distance(correction, candidates[i]);
                    if(distance > winner) {
                        winner = candidates[i];
                    }
                }

                if(winner < 3) {
                    var fix = data.message.replace(winner, correction);
                    dbot.say(data.channel, data.user + ':' + fix);
                }
            } else {
                 if(last.hasOwnProperty(data.channel)) {
                   last[data.channel][data.user] = data.message;
                } else {
                    last[data.channel] = { };
                    last[data.channel][data.user] = data.message;
                }
            }
        },

        'on': 'PRIVMSG'
}

exports.fetch = function(dbot) {
    return spelling(dbot);
};
<commit_msg>var not int jesus christ java<commit_after>var spelling = function(dbot) {
    var dbot = dbot;
    var last = {};

    return {
        'listener': function(data, params) {
            var q = data.message.valMatch(/^\*([\d\w\s]*)/, 2);
            if(q) {
                var correction = q[1];
                var candidates = last[data.channel][data.user].split(' ');
                var winner = false;

                for(var i=0;i<candidates.length;i++) {
                    var distance = String.prototype.distance(correction, candidates[i]);
                    if(distance > winner) {
                        winner = candidates[i];
                    }
                }

                if(winner < 3) {
                    var fix = data.message.replace(winner, correction);
                    dbot.say(data.channel, data.user + ':' + fix);
                }
            } else {
                 if(last.hasOwnProperty(data.channel)) {
                   last[data.channel][data.user] = data.message;
                } else {
                    last[data.channel] = { };
                    last[data.channel][data.user] = data.message;
                }
            }
        },

        'on': 'PRIVMSG'
}

exports.fetch = function(dbot) {
    return spelling(dbot);
};
<|endoftext|>


<commit_before>import _clone from 'lodash-es/clone';
import _debounce from 'lodash-es/debounce';
import _uniqWith from 'lodash-es/uniqWith';

import { drag as d3_drag } from 'd3-drag';
import { event as d3_event, select as d3_select } from 'd3-selection';

import {
    modeAddArea,
    modeAddLine,
    modeAddPoint,
    modeBrowse
} from '../modes';

import { svgIcon } from '../svg';
import { t, textDirection } from '../util/locale';
import { tooltip } from '../util/tooltip';
import { uiPresetIcon } from './preset_icon';
import { uiTooltipHtml } from './tooltipHtml';

export function uiModes(context) {

    function enabled() {
        return osmEditable();
    }

    function osmEditable() {
        var mode = context.mode();
        return context.editable() && mode && mode.id !== 'save';
    }

    function toggleMode(d) {

        if (!enabled(d)) return;

        if (d.button === context.mode().button) {
            context.enter(modeBrowse(context));
        } else {
            if (d.preset &&
                // don't set a recent as most recent to avoid reordering buttons
                !d.isRecent()) {
                context.presets().setMostRecent(d.preset, d.geometry);
            }
            context.enter(d);
        }
    }

    return function(selection) {
        context
            .on('enter.editor', function(entered) {
                selection.selectAll('button.add-button')
                    .classed('active', function(mode) { return entered.button === mode.button; });
                context.container()
                    .classed('mode-' + entered.id, true);
            });

        context
            .on('exit.editor', function(exited) {
                context.container()
                    .classed('mode-' + exited.id, false);
            });

        var debouncedUpdate = _debounce(update, 500, { leading: true, trailing: true });

        context.map()
            .on('move.modes', debouncedUpdate)
            .on('drawn.modes', debouncedUpdate);

        context
            .on('enter.modes', update)
            .presets()
            .on('favoritePreset.modes', update)
            .on('recentsChange.modes', update);

        update();

        function update() {

            for (var i = 0; i <= 9; i++) {
                context.keybinding().off(i.toString());
            }

            var items = context.presets().getFavorites();

            var favoritesCount = items.length;

            if (favoritesCount < 10) {
                items = _uniqWith(items.concat(context.presets().getRecents()), function(item1, item2) {
                    return item1.matches(item2.preset, item2.geometry);
                });
                items = items.slice(0, 10);
            }

            var modes = items.map(function(d, index) {
                var presetName = d.preset.name().split(' – ')[0];
                var markerClass = 'add-preset add-' + d.geometry + ' add-preset-' + presetName.replace(/\s+/g, '_')
                    + '-' + d.geometry + ' add-' + d.source; // replace spaces with underscores to avoid css interpretation
                if (d.preset.isFallback()) {
                    markerClass += ' add-generic-preset';
                }

                var supportedGeometry = d.preset.geometry.filter(function(geometry) {
                    return ['vertex', 'point', 'line', 'area'].indexOf(geometry) !== -1;
                });
                var vertexIndex = supportedGeometry.indexOf('vertex');
                if (vertexIndex !== -1 && supportedGeometry.indexOf('point') !== -1) {
                    // both point and vertex allowed, just combine them
                    supportedGeometry.splice(vertexIndex, 1);
                }
                var tooltipTitleID = 'modes.add_preset.title';
                if (supportedGeometry.length !== 1) {
                    if (d.preset.setTags({}, d.geometry).building) {
                        tooltipTitleID = 'modes.add_preset.building.title';
                    } else {
                        tooltipTitleID = 'modes.add_preset.' + d.geometry + '.title';
                    }
                }
                var protoMode = _clone(d);
                protoMode.button = markerClass;
                protoMode.title = presetName;
                protoMode.description = t(tooltipTitleID, { feature: '' + presetName + '' });
                var keyCode;
                if (textDirection === 'ltr') {
                    // use number row order: 1 2 3 4 5 6 7 8 9 0
                    if (index === 9) {
                        keyCode = 0;
                    } else if (index < 10) {
                        keyCode = index + 1;
                    }
                } else {
                    // use number row order from right to left
                    if (index === 0) {
                        keyCode = 0;
                    } else if (index < 10) {
                        keyCode = 10 - index;
                    }
                }
                if (keyCode !== null) {
                    protoMode.key = keyCode.toString();
                }

                var mode;
                switch (d.geometry) {
                    case 'point':
                    case 'vertex':
                        mode = modeAddPoint(context, protoMode);
                        break;
                    case 'line':
                        mode = modeAddLine(context, protoMode);
                        break;
                    case 'area':
                        mode = modeAddArea(context, protoMode);
                }

                if (mode.key) {
                    context.keybinding().on(mode.key, function() {
                        toggleMode(mode);
                    });
                }

                return mode;
            });

            var buttons = selection.selectAll('button.add-button')
                .data(modes, function(d, index) { return d.button + index; });

            // exit
            buttons.exit()
                .remove();

            // enter
            var buttonsEnter = buttons.enter()
                .append('button')
                .attr('tabindex', -1)
                .attr('class', function(d, index) {
                    var classes = d.button + ' add-button bar-button';
                    if (index === favoritesCount && index !== 0) {
                        classes += ' first-recent';
                    }
                    return classes;
                })
                .on('click.mode-buttons', function(d) {

                    // When drawing, ignore accidental clicks on mode buttons - #4042
                    if (/^draw/.test(context.mode().id)) return;

                    toggleMode(d);
                })
                .call(tooltip()
                    .placement('bottom')
                    .html(true)
                    .title(function(d) { return uiTooltipHtml(d.description, d.key); })
                );

            buttonsEnter
                .each(function(d) {
                    if (d.preset.isFallback()) {
                        d3_select(this)
                            .call(svgIcon('#iD-icon-' + d.preset.id));
                    } else {
                        d3_select(this)
                            .call(uiPresetIcon()
                                .geometry(d.geometry)
                                .preset(d.preset)
                                .sizeClass('small')
                            );
                    }
                });

            var dragOrigin, dragMoved, targetIndex, targetData;

            buttonsEnter.call(d3_drag()
                .on('start', function() {
                    dragOrigin = {
                        x: d3_event.x,
                        y: d3_event.y
                    };
                    targetIndex = null;
                    targetData = null;
                    dragMoved = false;
                })
                .on('drag', function(d, index) {
                    dragMoved = true;
                    var x = d3_event.x - dragOrigin.x,
                        y = d3_event.y - dragOrigin.y;

                    if (!d3_select(this).classed('dragging') &&
                        // don't display drag until dragging beyond a distance threshold
                        Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= 5) return;

                    d3_select(this)
                        .classed('dragging', true)
                        .classed('removing', y > 50);

                    targetIndex = null;
                    targetData = null;

                    selection.selectAll('button.add-preset')
                        .style('transform', function(d2, index2) {
                            var node = d3_select(this).node();
                            if (index === index2) {
                                return 'translate(' + x + 'px, ' + y + 'px)';
                            } else if (y > 50) {
                                if (index2 > index) {
                                    return 'translateX(' + (textDirection === 'rtl' ? '' : '-') + '100%)';
                                }
                            } else if (d.source === d2.source) {
                                if (index2 > index && (
                                    (d3_event.x > node.offsetLeft && textDirection === 'ltr') ||
                                    (d3_event.x < node.offsetLeft + node.offsetWidth && textDirection === 'rtl')
                                )) {
                                    if (targetIndex === null || index2 > targetIndex) {
                                        targetIndex = index2;
                                        targetData = d2;
                                    }
                                    return 'translateX(' + (textDirection === 'rtl' ? '' : '-') + '100%)';
                                } else if (index2 < index && (
                                    (d3_event.x < node.offsetLeft + node.offsetWidth && textDirection === 'ltr') ||
                                    (d3_event.x > node.offsetLeft && textDirection === 'rtl')
                                )) {
                                    if (targetIndex === null || index2 < targetIndex) {
                                        targetIndex = index2;
                                        targetData = d2;
                                    }
                                    return 'translateX(' + (textDirection === 'rtl' ? '-' : '') + '100%)';
                                }
                            }
                            return null;
                        });
                })
                .on('end', function(d, index) {

                    if (dragMoved && !d3_select(this).classed('dragging')) {
                        toggleMode(d);
                        return;
                    }

                    d3_select(this)
                        .classed('dragging', false)
                        .classed('removing', false);

                    selection.selectAll('button.add-preset')
                        .style('transform', null);

                    var y = d3_event.y - dragOrigin.y;
                    if (y > 50) {
                        // dragged out of the top bar, remove
                        if (d.isFavorite()) {
                            context.presets().removeFavorite(d.preset, d.geometry);
                            // also remove this as a recent so it doesn't still appear
                            context.presets().removeRecent(d.preset, d.geometry);
                        } else if (d.isRecent()) {
                            context.presets().removeRecent(d.preset, d.geometry);
                        }
                    } else if (targetIndex !== null) {
                        // dragged to a new position, reorder
                        if (d.isFavorite()) {
                            context.presets().moveFavorite(index, targetIndex);
                        } else if (d.isRecent()) {
                            var item = context.presets().recentMatching(d.preset, d.geometry);
                            var beforeItem = context.presets().recentMatching(targetData.preset, targetData.geometry);
                            context.presets().moveRecent(item, beforeItem);
                        }
                    }
                })
            );

            // update
            buttons = buttons
                .merge(buttonsEnter)
                .classed('disabled', function(d) { return !enabled(d); });

            // check if toolbar has overflowed
            context.ui().checkOverflow('#bar', true);
        }
    };
}
<commit_msg>Only show five most recent presets in ribbon<commit_after>import _clone from 'lodash-es/clone';
import _debounce from 'lodash-es/debounce';
import _uniqWith from 'lodash-es/uniqWith';

import { drag as d3_drag } from 'd3-drag';
import { event as d3_event, select as d3_select } from 'd3-selection';

import {
    modeAddArea,
    modeAddLine,
    modeAddPoint,
    modeBrowse
} from '../modes';

import { svgIcon } from '../svg';
import { t, textDirection } from '../util/locale';
import { tooltip } from '../util/tooltip';
import { uiPresetIcon } from './preset_icon';
import { uiTooltipHtml } from './tooltipHtml';

export function uiModes(context) {

    function enabled() {
        return osmEditable();
    }

    function osmEditable() {
        var mode = context.mode();
        return context.editable() && mode && mode.id !== 'save';
    }

    function toggleMode(d) {

        if (!enabled(d)) return;

        if (d.button === context.mode().button) {
            context.enter(modeBrowse(context));
        } else {
            if (d.preset &&
                // don't set a recent as most recent to avoid reordering buttons
                !d.isRecent()) {
                context.presets().setMostRecent(d.preset, d.geometry);
            }
            context.enter(d);
        }
    }

    return function(selection) {
        context
            .on('enter.editor', function(entered) {
                selection.selectAll('button.add-button')
                    .classed('active', function(mode) { return entered.button === mode.button; });
                context.container()
                    .classed('mode-' + entered.id, true);
            });

        context
            .on('exit.editor', function(exited) {
                context.container()
                    .classed('mode-' + exited.id, false);
            });

        var debouncedUpdate = _debounce(update, 500, { leading: true, trailing: true });

        context.map()
            .on('move.modes', debouncedUpdate)
            .on('drawn.modes', debouncedUpdate);

        context
            .on('enter.modes', update)
            .presets()
            .on('favoritePreset.modes', update)
            .on('recentsChange.modes', update);

        update();

        function update() {

            for (var i = 0; i <= 9; i++) {
                context.keybinding().off(i.toString());
            }

            var items = context.presets().getFavorites();

            var favoritesCount = items.length;

            if (favoritesCount < 10) {
                items = _uniqWith(items.concat(context.presets().getRecents()), function(item1, item2) {
                    return item1.matches(item2.preset, item2.geometry);
                });
                var maxShown = 10;
                var maxRecents = 5;
                if (favoritesCount < maxRecents) {
                    // only show five most recent at most
                    maxShown = maxRecents + favoritesCount;
                }
                items = items.slice(0, maxShown);
            }

            var modes = items.map(function(d, index) {
                var presetName = d.preset.name().split(' – ')[0];
                var markerClass = 'add-preset add-' + d.geometry + ' add-preset-' + presetName.replace(/\s+/g, '_')
                    + '-' + d.geometry + ' add-' + d.source; // replace spaces with underscores to avoid css interpretation
                if (d.preset.isFallback()) {
                    markerClass += ' add-generic-preset';
                }

                var supportedGeometry = d.preset.geometry.filter(function(geometry) {
                    return ['vertex', 'point', 'line', 'area'].indexOf(geometry) !== -1;
                });
                var vertexIndex = supportedGeometry.indexOf('vertex');
                if (vertexIndex !== -1 && supportedGeometry.indexOf('point') !== -1) {
                    // both point and vertex allowed, just combine them
                    supportedGeometry.splice(vertexIndex, 1);
                }
                var tooltipTitleID = 'modes.add_preset.title';
                if (supportedGeometry.length !== 1) {
                    if (d.preset.setTags({}, d.geometry).building) {
                        tooltipTitleID = 'modes.add_preset.building.title';
                    } else {
                        tooltipTitleID = 'modes.add_preset.' + d.geometry + '.title';
                    }
                }
                var protoMode = _clone(d);
                protoMode.button = markerClass;
                protoMode.title = presetName;
                protoMode.description = t(tooltipTitleID, { feature: '' + presetName + '' });
                var keyCode;
                if (textDirection === 'ltr') {
                    // use number row order: 1 2 3 4 5 6 7 8 9 0
                    if (index === 9) {
                        keyCode = 0;
                    } else if (index < 10) {
                        keyCode = index + 1;
                    }
                } else {
                    // use number row order from right to left
                    if (index === 0) {
                        keyCode = 0;
                    } else if (index < 10) {
                        keyCode = 10 - index;
                    }
                }
                if (keyCode !== null) {
                    protoMode.key = keyCode.toString();
                }

                var mode;
                switch (d.geometry) {
                    case 'point':
                    case 'vertex':
                        mode = modeAddPoint(context, protoMode);
                        break;
                    case 'line':
                        mode = modeAddLine(context, protoMode);
                        break;
                    case 'area':
                        mode = modeAddArea(context, protoMode);
                }

                if (mode.key) {
                    context.keybinding().on(mode.key, function() {
                        toggleMode(mode);
                    });
                }

                return mode;
            });

            var buttons = selection.selectAll('button.add-button')
                .data(modes, function(d, index) { return d.button + index; });

            // exit
            buttons.exit()
                .remove();

            // enter
            var buttonsEnter = buttons.enter()
                .append('button')
                .attr('tabindex', -1)
                .attr('class', function(d, index) {
                    var classes = d.button + ' add-button bar-button';
                    if (index === favoritesCount && index !== 0) {
                        classes += ' first-recent';
                    }
                    return classes;
                })
                .on('click.mode-buttons', function(d) {

                    // When drawing, ignore accidental clicks on mode buttons - #4042
                    if (/^draw/.test(context.mode().id)) return;

                    toggleMode(d);
                })
                .call(tooltip()
                    .placement('bottom')
                    .html(true)
                    .title(function(d) { return uiTooltipHtml(d.description, d.key); })
                );

            buttonsEnter
                .each(function(d) {
                    if (d.preset.isFallback()) {
                        d3_select(this)
                            .call(svgIcon('#iD-icon-' + d.preset.id));
                    } else {
                        d3_select(this)
                            .call(uiPresetIcon()
                                .geometry(d.geometry)
                                .preset(d.preset)
                                .sizeClass('small')
                            );
                    }
                });

            var dragOrigin, dragMoved, targetIndex, targetData;

            buttonsEnter.call(d3_drag()
                .on('start', function() {
                    dragOrigin = {
                        x: d3_event.x,
                        y: d3_event.y
                    };
                    targetIndex = null;
                    targetData = null;
                    dragMoved = false;
                })
                .on('drag', function(d, index) {
                    dragMoved = true;
                    var x = d3_event.x - dragOrigin.x,
                        y = d3_event.y - dragOrigin.y;

                    if (!d3_select(this).classed('dragging') &&
                        // don't display drag until dragging beyond a distance threshold
                        Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= 5) return;

                    d3_select(this)
                        .classed('dragging', true)
                        .classed('removing', y > 50);

                    targetIndex = null;
                    targetData = null;

                    selection.selectAll('button.add-preset')
                        .style('transform', function(d2, index2) {
                            var node = d3_select(this).node();
                            if (index === index2) {
                                return 'translate(' + x + 'px, ' + y + 'px)';
                            } else if (y > 50) {
                                if (index2 > index) {
                                    return 'translateX(' + (textDirection === 'rtl' ? '' : '-') + '100%)';
                                }
                            } else if (d.source === d2.source) {
                                if (index2 > index && (
                                    (d3_event.x > node.offsetLeft && textDirection === 'ltr') ||
                                    (d3_event.x < node.offsetLeft + node.offsetWidth && textDirection === 'rtl')
                                )) {
                                    if (targetIndex === null || index2 > targetIndex) {
                                        targetIndex = index2;
                                        targetData = d2;
                                    }
                                    return 'translateX(' + (textDirection === 'rtl' ? '' : '-') + '100%)';
                                } else if (index2 < index && (
                                    (d3_event.x < node.offsetLeft + node.offsetWidth && textDirection === 'ltr') ||
                                    (d3_event.x > node.offsetLeft && textDirection === 'rtl')
                                )) {
                                    if (targetIndex === null || index2 < targetIndex) {
                                        targetIndex = index2;
                                        targetData = d2;
                                    }
                                    return 'translateX(' + (textDirection === 'rtl' ? '-' : '') + '100%)';
                                }
                            }
                            return null;
                        });
                })
                .on('end', function(d, index) {

                    if (dragMoved && !d3_select(this).classed('dragging')) {
                        toggleMode(d);
                        return;
                    }

                    d3_select(this)
                        .classed('dragging', false)
                        .classed('removing', false);

                    selection.selectAll('button.add-preset')
                        .style('transform', null);

                    var y = d3_event.y - dragOrigin.y;
                    if (y > 50) {
                        // dragged out of the top bar, remove
                        if (d.isFavorite()) {
                            context.presets().removeFavorite(d.preset, d.geometry);
                            // also remove this as a recent so it doesn't still appear
                            context.presets().removeRecent(d.preset, d.geometry);
                        } else if (d.isRecent()) {
                            context.presets().removeRecent(d.preset, d.geometry);
                        }
                    } else if (targetIndex !== null) {
                        // dragged to a new position, reorder
                        if (d.isFavorite()) {
                            context.presets().moveFavorite(index, targetIndex);
                        } else if (d.isRecent()) {
                            var item = context.presets().recentMatching(d.preset, d.geometry);
                            var beforeItem = context.presets().recentMatching(targetData.preset, targetData.geometry);
                            context.presets().moveRecent(item, beforeItem);
                        }
                    }
                })
            );

            // update
            buttons = buttons
                .merge(buttonsEnter)
                .classed('disabled', function(d) { return !enabled(d); });

            // check if toolbar has overflowed
            context.ui().checkOverflow('#bar', true);
        }
    };
}
<|endoftext|>


<commit_before>var validationWrapperModule = angular.module("validationWrapper", []);

validationWrapperModule.controller("ValidationWrapperCtrl", ["$scope", "$timeout", "$element", function($scope, $timeout, $element) {
	var vm = this;
	var element = $element;

	vm.mandatoryNotifier = $scope.mandatoryNotifier || "*";
	vm.fieldName = $scope.fieldName;
	vm.validationMessages = $scope.validationMessages;
	vm.messageDebounce = $scope.messageDebounce || 0;
	vm.formObject = $scope.getForm();

	vm.isTouched = false;

	vm.shouldShowMessage = function(validationType){
		var show = false;

		if(vm.isTouched && vm.formObject[vm.fieldName]){
			if(!validationType){
				show = vm.formObject[vm.fieldName].$invalid;
			}
			else{
				show = vm.formObject[vm.fieldName].$error[validationType];
			}
		}

		return show;
	};

	$scope.$watch("vm.formObject[vm.fieldName].$touched", function(isTouched) {
		$timeout(function() {
			vm.isTouched = isTouched;
		}, vm.messageDebounce);
	}, false);

	vm.isRequired = function(){
		return element.find("[name='" + vm.fieldName + "']").attr("required") != undefined;
	};

	$scope.$watch("vm.isRequired()", function(isRequired){
		// Get the label to add the mandatory notifier to. Need to get it here because it  will not have been transcluded when the control is first called.
		var label = element.find("label[for='" + vm.fieldName + "']");

		if(!label.find(".mandatory-notifier").length){
			// First time running so add it
			label.append("" + vm.mandatoryNotifier + "");
		}

		var mandatoryNotifier = label.find(".mandatory-notifier");
		mandatoryNotifier.toggle(isRequired);
	}, true);

	$scope.$watch("vm.shouldShowMessage()", function(invalid) {
		// Get addon to add alert-danger style to on invalid. Needs to be here as the element will not have been transcluded when the control is first called.
		var addOn = element.find(".input-group-addon");
		addOn.toggleClass("alert-danger", invalid);
	}, true);
}]);

validationWrapperModule.directive("validationWrapper", function() {
	return {
		templateUrl: function(element, attributes) {
			return attributes.templateUrl || "validation-wrapper/validation-wrapper-template.html";
		},
		transclude: {
			labelElement: "?labelElement",
			inputElement: "inputElement"
		},
		controller: "ValidationWrapperCtrl",
		scope:
		{
			mandatoryNotifier: "=",
			getForm: "=",
			fieldName: "@",
			validationMessages: "=",
			messageDebounce: "@"
		}
	};
});
<commit_msg>Only delay shown messages<commit_after>var validationWrapperModule = angular.module("validationWrapper", []);

validationWrapperModule.controller("ValidationWrapperCtrl", ["$scope", "$timeout", "$element", function($scope, $timeout, $element) {
	var vm = this;
	var element = $element;

	vm.mandatoryNotifier = $scope.mandatoryNotifier || "*";
	vm.fieldName = $scope.fieldName;
	vm.validationMessages = $scope.validationMessages;
	vm.messageDebounce = $scope.messageDebounce || 0;
	vm.formObject = $scope.getForm();

	vm.isTouched = false;

	vm.shouldShowMessage = function(validationType){
		var show = false;

		if(vm.isTouched && vm.formObject[vm.fieldName]){
			if(!validationType){
				show = vm.formObject[vm.fieldName].$invalid;
			}
			else{
				show = vm.formObject[vm.fieldName].$error[validationType];
			}
		}

		return show;
	};

	$scope.$watch("vm.formObject[vm.fieldName].$touched", function(isTouched) {
		if(isTouched){
			$timeout(function() {
				vm.isTouched = isTouched;
			}, vm.messageDebounce);
		}
		else{
			vm.isTouched = isTouched;
		}
	}, false);

	vm.isRequired = function(){
		return element.find("[name='" + vm.fieldName + "']").attr("required") != undefined;
	};

	$scope.$watch("vm.isRequired()", function(isRequired){
		// Get the label to add the mandatory notifier to. Need to get it here because it  will not have been transcluded when the control is first called.
		var label = element.find("label[for='" + vm.fieldName + "']");

		if(!label.find(".mandatory-notifier").length){
			// First time running so add it
			label.append("" + vm.mandatoryNotifier + "");
		}

		var mandatoryNotifier = label.find(".mandatory-notifier");
		mandatoryNotifier.toggle(isRequired);
	}, true);

	$scope.$watch("vm.shouldShowMessage()", function(invalid) {
		// Get addon to add alert-danger style to on invalid. Needs to be here as the element will not have been transcluded when the control is first called.
		var addOn = element.find(".input-group-addon");
		addOn.toggleClass("alert-danger", invalid);
	}, true);
}]);

validationWrapperModule.directive("validationWrapper", function() {
	return {
		templateUrl: function(element, attributes) {
			return attributes.templateUrl || "validation-wrapper/validation-wrapper-template.html";
		},
		transclude: {
			labelElement: "?labelElement",
			inputElement: "inputElement"
		},
		controller: "ValidationWrapperCtrl",
		scope:
		{
			mandatoryNotifier: "=",
			getForm: "=",
			fieldName: "@",
			validationMessages: "=",
			messageDebounce: "@"
		}
	};
});
<|endoftext|>


<commit_before>import Ember from 'ember';
import Application from '../../app';
import Router from '../../router';
import config from '../../config/environment';

export default function startApp(attrs) {
  var application;

  var attributes = Ember.merge({}, config.APP);
  attributes = Ember.merge(attributes, attrs); // use defaults, but you can override;

  Ember.run(function() {
    application = Application.create(attributes);
    application.setupForTesting();
    application.injectTestHelpers();
  });

  return application;
}
<commit_msg>Made test helper to pass jshint<commit_after>import Ember from 'ember';
import Application from '../../app';
import config from '../../config/environment';

export default function startApp(attrs) {
  var application;

  var attributes = Ember.merge({}, config.APP);
  attributes = Ember.merge(attributes, attrs); // use defaults, but you can override;

  Ember.run(function() {
    application = Application.create(attributes);
    application.setupForTesting();
    application.injectTestHelpers();
  });

  return application;
}
<|endoftext|>


<commit_before>/* global videojs */
/* eslint-disable max-len */

import './videojs-resumecard.scss';

let resumeCard = null;

/**
 * Video.js plugin for resuming playback.
 *
 * @example
 * videojs('player_id', {
 *   plugins: {
 *     resumecard: {
 *       time: 42,
 *
 *       template: function template() {
 *         // ...
 *       }
 *     },
 *   },
 * });
 *
 * videojs('player_id').resumecard({
 *   time: 42,
 * });
 *
 * @param {Object} player VideoJS player
 * @param {Object} options={}
 * @param {string} [options.className=vjs-resumecard] HTMLElement class.
 * @param {string} [options.id=vjs_resumecard] HTMLElement id.
 * @param {boolean} [options.preload] Have the player *immediately* seek to time specified.
 * @param {function(event)} [options.restartCallback] Custom restart button callback.
 * @param {function(event)} [options.resumeCallback] Custom resume button callback.
 * @param {function(restartCallback, resumeCallback):HTMLElement} options.template Callback to return custom template HTMLElement.
 * @param {number} options.time Start time, in seconds, for the contented to be resumed.
 */
class ResumeCard {
  constructor(player, options = {}) {
    if (!options.time) {
      throw new Error('videojs-resumecard requires a valid time.');
    }

    if (!options.template) {
      throw new Error('videojs-resumecard requires a valid template.');
    }

    // hide big play button since resume card will take over initial play experience
    if (player.bigPlayButton) {
      player.bigPlayButton.hide();
    }

    // seek immediately on preload
    if (options.preload === true) {
      player.currentTime(options.time);
    }

    this.player = player;
    this.settings = videojs.mergeOptions({
      className: 'vjs-resumecard',
      id: 'vjs_resumecard',
    }, options);

    this.restartCallback = this.restartCallback.bind(this);
    this.resumeCallback = this.resumeCallback.bind(this);

    resumeCard = null;
  }

  fade() {
    if (resumeCard instanceof HTMLElement === false) {
      return;
    }

    resumeCard.style.opacity = 0;
  }

  hide() {
    if (resumeCard instanceof HTMLElement === false) {
      return;
    }

    resumeCard.style.display = 'none';
  }

  render() {
    const {
      className,
      id,
      template,
    } = this.settings;

    resumeCard = document.createElement('div');
    resumeCard.className = className;
    resumeCard.id = id;

    if (typeof template !== 'function') {
      this.player.el().appendChild(resumeCard);
    }

    const resumeCardTemplate = template(
      this.restartCallback,
      this.resumeCallback
    );

    if (resumeCardTemplate instanceof HTMLElement === false) {
      throw new Error('videojs-resumecard requires template to be a valid HTMLElement.');
    }

    resumeCard.appendChild(resumeCardTemplate);

    this.player.el().appendChild(resumeCard);

    this.show();
  }

  /**
   * Executes the callback on restartButton interaction.
   *
   * @param {Object} event
   */
  restartCallback(event) {
    const {
      restartCallback,
    } = this.settings;

    if (typeof restartCallback === 'function') {
      return restartCallback.call(this, event);
    }

    this.player.ready(() => {
      this.player.one('seeked', () => {
        this.hide();

        this.player.play();
      });

      this.player.currentTime(0);

      // visually hide resumecard, but prevent player interaction while video loads.
      this.fade();
    });

    return undefined;
  }

  /**
   * Executes the callback on resumeButton interaction.
   *
   * @param {Object} event
   */
  resumeCallback(event) {
    const {
      resumeCallback,
      time,
    } = this.settings;

    if (typeof resumeCallback === 'function') {
      return resumeCallback.call(this, event);
    }

    this.player.ready(() => {
      this.player.one('seeked', () => {
        this.hide();

        this.player.play();
      });

      this.player.currentTime(time);

      // visually hide resumecard, but prevent player interaction while video loads.
      this.fade();
    });

    return undefined;
  }

  show() {
    if (resumeCard instanceof HTMLElement === false) {
      return;
    }

    resumeCard.style.display = 'block';
  }
}

function resumecard(options) {
  (new ResumeCard(this, options)).render();
}

videojs.plugin('resumecard', resumecard);

export default resumecard;
<commit_msg>fix(videojs-resumecard): Clean up DOM elements if plugin is reinstantiated.<commit_after>/* global videojs */
/* eslint-disable max-len */

import './videojs-resumecard.scss';

let resumeCard = null;

/**
 * Video.js plugin for resuming playback.
 *
 * @example
 * videojs('player_id', {
 *   plugins: {
 *     resumecard: {
 *       time: 42,
 *
 *       template: function template() {
 *         // ...
 *       }
 *     },
 *   },
 * });
 *
 * videojs('player_id').resumecard({
 *   time: 42,
 * });
 *
 * @param {Object} player VideoJS player
 * @param {Object} options={}
 * @param {string} [options.className=vjs-resumecard] HTMLElement class.
 * @param {string} [options.id=vjs_resumecard] HTMLElement id.
 * @param {boolean} [options.preload] Have the player *immediately* seek to time specified.
 * @param {function(event)} [options.restartCallback] Custom restart button callback.
 * @param {function(event)} [options.resumeCallback] Custom resume button callback.
 * @param {function(restartCallback, resumeCallback):HTMLElement} options.template Callback to return custom template HTMLElement.
 * @param {number} options.time Start time, in seconds, for the contented to be resumed.
 */
class ResumeCard {
  constructor(player, options = {}) {
    if (!options.time) {
      throw new Error('videojs-resumecard requires a valid time.');
    }

    if (!options.template) {
      throw new Error('videojs-resumecard requires a valid template.');
    }

    // hide big play button since resume card will take over initial play experience
    if (player.bigPlayButton) {
      player.bigPlayButton.hide();
    }

    // seek immediately on preload
    if (options.preload === true) {
      player.currentTime(options.time);
    }

    this.player = player;
    this.settings = videojs.mergeOptions({
      className: 'vjs-resumecard',
      id: 'vjs_resumecard',
    }, options);

    this.restartCallback = this.restartCallback.bind(this);
    this.resumeCallback = this.resumeCallback.bind(this);

    if (resumeCard instanceof HTMLElement) {
      this.player.el().removeChild(resumeCard);

      resumeCard = null;
    }

  }

  fade() {
    if (resumeCard instanceof HTMLElement === false) {
      return;
    }

    resumeCard.style.opacity = 0;
  }

  hide() {
    if (resumeCard instanceof HTMLElement === false) {
      return;
    }

    resumeCard.style.display = 'none';
  }

  render() {
    const {
      className,
      id,
      template,
    } = this.settings;

    resumeCard = document.createElement('div');
    resumeCard.className = className;
    resumeCard.id = id;

    if (typeof template !== 'function') {
      this.player.el().appendChild(resumeCard);
    }

    const resumeCardTemplate = template(
      this.restartCallback,
      this.resumeCallback
    );

    if (resumeCardTemplate instanceof HTMLElement === false) {
      throw new Error('videojs-resumecard requires template to be a valid HTMLElement.');
    }

    resumeCard.appendChild(resumeCardTemplate);

    this.player.el().appendChild(resumeCard);

    this.show();
  }

  /**
   * Executes the callback on restartButton interaction.
   *
   * @param {Object} event
   */
  restartCallback(event) {
    const {
      restartCallback,
    } = this.settings;

    if (typeof restartCallback === 'function') {
      return restartCallback.call(this, event);
    }

    this.player.ready(() => {
      this.player.one('seeked', () => {
        this.hide();

        this.player.play();
      });

      this.player.currentTime(0);

      // visually hide resumecard, but prevent player interaction while video loads.
      this.fade();
    });

    return undefined;
  }

  /**
   * Executes the callback on resumeButton interaction.
   *
   * @param {Object} event
   */
  resumeCallback(event) {
    const {
      resumeCallback,
      time,
    } = this.settings;

    if (typeof resumeCallback === 'function') {
      return resumeCallback.call(this, event);
    }

    this.player.ready(() => {
      this.player.one('seeked', () => {
        this.hide();

        this.player.play();
      });

      this.player.currentTime(time);

      // visually hide resumecard, but prevent player interaction while video loads.
      this.fade();
    });

    return undefined;
  }

  show() {
    if (resumeCard instanceof HTMLElement === false) {
      return;
    }

    resumeCard.style.display = 'block';
  }
}

function resumecard(options) {
  (new ResumeCard(this, options)).render();
}

videojs.plugin('resumecard', resumecard);

export default resumecard;
<|endoftext|>


<commit_before>'use strict';

const defaultByteLength = 1024 * 8;
const charArray = [];

/**
 * IOBuffer
 * @constructor
 * @param {undefined|number|ArrayBuffer|TypedArray|IOBuffer|Buffer} data - The data to construct the IOBuffer with.
 *
 * If it's a number, it will initialize the buffer with the number as the buffer's length

 * If it's undefined, it will initialize the buffer with a default length of 8 Kb

 * If its an ArrayBuffer, a TypedArray, an IOBuffer instance,
 * or a Node.js Buffer, it will create a view over the underlying ArrayBuffer.
 * @param {object} [options]
 * @param {number} [options.offset=0] - Ignore the first n bytes of the ArrayBuffer
 * @property {ArrayBuffer} buffer - Reference to the internal ArrayBuffer object
 * @property {number} length - Byte length of the internal ArrayBuffer
 * @property {number} offset - The current offset of the buffer's pointer
 * @property {number} byteLength - Byte length of the internal ArrayBuffer
 * @property {number} byteOffset - Byte offset of the internal ArrayBuffer
 */
class IOBuffer {
    constructor(data, options) {
        options = options || {};
        var dataIsGiven = false;
        if (data === undefined) {
            data = defaultByteLength;
        }
        if (typeof data === 'number') {
            data = new ArrayBuffer(data);
        } else {
            dataIsGiven = true;
            this._lastWrittenByte = data.byteLength;
        }

        const offset = options.offset ? options.offset >>> 0 : 0;
        let byteLength = data.byteLength - offset;
        let dvOffset = offset;
        if (data.buffer) {
            if (data.byteLength !== data.buffer.byteLength) {
                dvOffset = data.byteOffset + offset;
            }
            data = data.buffer;
        }
        if (dataIsGiven) {
            this._lastWrittenByte = byteLength;
        } else {
            this._lastWrittenByte = 0;
        }
        this.buffer = data;
        this.length = byteLength;
        this.byteLength = byteLength;
        this.byteOffset = dvOffset;
        this.offset = 0;
        this.littleEndian = true;
        this._data = new DataView(this.buffer, dvOffset, byteLength);
        this._increment = byteLength || defaultByteLength;
        this._mark = 0;
        this._marks = [];
    }

    /**
     * Checks if the memory allocated to the buffer is sufficient to store more bytes after the offset
     * @param {number} [byteLength=1] The needed memory in bytes
     * @return {boolean} Returns true if there is sufficient space and false otherwise
     */
    available(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        return (this.offset + byteLength) <= this.length;
    }

    /**
     * Check if little-endian mode is used for reading and writing multi-byte values
     * @return {boolean} Returns true if little-endian mode is used, false otherwise
     */
    isLittleEndian() {
        return this.littleEndian;
    }

    /**
     * Set little-endian mode for reading and writing multi-byte values
     * @return {IOBuffer}
     */
    setLittleEndian() {
        this.littleEndian = true;
        return this;
    }

    /**
     * Check if big-endian mode is used for reading and writing multi-byte values
     * @return {boolean} Returns true if big-endian mode is used, false otherwise
     */
    isBigEndian() {
        return !this.littleEndian;
    }

    /**
     * Switches to big-endian mode for reading and writing multi-byte values
     * @return {IOBuffer}
     */
    setBigEndian() {
        this.littleEndian = false;
        return this;
    }

    /**
     * Move the pointer n bytes forward
     * @param {number} n
     * @return {IOBuffer}
     */
    skip(n) {
        if (n === undefined) n = 1;
        this.offset += n;
        return this;
    }

    /**
     * Move the pointer to the given offset
     * @param {number} offset
     * @return {IOBuffer}
     */
    seek(offset) {
        this.offset = offset;
        return this;
    }

    /**
     * Store the current pointer offset.
     * @see {@link IOBuffer#reset}
     * @return {IOBuffer}
     */
    mark() {
        this._mark = this.offset;
        return this;
    }

    /**
     * Move the pointer back to the last pointer offset set by mark
     * @see {@link IOBuffer#mark}
     * @return {IOBuffer}
     */
    reset() {
        this.offset = this._mark;
        return this;
    }

    /**
     * Push the current pointer offset to the mark stack
     * @see {@link IOBuffer#popMark}
     * @return {IOBuffer}
     */
    pushMark() {
        this._marks.push(this.offset);
        return this;
    }

    /**
     * Pop the last pointer offset from the mark stack, and set the current pointer offset to the popped value
     * @see {@link IOBuffer#pushMark}
     * @return {IOBuffer}
     */
    popMark() {
        const offset = this._marks.pop();
        if (offset === undefined) throw new Error('Mark stack empty');
        this.seek(offset);
        return this;
    }

    /**
     * Move the pointer offset back to 0
     * @return {IOBuffer}
     */
    rewind() {
        this.offset = 0;
        return this;
    }

    /**
     * Make sure the buffer has sufficient memory to write a given byteLength at the current pointer offset
     * If the buffer's memory is insufficient, this method will double the internal buffer's length
     * @param {number} byteLength
     * @return {IOBuffer}
     */
    ensureAvailable(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        if (!this.available(byteLength)) {
            const newIncrement = this._increment + this._increment;
            this._increment = newIncrement;
            const newLength = this.length + newIncrement;
            const newArray = new Uint8Array(newLength);
            newArray.set(new Uint8Array(this.buffer));
            this.buffer = newArray.buffer;
            this.length = newLength;
            this._data = new DataView(this.buffer);
        }
        return this;
    }

    /**
     * Read a byte and return false if the byte's value is 0, or true otherwise
     * Moves pointer forward
     * @return {boolean}
     */
    readBoolean() {
        return this.readUint8() !== 0;
    }

    /**
     * Read a signed 8-bit integer and move pointer forward
     * @return {number}
     */
    readInt8() {
        return this._data.getInt8(this.offset++);
    }

    /**
     * Read an unsigned 8-bit integer and move pointer forward
     * @return {number}
     */
    readUint8() {
        return this._data.getUint8(this.offset++);
    }

    /**
     * Alias for {@link IOBuffer#readUint8}
     * @return {number}
     */
    readByte() {
        return this.readUint8();
    }

    /**
     * Read n bytes and move pointer forward.
     * @param n
     * @return {Uint8Array}
     */
    readBytes(n) {
        if (n === undefined) n = 1;
        var bytes = new Uint8Array(n);
        for (var i = 0; i < n; i++) {
            bytes[i] = this.readByte();
        }
        return bytes;
    }

    /**
     * Read a 16-bit signed integer and move pointer forward
     * @return {number}
     */
    readInt16() {
        var value = this._data.getInt16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    /**
     * Read a 16-bit unsigned integer and move pointer forward
     * @return {number}
     */
    readUint16() {
        var value = this._data.getUint16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    /**
     * Read a 32-bit signed integer and move pointer forward
     * @return {number}
     */
    readInt32() {
        var value = this._data.getInt32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 32-bit unsigned integer and move pointer forward
     * @return {number}
     */
    readUint32() {
        var value = this._data.getUint32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 32-bit floating number and move pointer forward
     * @return {number}
     */
    readFloat32() {
        var value = this._data.getFloat32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 64-bit floating number and move pointer forward
     * @return {number}
     */
    readFloat64() {
        var value = this._data.getFloat64(this.offset, this.littleEndian);
        this.offset += 8;
        return value;
    }

    /**
     * Read 1-byte ascii character and move pointer forward
     * @return {string}
     */
    readChar() {
        return String.fromCharCode(this.readInt8());
    }

    /**
     * Read n 1-byte ascii characters and move pointer forward
     * @param n
     * @return {string}
     */
    readChars(n) {
        if (n === undefined) n = 1;
        charArray.length = n;
        for (var i = 0; i < n; i++) {
            charArray[i] = this.readChar();
        }
        return charArray.join('');
    }

    /**
     * Write 0xff if the passed value is truthy, 0x00 otherwise
     * @param {any} value
     * @return {IOBuffer}
     */
    writeBoolean(value) {
        this.writeUint8(value ? 0xff : 0x00);
        return this;
    }

    /**
     * Write value as an 8-bit signed integer
     * @param value
     * @return {IOBuffer}
     */
    writeInt8(value) {
        this.ensureAvailable(1);
        this._data.setInt8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as a 8-bit unsigned integer
     * @param value
     * @return {IOBuffer}
     */
    writeUint8(value) {
        this.ensureAvailable(1);
        this._data.setUint8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * An alias for {@link IOBuffer#writeUint8}
     * @param value
     * @return {IOBuffer}
     */
    writeByte(value) {
        return this.writeUint8(value);
    }

    /**
     * Write bytes
     * @param {Array|Uint8Array} bytes
     * @return {IOBuffer}
     */
    writeBytes(bytes) {
        this.ensureAvailable(bytes.length);
        for (var i = 0; i < bytes.length; i++) {
            this._data.setUint8(this.offset++, bytes[i]);
        }
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as an 16-bit signed integer
     * @param value
     * @return {IOBuffer}
     */
    writeInt16(value) {
        this.ensureAvailable(2);
        this._data.setInt16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as a 16-bit unsigned integer
     * @param value
     * @return {IOBuffer}
     */
    writeUint16(value) {
        this.ensureAvailable(2);
        this._data.setUint16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit signed integer at the current pointer offset
     * @param value
     * @return {IOBuffer}
     */
    writeInt32(value) {
        this.ensureAvailable(4);
        this._data.setInt32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit unsigned integer at the current pointer offset
     * @param value - The value to set
     * @return {IOBuffer}
     */
    writeUint32(value) {
        this.ensureAvailable(4);
        this._data.setUint32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit floating number at the current pointer offset
     * @param value - The value to set
     * @return {IOBuffer}
     */
    writeFloat32(value) {
        this.ensureAvailable(4);
        this._data.setFloat32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 64-bit floating number at the current pointer offset
     * @param value
     * @return {IOBuffer}
     */
    writeFloat64(value) {
        this.ensureAvailable(8);
        this._data.setFloat64(this.offset, value, this.littleEndian);
        this.offset += 8;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write the charCode of the passed string's first character to the current pointer offset
     * @param {string} str - The character to set
     * @return {IOBuffer}
     */
    writeChar(str) {
        return this.writeUint8(str.charCodeAt(0));
    }

    /**
     * Write the charCodes of the passed string's characters to the current pointer offset
     * @param str
     * @return {IOBuffer}
     */
    writeChars(str) {
        for (var i = 0; i < str.length; i++) {
            this.writeUint8(str.charCodeAt(i));
        }
        return this;
    }

    /**
     * Export a Uint8Array view of the internal buffer.
     * The view starts at the byte offset and its length
     * is calculated to stop at the last written byte or the original length.
     * @return {Uint8Array}
     */
    toArray() {
        return new Uint8Array(this.buffer, this.byteOffset, this._lastWrittenByte);
    }

    /**
     * Update the last written byte offset
     * @private
     */
    _updateLastWrittenByte() {
        if (this.offset > this._lastWrittenByte) {
            this._lastWrittenByte = this.offset;
        }
    }
}

module.exports = IOBuffer;
<commit_msg>fix(iobuffer): fix an edge case with ensureAvailable<commit_after>'use strict';

const defaultByteLength = 1024 * 8;
const charArray = [];

/**
 * IOBuffer
 * @constructor
 * @param {undefined|number|ArrayBuffer|TypedArray|IOBuffer|Buffer} data - The data to construct the IOBuffer with.
 *
 * If it's a number, it will initialize the buffer with the number as the buffer's length

 * If it's undefined, it will initialize the buffer with a default length of 8 Kb

 * If its an ArrayBuffer, a TypedArray, an IOBuffer instance,
 * or a Node.js Buffer, it will create a view over the underlying ArrayBuffer.
 * @param {object} [options]
 * @param {number} [options.offset=0] - Ignore the first n bytes of the ArrayBuffer
 * @property {ArrayBuffer} buffer - Reference to the internal ArrayBuffer object
 * @property {number} length - Byte length of the internal ArrayBuffer
 * @property {number} offset - The current offset of the buffer's pointer
 * @property {number} byteLength - Byte length of the internal ArrayBuffer
 * @property {number} byteOffset - Byte offset of the internal ArrayBuffer
 */
class IOBuffer {
    constructor(data, options) {
        options = options || {};
        var dataIsGiven = false;
        if (data === undefined) {
            data = defaultByteLength;
        }
        if (typeof data === 'number') {
            data = new ArrayBuffer(data);
        } else {
            dataIsGiven = true;
            this._lastWrittenByte = data.byteLength;
        }

        const offset = options.offset ? options.offset >>> 0 : 0;
        let byteLength = data.byteLength - offset;
        let dvOffset = offset;
        if (data.buffer) {
            if (data.byteLength !== data.buffer.byteLength) {
                dvOffset = data.byteOffset + offset;
            }
            data = data.buffer;
        }
        if (dataIsGiven) {
            this._lastWrittenByte = byteLength;
        } else {
            this._lastWrittenByte = 0;
        }
        this.buffer = data;
        this.length = byteLength;
        this.byteLength = byteLength;
        this.byteOffset = dvOffset;
        this.offset = 0;
        this.littleEndian = true;
        this._data = new DataView(this.buffer, dvOffset, byteLength);
        this._mark = 0;
        this._marks = [];
    }

    /**
     * Checks if the memory allocated to the buffer is sufficient to store more bytes after the offset
     * @param {number} [byteLength=1] The needed memory in bytes
     * @return {boolean} Returns true if there is sufficient space and false otherwise
     */
    available(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        return (this.offset + byteLength) <= this.length;
    }

    /**
     * Check if little-endian mode is used for reading and writing multi-byte values
     * @return {boolean} Returns true if little-endian mode is used, false otherwise
     */
    isLittleEndian() {
        return this.littleEndian;
    }

    /**
     * Set little-endian mode for reading and writing multi-byte values
     * @return {IOBuffer}
     */
    setLittleEndian() {
        this.littleEndian = true;
        return this;
    }

    /**
     * Check if big-endian mode is used for reading and writing multi-byte values
     * @return {boolean} Returns true if big-endian mode is used, false otherwise
     */
    isBigEndian() {
        return !this.littleEndian;
    }

    /**
     * Switches to big-endian mode for reading and writing multi-byte values
     * @return {IOBuffer}
     */
    setBigEndian() {
        this.littleEndian = false;
        return this;
    }

    /**
     * Move the pointer n bytes forward
     * @param {number} n
     * @return {IOBuffer}
     */
    skip(n) {
        if (n === undefined) n = 1;
        this.offset += n;
        return this;
    }

    /**
     * Move the pointer to the given offset
     * @param {number} offset
     * @return {IOBuffer}
     */
    seek(offset) {
        this.offset = offset;
        return this;
    }

    /**
     * Store the current pointer offset.
     * @see {@link IOBuffer#reset}
     * @return {IOBuffer}
     */
    mark() {
        this._mark = this.offset;
        return this;
    }

    /**
     * Move the pointer back to the last pointer offset set by mark
     * @see {@link IOBuffer#mark}
     * @return {IOBuffer}
     */
    reset() {
        this.offset = this._mark;
        return this;
    }

    /**
     * Push the current pointer offset to the mark stack
     * @see {@link IOBuffer#popMark}
     * @return {IOBuffer}
     */
    pushMark() {
        this._marks.push(this.offset);
        return this;
    }

    /**
     * Pop the last pointer offset from the mark stack, and set the current pointer offset to the popped value
     * @see {@link IOBuffer#pushMark}
     * @return {IOBuffer}
     */
    popMark() {
        const offset = this._marks.pop();
        if (offset === undefined) throw new Error('Mark stack empty');
        this.seek(offset);
        return this;
    }

    /**
     * Move the pointer offset back to 0
     * @return {IOBuffer}
     */
    rewind() {
        this.offset = 0;
        return this;
    }

    /**
     * Make sure the buffer has sufficient memory to write a given byteLength at the current pointer offset
     * If the buffer's memory is insufficient, this method will create a new buffer (a copy) with a length
     * that is twice (byteLength + current offset)
     * @param {number} byteLength
     * @return {IOBuffer}
     */
    ensureAvailable(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        if (!this.available(byteLength)) {
            const lengthNeeded = this.offset + byteLength;
            const newLength = lengthNeeded * 2;

            const newArray = new Uint8Array(newLength);
            newArray.set(new Uint8Array(this.buffer));
            this.buffer = newArray.buffer;
            this.length = newLength;
            this._data = new DataView(this.buffer);
        }
        return this;
    }

    /**
     * Read a byte and return false if the byte's value is 0, or true otherwise
     * Moves pointer forward
     * @return {boolean}
     */
    readBoolean() {
        return this.readUint8() !== 0;
    }

    /**
     * Read a signed 8-bit integer and move pointer forward
     * @return {number}
     */
    readInt8() {
        return this._data.getInt8(this.offset++);
    }

    /**
     * Read an unsigned 8-bit integer and move pointer forward
     * @return {number}
     */
    readUint8() {
        return this._data.getUint8(this.offset++);
    }

    /**
     * Alias for {@link IOBuffer#readUint8}
     * @return {number}
     */
    readByte() {
        return this.readUint8();
    }

    /**
     * Read n bytes and move pointer forward.
     * @param n
     * @return {Uint8Array}
     */
    readBytes(n) {
        if (n === undefined) n = 1;
        var bytes = new Uint8Array(n);
        for (var i = 0; i < n; i++) {
            bytes[i] = this.readByte();
        }
        return bytes;
    }

    /**
     * Read a 16-bit signed integer and move pointer forward
     * @return {number}
     */
    readInt16() {
        var value = this._data.getInt16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    /**
     * Read a 16-bit unsigned integer and move pointer forward
     * @return {number}
     */
    readUint16() {
        var value = this._data.getUint16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    /**
     * Read a 32-bit signed integer and move pointer forward
     * @return {number}
     */
    readInt32() {
        var value = this._data.getInt32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 32-bit unsigned integer and move pointer forward
     * @return {number}
     */
    readUint32() {
        var value = this._data.getUint32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 32-bit floating number and move pointer forward
     * @return {number}
     */
    readFloat32() {
        var value = this._data.getFloat32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 64-bit floating number and move pointer forward
     * @return {number}
     */
    readFloat64() {
        var value = this._data.getFloat64(this.offset, this.littleEndian);
        this.offset += 8;
        return value;
    }

    /**
     * Read 1-byte ascii character and move pointer forward
     * @return {string}
     */
    readChar() {
        return String.fromCharCode(this.readInt8());
    }

    /**
     * Read n 1-byte ascii characters and move pointer forward
     * @param n
     * @return {string}
     */
    readChars(n) {
        if (n === undefined) n = 1;
        charArray.length = n;
        for (var i = 0; i < n; i++) {
            charArray[i] = this.readChar();
        }
        return charArray.join('');
    }

    /**
     * Write 0xff if the passed value is truthy, 0x00 otherwise
     * @param {any} value
     * @return {IOBuffer}
     */
    writeBoolean(value) {
        this.writeUint8(value ? 0xff : 0x00);
        return this;
    }

    /**
     * Write value as an 8-bit signed integer
     * @param value
     * @return {IOBuffer}
     */
    writeInt8(value) {
        this.ensureAvailable(1);
        this._data.setInt8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as a 8-bit unsigned integer
     * @param value
     * @return {IOBuffer}
     */
    writeUint8(value) {
        this.ensureAvailable(1);
        this._data.setUint8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * An alias for {@link IOBuffer#writeUint8}
     * @param value
     * @return {IOBuffer}
     */
    writeByte(value) {
        return this.writeUint8(value);
    }

    /**
     * Write bytes
     * @param {Array|Uint8Array} bytes
     * @return {IOBuffer}
     */
    writeBytes(bytes) {
        this.ensureAvailable(bytes.length);
        for (var i = 0; i < bytes.length; i++) {
            this._data.setUint8(this.offset++, bytes[i]);
        }
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as an 16-bit signed integer
     * @param value
     * @return {IOBuffer}
     */
    writeInt16(value) {
        this.ensureAvailable(2);
        this._data.setInt16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as a 16-bit unsigned integer
     * @param value
     * @return {IOBuffer}
     */
    writeUint16(value) {
        this.ensureAvailable(2);
        this._data.setUint16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit signed integer at the current pointer offset
     * @param value
     * @return {IOBuffer}
     */
    writeInt32(value) {
        this.ensureAvailable(4);
        this._data.setInt32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit unsigned integer at the current pointer offset
     * @param value - The value to set
     * @return {IOBuffer}
     */
    writeUint32(value) {
        this.ensureAvailable(4);
        this._data.setUint32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit floating number at the current pointer offset
     * @param value - The value to set
     * @return {IOBuffer}
     */
    writeFloat32(value) {
        this.ensureAvailable(4);
        this._data.setFloat32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 64-bit floating number at the current pointer offset
     * @param value
     * @return {IOBuffer}
     */
    writeFloat64(value) {
        this.ensureAvailable(8);
        this._data.setFloat64(this.offset, value, this.littleEndian);
        this.offset += 8;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write the charCode of the passed string's first character to the current pointer offset
     * @param {string} str - The character to set
     * @return {IOBuffer}
     */
    writeChar(str) {
        return this.writeUint8(str.charCodeAt(0));
    }

    /**
     * Write the charCodes of the passed string's characters to the current pointer offset
     * @param str
     * @return {IOBuffer}
     */
    writeChars(str) {
        for (var i = 0; i < str.length; i++) {
            this.writeUint8(str.charCodeAt(i));
        }
        return this;
    }

    /**
     * Export a Uint8Array view of the internal buffer.
     * The view starts at the byte offset and its length
     * is calculated to stop at the last written byte or the original length.
     * @return {Uint8Array}
     */
    toArray() {
        return new Uint8Array(this.buffer, this.byteOffset, this._lastWrittenByte);
    }

    /**
     * Update the last written byte offset
     * @private
     */
    _updateLastWrittenByte() {
        if (this.offset > this._lastWrittenByte) {
            this._lastWrittenByte = this.offset;
        }
    }
}

module.exports = IOBuffer;
<|endoftext|>