<commit_before>// add pound symbol for jquery element id addressing
function tag_id(id_name) {

    return '#' + id_name;
}

// translate domain (search category such as disease, biomarker, etc.)
// into standardized field ids following the convention:
// disease ->
//      disease_names -> for hidden field holding the concept names as comma delimited string
//      disease_codes -> for hidden field holding the concept codes as comma delimited string
//      diseases      -> for unordered list of selected concept names
function get_fields(dom) {

    return {
        'name_list': tag_id(dom + '_names'),
        'code_list': tag_id(dom + '_codes'),
        'ul_list': tag_id(dom + 's')
    }
}

// load comma delimited list of concept codes from hidden string field and populate an UL
// will callback to retrieve names for the concept codes
// code_list_id - id of the text form field
// ul_list_id - id of the ul list
function refresh_ul_list_from_hidden(dom) {

    var fields = get_fields(dom);
    var names = $(fields.name_list).val().split(', ');
    var codes = $(fields.code_list).val().split(', ');

    $.each(names, function (i, name) {
        if (name !== '') {
            var code = codes[i];
            $(fields.ul_list).append(create_li(code, name, dom));
        }
    });
}

// wrapper to call the remove_term for both names and code hidden fields
function rm_term(code, name, dom) {

    var fields = get_fields(dom);
    // remove ul list item
    $(tag_id(code)).remove();
    // remove name from hidden form field
    remove_term(name, fields.name_list);
    // remove code from hidden form field
    remove_term(code, fields.code_list);
}

// remove term from comma separated list in text field
function remove_term(term, list_id) {

    var hidden_field_value = $(list_id).val();
    var terms = hidden_field_value.split(', ');
    var pos = terms.indexOf(term);
    terms.splice(pos, 1);
    $(list_id).val(terms.join(", "));
}

function _do_code_callback(code, dom, url, reverse) {

    $.get(url + code, function (data) {
        data.forEach(function(entry) {
            var code = entry.code;
            var name = entry.name.replace(/_/g,' ');
            if (reverse) {
                rm_term(code, name, dom);
            } else {
                add_term(code, name, dom);
            }
        });
    });
}

// code is nci thesaurus concept id
// dom is domain either biomarker or disease (or other in the future)
function add_child_terms(code, dom) {
    _do_code_callback(code, dom, 'get_child_codes?code=', false);
}

// code is nci thesaurus concept id
// dom is domain either biomarker or disease (or other in the future)
function rm_child_terms(code, dom) {
    _do_code_callback(code, dom, 'get_child_codes?code=', true);
}

// code is nci thesaurus concept id
// dom is domain either biomarker or disease (or other in the future)
function add_parent_terms(code, dom) {
    _do_code_callback(code, dom, 'get_parent_codes?code=', false);
}

// code is nci thesaurus concept id
// dom is domain either biomarker or disease (or other in the future)
function rm_parent_terms(code, dom) {
    _do_code_callback(code, dom, 'get_parent_codes?code=', true);
}

// generates link for selected code and name
// to be appended in the ul
function create_li(code, name, dom) {

    return '
'
    + 'remove'
    + ' | add child terms'
    + ' | remove child terms'
    + ' | add parent terms'
    + ' | remove parent terms'
    + ' | ' + name + '
';
}

// update the hidden fields for codes and names
// and the unordered list for selected terms
function add_term(code, name, dom) {

    var fields = get_fields(dom);

    if(!$(fields.code_list).val().includes(code)) {
        add_item_to_comma_delimited_list(fields.name_list, name);
        add_item_to_comma_delimited_list(fields.code_list, code);
        $(fields.ul_list).append( create_li(code, name, dom) );
    }
}

// split the comma-delimited string into array
// add item to list if it doesn't already exist in the list
// join it back together and replace the value
function add_item_to_comma_delimited_list(field_name, item) {

    $(field_name).val( $.grep([$(field_name).val(), item], Boolean).join(", ") );
}

function update_tree(code, item, dom) {
    var tree = $('#jstree').jstree(true);
    var root = tree.get_node('root');
    var node = build_jstree_node(code, item, 'root');
    add_jstree_node(root, node, tree);
}

function get_code_for_id(node_id) {
    var codes = node_id.split('*');
    return codes[codes.length-1];
}

function get_id_for_code(parent_code, code) {
    return parent_code ? parent_code + '*' + code : code;
}

function get_icon_for_code(code) {
    return (code == 'root') ? 'glyphicon glyphicon-info-sign' : 'glyphicon glyphicon-check';
}

function build_jstree_node(code, name, parent_id) {
    var id;
    if (parent_id) {
        var parent_code = get_code_for_id(parent_id);
        id = get_id_for_code(parent_code, code);
    } else {
        id = code;
    }
    var node = {
        'id': id,
        'text': name.replace(/_/g,' '),
        'icon': get_icon_for_code(code),
        'state': { 'opened' : true, 'selected' : false }
    };
    return node;
}

function add_jstree_node(parent, node, tree) {
    if(tree.get_node(node)) {
        return node.id;
    }
    var position = 'last';
    function callback(){ };
    function is_loaded(){ };
    return tree.create_node(parent, node, position, callback, is_loaded);
}

function add_children(parent, tree, node_ids_to_recurse) {
    parent.state.opened = true;
    parent.state.selected = true;
    $.get('/get_child_codes?code=' + get_code_for_id(parent.id), function (data) {
        data.forEach(function (item) {
            var node = build_jstree_node(item.code, item.name, parent.id);
            if(tree.get_node(node)) {
                if(tree.is_leaf(node) == false) {
                    node_ids_to_recurse = node_ids_to_recurse ? node_ids_to_recurse : [];
                    node_ids_to_recurse.push(node.id);
                }
                tree.delete_node(node);
            }
            var new_id = add_jstree_node(parent, node, tree);
            if(node_ids_to_recurse && node_ids_to_recurse.includes(new_id)) {
                tree.select_node(node);
                add_children(node, tree);
                node_ids_to_recurse.pop(new_id);
            }
        });
    });
}

function remove_children(parent, tree) {
    parent.state.opened = false;
    $.get('/get_child_codes?code=' + get_code_for_id(parent.id), function (data) {
        data.forEach(function(item) {
            var parent_code = get_code_for_id(parent.id);
            var id = get_id_for_code(parent_code, item.code);
            var node = tree.get_node(id);
            if(node) {
                tree.delete_node(node);
            }
        });
    });
}

function add_parents(child, tree) {
    var current_parent_id = tree.get_parent(child);
    var current_parent = tree.get_node(current_parent_id);
    var grandparent = ('root' == current_parent_id) ? current_parent : tree.get_node(tree.get_parent(current_parent));
    var child_code = get_code_for_id(child.id);
    $.get('/get_parent_codes?code=' + child_code, function (data) {
        data.forEach(function(item) {
            var parent_code = item.code;
            var parent = build_jstree_node(parent_code, item.name, grandparent.id);
            if (tree.get_node(parent) == false) {
                add_jstree_node(grandparent, parent, tree);
                add_children(parent, tree, [get_id_for_code(parent_code, child_code)]);
                // we dispose the child nodes
                if ('root' == current_parent_id) {
                    tree.delete_node(child);
                }
            }
        });
    });
}

function remove_parents(child, tree) {
    var parent_id = tree.get_parent(child);
    var parent = tree.get_node(parent_id);
    if(parent && parent != tree.get_node('root')) {
        tree.delete_node(parent);
    }
}

function get_jstree_context_menu() {
    return {
        "items": function($node) {
            var tree = $("#jstree").jstree(true);
            return {
                "Remove": {
                    "separator_before": false,
                    "separator_after": false,
                    "icon": 'glyphicon glyphicon-minus',
                    "label": "Remove",
                    "action": function (obj) {
                        if ($node !== tree.get_node('root')) {
                            tree.delete_node($node);
                        }
                    }
                },
                "AddChildren": {
                    "separator_before": false,
                    "separator_after": false,
                    "icon": 'glyphicon glyphicon-plus',
                    "label": "Add child concepts",
                    "action": function (obj) {
                        add_children($node, tree);
                    }
                },
                "RemoveChildren": {
                    "separator_before": false,
                    "separator_after": false,
                    "icon": 'glyphicon glyphicon-minus',
                    "label": "Remove child concepts",
                    "action": function (obj) {
                        remove_children($node, tree);
                    }
                },
                "AddParent": {
                    "separator_before": false,
                    "separator_after": false,
                    "icon": 'glyphicon glyphicon-plus',
                    "label": "Add parent concepts",
                    "action": function (obj) {
                        add_parents($node, tree);
                    }
                },
                "RemoveParents": {
                    "separator_before": false,
                    "separator_after": false,
                    "icon": 'glyphicon glyphicon-minus',
                    "label": "Remove parent concepts",
                    "action": function (obj) {
                        remove_parents($node, tree);
                    }
                }
            };
        }
    }
}<commit_msg>Fix equality operators and unnecessary semicolons in ctrp_rest_client/static/js/search_utils.js ctrp_rest_client-14 ctrp_rest_client-13 ctrp_rest_client-12 ctrp_rest_client-11 ctrp_rest_client-10 ctrp_rest_client-9 ctrp_rest_client-8<commit_after>// add pound symbol for jquery element id addressing
function tag_id(id_name) {

    return '#' + id_name;
}

// translate domain (search category such as disease, biomarker, etc.)
// into standardized field ids following the convention:
// disease ->
//      disease_names -> for hidden field holding the concept names as comma delimited string
//      disease_codes -> for hidden field holding the concept codes as comma delimited string
//      diseases      -> for unordered list of selected concept names
function get_fields(dom) {

    return {
        'name_list': tag_id(dom + '_names'),
        'code_list': tag_id(dom + '_codes'),
        'ul_list': tag_id(dom + 's')
    }
}

// load comma delimited list of concept codes from hidden string field and populate an UL
// will callback to retrieve names for the concept codes
// code_list_id - id of the text form field
// ul_list_id - id of the ul list
function refresh_ul_list_from_hidden(dom) {

    var fields = get_fields(dom);
    var names = $(fields.name_list).val().split(', ');
    var codes = $(fields.code_list).val().split(', ');

    $.each(names, function (i, name) {
        if (name !== '') {
            var code = codes[i];
            $(fields.ul_list).append(create_li(code, name, dom));
        }
    });
}

// wrapper to call the remove_term for both names and code hidden fields
function rm_term(code, name, dom) {

    var fields = get_fields(dom);
    // remove ul list item
    $(tag_id(code)).remove();
    // remove name from hidden form field
    remove_term(name, fields.name_list);
    // remove code from hidden form field
    remove_term(code, fields.code_list);
}

// remove term from comma separated list in text field
function remove_term(term, list_id) {

    var hidden_field_value = $(list_id).val();
    var terms = hidden_field_value.split(', ');
    var pos = terms.indexOf(term);
    terms.splice(pos, 1);
    $(list_id).val(terms.join(", "));
}

function _do_code_callback(code, dom, url, reverse) {

    $.get(url + code, function (data) {
        data.forEach(function(entry) {
            var code = entry.code;
            var name = entry.name.replace(/_/g,' ');
            if (reverse) {
                rm_term(code, name, dom);
            } else {
                add_term(code, name, dom);
            }
        });
    });
}

// code is nci thesaurus concept id
// dom is domain either biomarker or disease (or other in the future)
function add_child_terms(code, dom) {
    _do_code_callback(code, dom, 'get_child_codes?code=', false);
}

// code is nci thesaurus concept id
// dom is domain either biomarker or disease (or other in the future)
function rm_child_terms(code, dom) {
    _do_code_callback(code, dom, 'get_child_codes?code=', true);
}

// code is nci thesaurus concept id
// dom is domain either biomarker or disease (or other in the future)
function add_parent_terms(code, dom) {
    _do_code_callback(code, dom, 'get_parent_codes?code=', false);
}

// code is nci thesaurus concept id
// dom is domain either biomarker or disease (or other in the future)
function rm_parent_terms(code, dom) {
    _do_code_callback(code, dom, 'get_parent_codes?code=', true);
}

// generates link for selected code and name
// to be appended in the ul
function create_li(code, name, dom) {

    return '
'
    + 'remove'
    + ' | add child terms'
    + ' | remove child terms'
    + ' | add parent terms'
    + ' | remove parent terms'
    + ' | ' + name + '
';
}

// update the hidden fields for codes and names
// and the unordered list for selected terms
function add_term(code, name, dom) {

    var fields = get_fields(dom);

    if(!$(fields.code_list).val().includes(code)) {
        add_item_to_comma_delimited_list(fields.name_list, name);
        add_item_to_comma_delimited_list(fields.code_list, code);
        $(fields.ul_list).append( create_li(code, name, dom) );
    }
}

// split the comma-delimited string into array
// add item to list if it doesn't already exist in the list
// join it back together and replace the value
function add_item_to_comma_delimited_list(field_name, item) {

    $(field_name).val( $.grep([$(field_name).val(), item], Boolean).join(", ") );
}

function update_tree(code, item, dom) {
    var tree = $('#jstree').jstree(true);
    var root = tree.get_node('root');
    var node = build_jstree_node(code, item, 'root');
    add_jstree_node(root, node, tree);
}

function get_code_for_id(node_id) {
    var codes = node_id.split('*');
    return codes[codes.length-1];
}

function get_id_for_code(parent_code, code) {
    return parent_code ? parent_code + '*' + code : code;
}

function get_icon_for_code(code) {
    return (code === 'root') ? 'glyphicon glyphicon-info-sign' : 'glyphicon glyphicon-check';
}

function build_jstree_node(code, name, parent_id) {
    var id;
    if (parent_id) {
        var parent_code = get_code_for_id(parent_id);
        id = get_id_for_code(parent_code, code);
    } else {
        id = code;
    }
    var node = {
        'id': id,
        'text': name.replace(/_/g,' '),
        'icon': get_icon_for_code(code),
        'state': { 'opened' : true, 'selected' : false }
    };
    return node;
}

function add_jstree_node(parent, node, tree) {
    if(tree.get_node(node)) {
        return node.id;
    }
    var position = 'last';
    function callback(){ }
    function is_loaded(){ }
    return tree.create_node(parent, node, position, callback, is_loaded);
}

function add_children(parent, tree, node_ids_to_recurse) {
    parent.state.opened = true;
    parent.state.selected = true;
    $.get('/get_child_codes?code=' + get_code_for_id(parent.id), function (data) {
        data.forEach(function (item) {
            var node = build_jstree_node(item.code, item.name, parent.id);
            if(tree.get_node(node)) {
                if(tree.is_leaf(node) === false) {
                    node_ids_to_recurse = node_ids_to_recurse ? node_ids_to_recurse : [];
                    node_ids_to_recurse.push(node.id);
                }
                tree.delete_node(node);
            }
            var new_id = add_jstree_node(parent, node, tree);
            if(node_ids_to_recurse && node_ids_to_recurse.includes(new_id)) {
                tree.select_node(node);
                add_children(node, tree);
                node_ids_to_recurse.pop(new_id);
            }
        });
    });
}

function remove_children(parent, tree) {
    parent.state.opened = false;
    $.get('/get_child_codes?code=' + get_code_for_id(parent.id), function (data) {
        data.forEach(function(item) {
            var parent_code = get_code_for_id(parent.id);
            var id = get_id_for_code(parent_code, item.code);
            var node = tree.get_node(id);
            if(node) {
                tree.delete_node(node);
            }
        });
    });
}

function add_parents(child, tree) {
    var current_parent_id = tree.get_parent(child);
    var current_parent = tree.get_node(current_parent_id);
    var grandparent = ('root' === current_parent_id) ? current_parent : tree.get_node(tree.get_parent(current_parent));
    var child_code = get_code_for_id(child.id);
    $.get('/get_parent_codes?code=' + child_code, function (data) {
        data.forEach(function(item) {
            var parent_code = item.code;
            var parent = build_jstree_node(parent_code, item.name, grandparent.id);
            if (tree.get_node(parent) === false) {
                add_jstree_node(grandparent, parent, tree);
                add_children(parent, tree, [get_id_for_code(parent_code, child_code)]);
                // we dispose the child nodes
                if ('root' === current_parent_id) {
                    tree.delete_node(child);
                }
            }
        });
    });
}

function remove_parents(child, tree) {
    var parent_id = tree.get_parent(child);
    var parent = tree.get_node(parent_id);
    if(parent && parent !== tree.get_node('root')) {
        tree.delete_node(parent);
    }
}

function get_jstree_context_menu() {
    return {
        "items": function($node) {
            var tree = $("#jstree").jstree(true);
            return {
                "Remove": {
                    "separator_before": false,
                    "separator_after": false,
                    "icon": 'glyphicon glyphicon-minus',
                    "label": "Remove",
                    "action": function (obj) {
                        if ($node !== tree.get_node('root')) {
                            tree.delete_node($node);
                        }
                    }
                },
                "AddChildren": {
                    "separator_before": false,
                    "separator_after": false,
                    "icon": 'glyphicon glyphicon-plus',
                    "label": "Add child concepts",
                    "action": function (obj) {
                        add_children($node, tree);
                    }
                },
                "RemoveChildren": {
                    "separator_before": false,
                    "separator_after": false,
                    "icon": 'glyphicon glyphicon-minus',
                    "label": "Remove child concepts",
                    "action": function (obj) {
                        remove_children($node, tree);
                    }
                },
                "AddParent": {
                    "separator_before": false,
                    "separator_after": false,
                    "icon": 'glyphicon glyphicon-plus',
                    "label": "Add parent concepts",
                    "action": function (obj) {
                        add_parents($node, tree);
                    }
                },
                "RemoveParents": {
                    "separator_before": false,
                    "separator_after": false,
                    "icon": 'glyphicon glyphicon-minus',
                    "label": "Remove parent concepts",
                    "action": function (obj) {
                        remove_parents($node, tree);
                    }
                }
            };
        }
    }
}<|endoftext|>


<commit_before>/* global L */

/*
 * @class
 * @extends L.Control
 */
L.TimelineSliderControl = L.Control.extend({
  /**
   * @constructor
   * @param {Number} [options.duration=10000] The amount of time a complete
   * playback should take. Not guaranteed; if there's a lot of data or
   * complicated rendering, it will likely wind up taking longer.
   * @param {Boolean} [options.enableKeyboardControls=false] Allow playback to
   * be controlled using the spacebar (play/pause) and right/left arrow keys
   * (next/previous).
   * @param {Boolean} [options.enablePlayback=true] Show playback controls (i.e.
   * prev/play/pause/next).
   * @param {Function} [options.formatOutput] A function which takes the current
   * time value (usually a Unix timestamp) and outputs a string that is
   * displayed beneath the control buttons.
   * @param {Boolean} [options.showTicks=true] Show ticks on the timeline (if
   * the browser supports it).
   * @param {Boolean} [options.waitToUpdateMap=false] Wait until the user is
   * finished changing the date to update the map. By default, both the map and
   * the date update for every change. With complex data, this can slow things
   * down, so set this to true to only update the displayed date.
   * @param {Number} [options.start] The start time of the timeline. If unset,
   * this will be calculated automatically based on the timelines registered to
   * this control.
   * @param {Number} [options.end] The end time of the timeline. If unset, this
   * will be calculated automatically based on the timelines registered to this
   * control.
   */
  initialize(options = {}) {
    const defaultOptions = {
      duration:               10000,
      enableKeyboardControls: false,
      enablePlayback:         true,
      formatOutput:           output => `${output || ''}`,
      showTicks:              true,
      waitToUpdateMap:        false,
      position:               'bottomleft',
      steps:                  1000,
      autoPlay:               false
    };
    this.timelines = [];
    L.Util.setOptions(this, defaultOptions);
    L.Util.setOptions(this, options);
    if (typeof options.start !== 'undefined') {
      this.start = options.start;
    }
    if (typeof options.end !== 'undefined') {
      this.end = options.end;
    }
  },

  /* INTERNAL API *************************************************************/

  /**
   * @private
   * @returns {Number[]} A flat, sorted list of all the times of all layers
   */
  _getTimes() {
    const times = [];
    this.timelines.forEach((timeline) => {
      const timesInRange = timeline.times
        .filter(time => time >= this.start && time <= this.end);
      times.push(...timesInRange);
    });
    if (times.length) {
      times.sort((a, b) => a - b);
      const dedupedTimes = [times[0]];
      times.reduce((a, b) => {
        if (a !== b) {
          dedupedTimes.push(b);
        }
        return b;
      });
      return dedupedTimes;
    }
    return times;
  },

  /**
   * Adjusts start/end/step size/etc. Should be called if any of those might
   * change (e.g. when adding a new layer).
   *
   * @private
   */
  _recalculate() {
    const manualStart = typeof this.options.start !== 'undefined';
    const manualEnd = typeof this.options.end !== 'undefined';
    const duration = this.options.duration;
    let min = Infinity;
    let max = -Infinity;
    this.timelines.forEach((timeline) => {
      if (timeline.start < min) {
        min = timeline.start;
      }
      if (timeline.end > max) {
        max = timeline.end;
      }
    });
    if (!manualStart) {
      this.start = min;
      this._timeSlider.min = min === Infinity ? 0 : min;
      this._timeSlider.value = this._timeSlider.min;
    }
    if (!manualEnd) {
      this.end = max;
      this._timeSlider.max = max === -Infinity ? 0 : max;
    }
    this._stepSize = Math.max(1, (this.end - this.start) / this.options.steps);
    this._stepDuration = Math.max(1, duration / this.options.steps);
  },

  /**
   * If `mode` is 0, finds the event nearest to `findTime`.
   *
   * If `mode` is 1, finds the event immediately after `findTime`.
   *
   * If `mode` is -1, finds the event immediately before `findTime`.
   *
   * @private
   * @param {Number} findTime The time to find events around
   * @param {Number} mode The operating mode. See main function description.
   * @returns {Number} The time of the nearest event.
   */
  _nearestEventTime(findTime, mode = 0) {
    const times = this._getTimes();
    let retNext = false;
    let lastTime = times[0];
    for (let i = 1; i < times.length; i++) {
      const time = times[i];
      if (retNext) {
        return time;
      }
      if (time >= findTime) {
        if (mode === -1) {
          return lastTime;
        }
        // else if (mode === 1) {
        if (time === findTime) {
          retNext = true;
        } else {
          return time;
        }
        // }
        // this isn't actually used anywhere, and it's a private method
        // so .. commenting out
        // else {
        //   const prevDiff = Math.abs(findTime - lastTime);
        //   const nextDiff = Math.abs(findTime - time);
        //   return prevDiff < nextDiff ? lastTime : time;
        // }
      }
      lastTime = time;
    }
    return lastTime;
  },

  /* DOM CREATION & INTERACTION ***********************************************/

  /**
   * Create all of the DOM for the control.
   *
   * @private
   */
  _createDOM() {
    const classes = [
      'leaflet-control-layers',
      'leaflet-control-layers-expanded',
      'leaflet-timeline-control',
    ];
    const container = L.DomUtil.create('div', classes.join(' '));
    this.container = container;
    if (this.options.enablePlayback) {
      const sliderCtrlC = L.DomUtil.create(
        'div',
        'sldr-ctrl-container',
        container,
      );
      const buttonContainer = L.DomUtil.create(
        'div',
        'button-container',
        sliderCtrlC,
      );
      this._makeButtons(buttonContainer);
      if (this.options.enableKeyboardControls) {
        this._addKeyListeners();
      }
      this._makeOutput(sliderCtrlC);
    }
    this._makeSlider(container);
    if (this.options.showTicks) {
      this._buildDataList(container);
    }
    if (this.options.autoPlay) {
      this._autoPlay();
    }
  },

  /**
   * Autoplay when loaded
   *
   * @private
   */
  _autoPlay() {
    if (document.readyState == 'loading') {
      window.addEventListener('load', e => this._autoPlay());
    } else {
      this.play();
    }
  },

  /**
   * Add keyboard listeners for keyboard control
   *
   * @private
   */
  _addKeyListeners() {
    this._listener = (...args) => this._onKeydown(...args);
    document.addEventListener('keydown', this._listener);
  },

  /**
   * Remove keyboard listeners
   *
   * @private
   */
  _removeKeyListeners() {
    document.removeEventListener('keydown', this._listener);
  },

  /**
   * Constructs a <datalist>, for showing ticks on the range input.
   *
   * @private
   * @param {HTMLElement} container The container to which to add the datalist
   */
  _buildDataList(container) {
    this._datalist = L.DomUtil.create('datalist', '', container);
    const idNum = Math.floor(Math.random() * 1000000);
    this._datalist.id = `timeline-datalist-${idNum}`;
    this._timeSlider.setAttribute('list', this._datalist.id);
    this._rebuildDataList();
  },

  /**
   * Reconstructs the <datalist>. Should be called when new data comes in.
   */
  _rebuildDataList() {
    const datalist = this._datalist;
    while (datalist.firstChild) {
      datalist.removeChild(datalist.firstChild);
    }
    const datalistSelect = L.DomUtil.create('select', '', this._datalist);
    this._getTimes().forEach((time) => {
      L.DomUtil.create('option', '', datalistSelect).value = time;
    });
  },

  /**
   * Makes a button with the passed name as a class, which calls the
   * corresponding function when clicked. Attaches the button to container.
   *
   * @private
   * @param {HTMLElement} container The container to which to add the button
   * @param {String} name The class to give the button and the function to call
   */
  _makeButton(container, name) {
    const button = L.DomUtil.create('button', name, container);
    button.addEventListener('click', () => this[name]());
    L.DomEvent.disableClickPropagation(button);
  },

  /**
   * Makes the prev, play, pause, and next buttons
   *
   * @private
   * @param {HTMLElement} container The container to which to add the buttons
   */
  _makeButtons(container) {
    this._makeButton(container, 'prev');
    this._makeButton(container, 'play');
    this._makeButton(container, 'pause');
    this._makeButton(container, 'next');
  },

  /**
   * DOM event handler to disable dragging on map
   *
   * @private
   */
  _disableMapDragging() {
    this.map.dragging.disable();
  },

  /**
   * DOM event handler to enable dragging on map
   *
   * @private
   */
  _enableMapDragging() {
    this.map.dragging.enable();
  },

  /**
   * Creates the range input
   *
   * @private
   * @param {HTMLElement} container The container to which to add the input
   */
  _makeSlider(container) {
    const slider = L.DomUtil.create('input', 'time-slider', container);
    slider.type = 'range';
    slider.min = this.start || 0;
    slider.max = this.end || 0;
    slider.value = this.start || 0;
    this._timeSlider = slider;
    // register events using leaflet for easy removal
    L.DomEvent.on(this._timeSlider, 'change input', this._sliderChanged, this);
    L.DomEvent.on(this._timeSlider, 'pointerdown mousedown touchstart', this._disableMapDragging, this);
    L.DomEvent.on(document, 'pointerup mouseup touchend', this._enableMapDragging, this);
  },

  _makeOutput(container) {
    this._output = L.DomUtil.create('output', 'time-text', container);
    this._output.innerHTML = this.options.formatOutput(this.start);
  },

  _onKeydown(e) {
    switch (e.keyCode || e.which) {
      case 37: this.prev(); break;
      case 39: this.next(); break;
      case 32: this.toggle(); break;
      default: return;
    }
    e.preventDefault();
  },

  _sliderChanged(e) {
    const time = parseFloat(+e.target.value, 10);
    this.time = time;
    if (!this.options.waitToUpdateMap || e.type === 'change') {
      this.timelines.forEach(timeline => timeline.setTime(time));
    }
    if (this._output) {
      this._output.innerHTML = this.options.formatOutput(time);
    }
  },

  _resetIfTimelinesChanged(oldTimelineCount) {
    if (this.timelines.length !== oldTimelineCount) {
      this._recalculate();
      if (this.options.showTicks) {
        this._rebuildDataList();
      }
      this.setTime(this.start);
    }
  },

  /* EXTERNAL API *************************************************************/

  /**
   * Register timeline layers with this control. This could change the start and
   * end points of the timeline (unless manually set). It will also reset the
   * playback.
   *
   * @param {...L.Timeline} timelines The `L.Timeline`s to register
   */
  addTimelines(...timelines) {
    this.pause();
    const timelineCount = this.timelines.length;
    timelines.forEach((timeline) => {
      if (this.timelines.indexOf(timeline) === -1) {
        this.timelines.push(timeline);
      }
    });
    this._resetIfTimelinesChanged(timelineCount);
  },

  /**
   * Unregister timeline layers with this control. This could change the start
   * and end points of the timeline unless manually set. It will also reset the
   * playback.
   *
   * @param {...L.Timeline} timelines The `L.Timeline`s to unregister
   */
  removeTimelines(...timelines) {
    this.pause();
    const timelineCount = this.timelines.length;
    timelines.forEach((timeline) => {
      const index = this.timelines.indexOf(timeline);
      if (index !== -1) {
        this.timelines.splice(index, 1);
      }
    });
    this._resetIfTimelinesChanged(timelineCount);
  },

  /**
   * Toggles play/pause state.
   */
  toggle() {
    if (this._playing) {
      this.pause();
    } else {
      this.play();
    }
  },

  /**
   * Pauses playback and goes to the previous event.
   */
  prev() {
    this.pause();
    const prevTime = this._nearestEventTime(this.time, -1);
    this._timeSlider.value = prevTime;
    this.setTime(prevTime);
  },

  /**
   * Pauses playback.
   */
  pause(fromSynced) {
    clearTimeout(this._timer);
    this._playing = false;
    this.container.classList.remove('playing');

    if (this.syncedControl && !fromSynced) {
      this.syncedControl.map(function (control) {
        control.pause(true);
      })
    }
  },

  /**
   * Starts playback.
   */
  play(fromSynced) {
    clearTimeout(this._timer);
    if (parseFloat(this._timeSlider.value, 10) === this.end) {
      this._timeSlider.value = this.start;
    }
    this._timeSlider.value = parseFloat(this._timeSlider.value, 10) +
      this._stepSize;
    this.setTime(this._timeSlider.value);
    if (parseFloat(this._timeSlider.value, 10) === this.end) {
      this._playing = false;
      this.container.classList.remove('playing');
    } else {
      this._playing = true;
      this.container.classList.add('playing');
      this._timer = setTimeout(() => this.play(true), this._stepDuration);
    }

    if (this.syncedControl && !fromSynced) {
      this.syncedControl.map(function (control) {
        control.play(true);
      });
    }
  },

  /**
   * Pauses playback and goes to the next event.
   */
  next() {
    this.pause();
    const nextTime = this._nearestEventTime(this.time, 1);
    this._timeSlider.value = nextTime;
    this.setTime(nextTime);
  },

  /**
   * Set the time displayed.
   *
   * @param {Number} time The time to set
   */
  setTime(time) {
    if (this._timeSlider) this._timeSlider.value = +time;
    this._sliderChanged({
      type:   'change',
      target: { value: time },
    });
  },

  onAdd(map) {
    this.map = map;
    this._createDOM();
    this.setTime(this.start);
    return this.container;
  },

  onRemove() {
    /* istanbul ignore else */
    if (this.options.enableKeyboardControls) {
      this._removeKeyListeners();
    }
    // cleanup events registered in _makeSlider
    L.DomEvent.off(this._timeSlider, 'change input', this._sliderChanged, this);
    L.DomEvent.off(this._timeSlider, 'pointerdown mousedown touchstart', this._disableMapDragging, this);
    L.DomEvent.off(document, 'pointerup mouseup touchend', this._enableMapDragging, this);
    // make sure that dragging is restored to enabled state
    this._enableMapDragging();
  },

  syncControl(controlToSync) {
    if (!this.syncedControl) {
      this.syncedControl = [];
    }
    this.syncedControl.push(syncedControl);
  }
});

L.timelineSliderControl = (timeline, start, end, timelist) =>
  new L.TimelineSliderControl(timeline, start, end, timelist);
<commit_msg>Added aria-labels for slider controls<commit_after>/* global L */

/*
 * @class
 * @extends L.Control
 */
L.TimelineSliderControl = L.Control.extend({
  /**
   * @constructor
   * @param {Number} [options.duration=10000] The amount of time a complete
   * playback should take. Not guaranteed; if there's a lot of data or
   * complicated rendering, it will likely wind up taking longer.
   * @param {Boolean} [options.enableKeyboardControls=false] Allow playback to
   * be controlled using the spacebar (play/pause) and right/left arrow keys
   * (next/previous).
   * @param {Boolean} [options.enablePlayback=true] Show playback controls (i.e.
   * prev/play/pause/next).
   * @param {Function} [options.formatOutput] A function which takes the current
   * time value (usually a Unix timestamp) and outputs a string that is
   * displayed beneath the control buttons.
   * @param {Boolean} [options.showTicks=true] Show ticks on the timeline (if
   * the browser supports it).
   * @param {Boolean} [options.waitToUpdateMap=false] Wait until the user is
   * finished changing the date to update the map. By default, both the map and
   * the date update for every change. With complex data, this can slow things
   * down, so set this to true to only update the displayed date.
   * @param {Number} [options.start] The start time of the timeline. If unset,
   * this will be calculated automatically based on the timelines registered to
   * this control.
   * @param {Number} [options.end] The end time of the timeline. If unset, this
   * will be calculated automatically based on the timelines registered to this
   * control.
   */
  initialize(options = {}) {
    const defaultOptions = {
      duration:               10000,
      enableKeyboardControls: false,
      enablePlayback:         true,
      formatOutput:           output => `${output || ''}`,
      showTicks:              true,
      waitToUpdateMap:        false,
      position:               'bottomleft',
      steps:                  1000,
      autoPlay:               false
    };
    this.timelines = [];
    L.Util.setOptions(this, defaultOptions);
    L.Util.setOptions(this, options);
    if (typeof options.start !== 'undefined') {
      this.start = options.start;
    }
    if (typeof options.end !== 'undefined') {
      this.end = options.end;
    }
  },

  /* INTERNAL API *************************************************************/

  /**
   * @private
   * @returns {Number[]} A flat, sorted list of all the times of all layers
   */
  _getTimes() {
    const times = [];
    this.timelines.forEach((timeline) => {
      const timesInRange = timeline.times
        .filter(time => time >= this.start && time <= this.end);
      times.push(...timesInRange);
    });
    if (times.length) {
      times.sort((a, b) => a - b);
      const dedupedTimes = [times[0]];
      times.reduce((a, b) => {
        if (a !== b) {
          dedupedTimes.push(b);
        }
        return b;
      });
      return dedupedTimes;
    }
    return times;
  },

  /**
   * Adjusts start/end/step size/etc. Should be called if any of those might
   * change (e.g. when adding a new layer).
   *
   * @private
   */
  _recalculate() {
    const manualStart = typeof this.options.start !== 'undefined';
    const manualEnd = typeof this.options.end !== 'undefined';
    const duration = this.options.duration;
    let min = Infinity;
    let max = -Infinity;
    this.timelines.forEach((timeline) => {
      if (timeline.start < min) {
        min = timeline.start;
      }
      if (timeline.end > max) {
        max = timeline.end;
      }
    });
    if (!manualStart) {
      this.start = min;
      this._timeSlider.min = min === Infinity ? 0 : min;
      this._timeSlider.value = this._timeSlider.min;
    }
    if (!manualEnd) {
      this.end = max;
      this._timeSlider.max = max === -Infinity ? 0 : max;
    }
    this._stepSize = Math.max(1, (this.end - this.start) / this.options.steps);
    this._stepDuration = Math.max(1, duration / this.options.steps);
  },

  /**
   * If `mode` is 0, finds the event nearest to `findTime`.
   *
   * If `mode` is 1, finds the event immediately after `findTime`.
   *
   * If `mode` is -1, finds the event immediately before `findTime`.
   *
   * @private
   * @param {Number} findTime The time to find events around
   * @param {Number} mode The operating mode. See main function description.
   * @returns {Number} The time of the nearest event.
   */
  _nearestEventTime(findTime, mode = 0) {
    const times = this._getTimes();
    let retNext = false;
    let lastTime = times[0];
    for (let i = 1; i < times.length; i++) {
      const time = times[i];
      if (retNext) {
        return time;
      }
      if (time >= findTime) {
        if (mode === -1) {
          return lastTime;
        }
        // else if (mode === 1) {
        if (time === findTime) {
          retNext = true;
        } else {
          return time;
        }
        // }
        // this isn't actually used anywhere, and it's a private method
        // so .. commenting out
        // else {
        //   const prevDiff = Math.abs(findTime - lastTime);
        //   const nextDiff = Math.abs(findTime - time);
        //   return prevDiff < nextDiff ? lastTime : time;
        // }
      }
      lastTime = time;
    }
    return lastTime;
  },

  /* DOM CREATION & INTERACTION ***********************************************/

  /**
   * Create all of the DOM for the control.
   *
   * @private
   */
  _createDOM() {
    const classes = [
      'leaflet-control-layers',
      'leaflet-control-layers-expanded',
      'leaflet-timeline-control',
    ];
    const container = L.DomUtil.create('div', classes.join(' '));
    this.container = container;
    if (this.options.enablePlayback) {
      const sliderCtrlC = L.DomUtil.create(
        'div',
        'sldr-ctrl-container',
        container,
      );
      const buttonContainer = L.DomUtil.create(
        'div',
        'button-container',
        sliderCtrlC,
      );
      this._makeButtons(buttonContainer);
      if (this.options.enableKeyboardControls) {
        this._addKeyListeners();
      }
      this._makeOutput(sliderCtrlC);
    }
    this._makeSlider(container);
    if (this.options.showTicks) {
      this._buildDataList(container);
    }
    if (this.options.autoPlay) {
      this._autoPlay();
    }
  },

  /**
   * Autoplay when loaded
   *
   * @private
   */
  _autoPlay() {
    if (document.readyState == 'loading') {
      window.addEventListener('load', e => this._autoPlay());
    } else {
      this.play();
    }
  },

  /**
   * Add keyboard listeners for keyboard control
   *
   * @private
   */
  _addKeyListeners() {
    this._listener = (...args) => this._onKeydown(...args);
    document.addEventListener('keydown', this._listener);
  },

  /**
   * Remove keyboard listeners
   *
   * @private
   */
  _removeKeyListeners() {
    document.removeEventListener('keydown', this._listener);
  },

  /**
   * Constructs a <datalist>, for showing ticks on the range input.
   *
   * @private
   * @param {HTMLElement} container The container to which to add the datalist
   */
  _buildDataList(container) {
    this._datalist = L.DomUtil.create('datalist', '', container);
    const idNum = Math.floor(Math.random() * 1000000);
    this._datalist.id = `timeline-datalist-${idNum}`;
    this._timeSlider.setAttribute('list', this._datalist.id);
    this._rebuildDataList();
  },

  /**
   * Reconstructs the <datalist>. Should be called when new data comes in.
   */
  _rebuildDataList() {
    const datalist = this._datalist;
    while (datalist.firstChild) {
      datalist.removeChild(datalist.firstChild);
    }
    const datalistSelect = L.DomUtil.create('select', '', this._datalist);
    datalistSelect.setAttribute('aria-label', 'List of times');
    this._getTimes().forEach((time) => {
      L.DomUtil.create('option', '', datalistSelect).value = time;
    });
  },

  /**
   * Makes a button with the passed name as a class, which calls the
   * corresponding function when clicked. Attaches the button to container.
   *
   * @private
   * @param {HTMLElement} container The container to which to add the button
   * @param {String} name The class to give the button and the function to call
   */
  _makeButton(container, name) {
    const button = L.DomUtil.create('button', name, container);
    button.setAttribute('aria-label', name);
    button.addEventListener('click', () => this[name]());
    L.DomEvent.disableClickPropagation(button);
  },

  /**
   * Makes the prev, play, pause, and next buttons
   *
   * @private
   * @param {HTMLElement} container The container to which to add the buttons
   */
  _makeButtons(container) {
    this._makeButton(container, 'prev');
    this._makeButton(container, 'play');
    this._makeButton(container, 'pause');
    this._makeButton(container, 'next');
  },

  /**
   * DOM event handler to disable dragging on map
   *
   * @private
   */
  _disableMapDragging() {
    this.map.dragging.disable();
  },

  /**
   * DOM event handler to enable dragging on map
   *
   * @private
   */
  _enableMapDragging() {
    this.map.dragging.enable();
  },

  /**
   * Creates the range input
   *
   * @private
   * @param {HTMLElement} container The container to which to add the input
   */
  _makeSlider(container) {
    const slider = L.DomUtil.create('input', 'time-slider', container);
    slider.setAttribute('aria-label', 'Slider');
    slider.type = 'range';
    slider.min = this.start || 0;
    slider.max = this.end || 0;
    slider.value = this.start || 0;
    this._timeSlider = slider;
    // register events using leaflet for easy removal
    L.DomEvent.on(this._timeSlider, 'change input', this._sliderChanged, this);
    L.DomEvent.on(this._timeSlider, 'pointerdown mousedown touchstart', this._disableMapDragging, this);
    L.DomEvent.on(document, 'pointerup mouseup touchend', this._enableMapDragging, this);
  },

  _makeOutput(container) {
    this._output = L.DomUtil.create('output', 'time-text', container);
    this._output.innerHTML = this.options.formatOutput(this.start);
  },

  _onKeydown(e) {
    switch (e.keyCode || e.which) {
      case 37: this.prev(); break;
      case 39: this.next(); break;
      case 32: this.toggle(); break;
      default: return;
    }
    e.preventDefault();
  },

  _sliderChanged(e) {
    const time = parseFloat(+e.target.value, 10);
    this.time = time;
    if (!this.options.waitToUpdateMap || e.type === 'change') {
      this.timelines.forEach(timeline => timeline.setTime(time));
    }
    if (this._output) {
      this._output.innerHTML = this.options.formatOutput(time);
    }
  },

  _resetIfTimelinesChanged(oldTimelineCount) {
    if (this.timelines.length !== oldTimelineCount) {
      this._recalculate();
      if (this.options.showTicks) {
        this._rebuildDataList();
      }
      this.setTime(this.start);
    }
  },

  /* EXTERNAL API *************************************************************/

  /**
   * Register timeline layers with this control. This could change the start and
   * end points of the timeline (unless manually set). It will also reset the
   * playback.
   *
   * @param {...L.Timeline} timelines The `L.Timeline`s to register
   */
  addTimelines(...timelines) {
    this.pause();
    const timelineCount = this.timelines.length;
    timelines.forEach((timeline) => {
      if (this.timelines.indexOf(timeline) === -1) {
        this.timelines.push(timeline);
      }
    });
    this._resetIfTimelinesChanged(timelineCount);
  },

  /**
   * Unregister timeline layers with this control. This could change the start
   * and end points of the timeline unless manually set. It will also reset the
   * playback.
   *
   * @param {...L.Timeline} timelines The `L.Timeline`s to unregister
   */
  removeTimelines(...timelines) {
    this.pause();
    const timelineCount = this.timelines.length;
    timelines.forEach((timeline) => {
      const index = this.timelines.indexOf(timeline);
      if (index !== -1) {
        this.timelines.splice(index, 1);
      }
    });
    this._resetIfTimelinesChanged(timelineCount);
  },

  /**
   * Toggles play/pause state.
   */
  toggle() {
    if (this._playing) {
      this.pause();
    } else {
      this.play();
    }
  },

  /**
   * Pauses playback and goes to the previous event.
   */
  prev() {
    this.pause();
    const prevTime = this._nearestEventTime(this.time, -1);
    this._timeSlider.value = prevTime;
    this.setTime(prevTime);
  },

  /**
   * Pauses playback.
   */
  pause(fromSynced) {
    clearTimeout(this._timer);
    this._playing = false;
    this.container.classList.remove('playing');

    if (this.syncedControl && !fromSynced) {
      this.syncedControl.map(function (control) {
        control.pause(true);
      })
    }
  },

  /**
   * Starts playback.
   */
  play(fromSynced) {
    clearTimeout(this._timer);
    if (parseFloat(this._timeSlider.value, 10) === this.end) {
      this._timeSlider.value = this.start;
    }
    this._timeSlider.value = parseFloat(this._timeSlider.value, 10) +
      this._stepSize;
    this.setTime(this._timeSlider.value);
    if (parseFloat(this._timeSlider.value, 10) === this.end) {
      this._playing = false;
      this.container.classList.remove('playing');
    } else {
      this._playing = true;
      this.container.classList.add('playing');
      this._timer = setTimeout(() => this.play(true), this._stepDuration);
    }

    if (this.syncedControl && !fromSynced) {
      this.syncedControl.map(function (control) {
        control.play(true);
      });
    }
  },

  /**
   * Pauses playback and goes to the next event.
   */
  next() {
    this.pause();
    const nextTime = this._nearestEventTime(this.time, 1);
    this._timeSlider.value = nextTime;
    this.setTime(nextTime);
  },

  /**
   * Set the time displayed.
   *
   * @param {Number} time The time to set
   */
  setTime(time) {
    if (this._timeSlider) this._timeSlider.value = +time;
    this._sliderChanged({
      type:   'change',
      target: { value: time },
    });
  },

  onAdd(map) {
    this.map = map;
    this._createDOM();
    this.setTime(this.start);
    return this.container;
  },

  onRemove() {
    /* istanbul ignore else */
    if (this.options.enableKeyboardControls) {
      this._removeKeyListeners();
    }
    // cleanup events registered in _makeSlider
    L.DomEvent.off(this._timeSlider, 'change input', this._sliderChanged, this);
    L.DomEvent.off(this._timeSlider, 'pointerdown mousedown touchstart', this._disableMapDragging, this);
    L.DomEvent.off(document, 'pointerup mouseup touchend', this._enableMapDragging, this);
    // make sure that dragging is restored to enabled state
    this._enableMapDragging();
  },

  syncControl(controlToSync) {
    if (!this.syncedControl) {
      this.syncedControl = [];
    }
    this.syncedControl.push(syncedControl);
  }
});

L.timelineSliderControl = (timeline, start, end, timelist) =>
  new L.TimelineSliderControl(timeline, start, end, timelist);
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import theme from './theme.css';

import Box from '../box';
import { TextSmall } from '../typography';

class ProgressStep extends PureComponent {
  render() {
    const { label, active, completed, onClick } = this.props;
    const classNames = cx(theme['step'], {
      [theme['is-active']]: active,
      [theme['is-completed']]: completed,
      [theme['is-clickable']]: onClick,
    });
    return (
      <Box className={classNames} onClick={onClick}>
        <TextSmall className={theme['step-label']}>{label}</TextSmall>

      </Box>
    );
  }
}

ProgressStep.propTypes = {
  /** The label for the progress step */
  label: PropTypes.string.isRequired,
  /** Whether or not the step is active */
  active: PropTypes.bool.isRequired,
  /** Whether or not the step has been completed */
  completed: PropTypes.bool.isRequired,
  /** Callback function that is fired when the progress step is clicked */
  onClick: PropTypes.func,
};

ProgressStep.defaultProps = {
  active: false,
  completed: false,
};

export default ProgressStep;
<commit_msg>Move the onClick from the wrapper to the status bullet<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import theme from './theme.css';

import Box from '../box';
import { TextSmall } from '../typography';

class ProgressStep extends PureComponent {
  render() {
    const { label, active, completed, onClick } = this.props;
    const classNames = cx(theme['step'], {
      [theme['is-active']]: active,
      [theme['is-completed']]: completed,
      [theme['is-clickable']]: onClick,
    });
    return (
      <Box className={classNames}>
        <TextSmall className={theme['step-label']}>{label}</TextSmall>

      </Box>
    );
  }
}

ProgressStep.propTypes = {
  /** The label for the progress step */
  label: PropTypes.string.isRequired,
  /** Whether or not the step is active */
  active: PropTypes.bool.isRequired,
  /** Whether or not the step has been completed */
  completed: PropTypes.bool.isRequired,
  /** Callback function that is fired when the progress step is clicked */
  onClick: PropTypes.func,
};

ProgressStep.defaultProps = {
  active: false,
  completed: false,
};

export default ProgressStep;
<|endoftext|>


<commit_before>'use strict';

const spawnSync = require('child_process').spawnSync;
const fs = require('fs');
const path = require('path');

const conf = {};
const confFile = './conf.yml';
const confUiFile = './patternlab-config.json';
const enc = 'utf8';
const fepperPath = path.resolve('node_modules', 'fepper');
const prefFile = './pref.yml';

let extendDir;
let sourceDir;

if (fs.existsSync(confFile)) {
  const confStr = fs.readFileSync(confFile, enc);
  const confLines = confStr.split('\n');

  for (let confLine of confLines) {
    const keyVal = confLine.split(':');

    if (keyVal[0].trim() === 'extend_dir' && keyVal[1]) {
      extendDir = keyVal[1].trim();
    }
  }
}
// confFile may not exists at this point of installation. Hard-code in that case.
else {
  extendDir = 'extend';
}

if (fs.existsSync(confUiFile)) {
  const confUiStr = fs.readFileSync(confUiFile, enc);

  try {
    conf.ui = JSON.parse(confUiStr);
  }
  catch (err) {
    throw err;
  }

  if (conf.ui) {
    sourceDir = conf.ui.paths.source.root;
  }
}
// confUiFile may not exists at this point of installation. Hard-code in that case.
else {
  sourceDir = 'source';
}

// Create empty extend and source dirs so the postinstall script doesn't write the main profile there.
const existsAlreadyExtendDir = fs.existsSync(extendDir);
const existsAlreadySourceDir = fs.existsSync(sourceDir);

if (!existsAlreadyExtendDir) {
  fs.mkdirSync(extendDir);
}

if (!existsAlreadySourceDir) {
  fs.mkdirSync(sourceDir);
  fs.mkdirSync(`${sourceDir}/_data`);
  fs.mkdirSync(`${sourceDir}/_patterns`);
  fs.mkdirSync(`${sourceDir}/_styles`);
  fs.writeFileSync(`${sourceDir}/_data/data.json`, '{}');
  fs.writeFileSync(`${sourceDir}/_data/listitems.json`, '{}');
}

// Output to install.log.
const installLog = 'install.log';
let spawnedObj;

fs.writeFileSync(installLog, '');

// Only run npm install if not already installed.
if (!fs.existsSync('node_modules')) {
  let binNpm = 'npm';

  // Spawn npm.cmd if Windows and not BASH.
  if (process.env.ComSpec && process.env.ComSpec.toLowerCase() === 'c:\\windows\\system32\\cmd.exe') {
    binNpm = 'npm.cmd';
  }

  spawnedObj = spawnSync(binNpm, ['install', '--ignore-scripts'], {stdio: 'inherit'});
}

if (spawnedObj && spawnedObj.stderr) {
  fs.appendFileSync(installLog, `${spawnedObj.stderr}\n`);
}

if (!fs.existsSync(confFile)) {
  fs.copyFileSync(path.resolve(fepperPath, 'excludes', 'conf.yml'), confFile);
}

if (!fs.existsSync(confUiFile)) {
  fs.copyFileSync(path.resolve(fepperPath, 'excludes', 'patternlab-config.json'), confUiFile);
}

if (!fs.existsSync(prefFile)) {
  fs.copyFileSync(path.resolve(fepperPath, 'excludes', 'pref.yml'), prefFile);
}

// Delete placeholder source and extend dirs so new ones can be copied over.
if (!existsAlreadySourceDir) {
  fs.unlinkSync(`${sourceDir}/_data/listitems.json`);
  fs.unlinkSync(`${sourceDir}/_data/data.json`);
  fs.rmdirSync(`${sourceDir}/_styles`);
  fs.rmdirSync(`${sourceDir}/_patterns`);
  fs.rmdirSync(`${sourceDir}/_data`);
  fs.rmdirSync(sourceDir);
}

if (!existsAlreadyExtendDir) {
  fs.rmdirSync(extendDir);
}

const binPath = path.resolve('node_modules', '.bin');
const winGulp = path.resolve(binPath, 'gulp.cmd');

let binGulp = path.resolve(binPath, 'gulp');

// Spawn gulp.cmd if Windows and not BASH.
if (process.env.ComSpec && process.env.ComSpec.toLowerCase() === 'c:\\windows\\system32\\cmd.exe') {
  binGulp = winGulp;
}

// Copy over the base profile source dir.
const spawnedObj1 =
  spawnSync(binGulp, ['--gulpfile', path.resolve(fepperPath, 'tasker.js'), 'install:copy-base'], {stdio: 'inherit'});

if (spawnedObj1.stderr) {
  fs.appendFileSync(installLog, `${spawnedObj1.stderr}\n`);
}

// Complete installation. run/install.js handles its own logging so no need to log here.
spawnSync('node', [path.resolve('run', 'install.js')], {stdio: 'inherit'});
<commit_msg>no longer clearing out install.log<commit_after>'use strict';

const spawnSync = require('child_process').spawnSync;
const fs = require('fs');
const path = require('path');

const conf = {};
const confFile = './conf.yml';
const confUiFile = './patternlab-config.json';
const enc = 'utf8';
const fepperPath = path.resolve('node_modules', 'fepper');
const prefFile = './pref.yml';

let extendDir;
let sourceDir;

if (fs.existsSync(confFile)) {
  const confStr = fs.readFileSync(confFile, enc);
  const confLines = confStr.split('\n');

  for (let confLine of confLines) {
    const keyVal = confLine.split(':');

    if (keyVal[0].trim() === 'extend_dir' && keyVal[1]) {
      extendDir = keyVal[1].trim();
    }
  }
}
// confFile may not exists at this point of installation. Hard-code in that case.
else {
  extendDir = 'extend';
}

if (fs.existsSync(confUiFile)) {
  const confUiStr = fs.readFileSync(confUiFile, enc);

  try {
    conf.ui = JSON.parse(confUiStr);
  }
  catch (err) {
    throw err;
  }

  if (conf.ui) {
    sourceDir = conf.ui.paths.source.root;
  }
}
// confUiFile may not exists at this point of installation. Hard-code in that case.
else {
  sourceDir = 'source';
}

// Create empty extend and source dirs so the postinstall script doesn't write the main profile there.
const existsAlreadyExtendDir = fs.existsSync(extendDir);
const existsAlreadySourceDir = fs.existsSync(sourceDir);

if (!existsAlreadyExtendDir) {
  fs.mkdirSync(extendDir);
}

if (!existsAlreadySourceDir) {
  fs.mkdirSync(sourceDir);
  fs.mkdirSync(`${sourceDir}/_data`);
  fs.mkdirSync(`${sourceDir}/_patterns`);
  fs.mkdirSync(`${sourceDir}/_styles`);
  fs.writeFileSync(`${sourceDir}/_data/data.json`, '{}');
  fs.writeFileSync(`${sourceDir}/_data/listitems.json`, '{}');
}

// Output to install.log.
const installLog = 'install.log';
let spawnedObj;

// Only run npm install if not already installed.
if (!fs.existsSync('node_modules')) {
  let binNpm = 'npm';

  // Spawn npm.cmd if Windows and not BASH.
  if (process.env.ComSpec && process.env.ComSpec.toLowerCase() === 'c:\\windows\\system32\\cmd.exe') {
    binNpm = 'npm.cmd';
  }

  spawnedObj = spawnSync(binNpm, ['install', '--ignore-scripts'], {stdio: 'inherit'});
}

if (spawnedObj && spawnedObj.stderr) {
  fs.appendFileSync(installLog, `${spawnedObj.stderr}\n`);
}

if (!fs.existsSync(confFile)) {
  fs.copyFileSync(path.resolve(fepperPath, 'excludes', 'conf.yml'), confFile);
}

if (!fs.existsSync(confUiFile)) {
  fs.copyFileSync(path.resolve(fepperPath, 'excludes', 'patternlab-config.json'), confUiFile);
}

if (!fs.existsSync(prefFile)) {
  fs.copyFileSync(path.resolve(fepperPath, 'excludes', 'pref.yml'), prefFile);
}

// Delete placeholder source and extend dirs so new ones can be copied over.
if (!existsAlreadySourceDir) {
  fs.unlinkSync(`${sourceDir}/_data/listitems.json`);
  fs.unlinkSync(`${sourceDir}/_data/data.json`);
  fs.rmdirSync(`${sourceDir}/_styles`);
  fs.rmdirSync(`${sourceDir}/_patterns`);
  fs.rmdirSync(`${sourceDir}/_data`);
  fs.rmdirSync(sourceDir);
}

if (!existsAlreadyExtendDir) {
  fs.rmdirSync(extendDir);
}

const binPath = path.resolve('node_modules', '.bin');
const winGulp = path.resolve(binPath, 'gulp.cmd');

let binGulp = path.resolve(binPath, 'gulp');

// Spawn gulp.cmd if Windows and not BASH.
if (process.env.ComSpec && process.env.ComSpec.toLowerCase() === 'c:\\windows\\system32\\cmd.exe') {
  binGulp = winGulp;
}

// Copy over the base profile source dir.
const spawnedObj1 =
  spawnSync(binGulp, ['--gulpfile', path.resolve(fepperPath, 'tasker.js'), 'install:copy-base'], {stdio: 'inherit'});

if (spawnedObj1.stderr) {
  fs.appendFileSync(installLog, `${spawnedObj1.stderr}\n`);
}

// Complete installation. run/install.js handles its own logging so no need to log here.
spawnSync('node', [path.resolve('run', 'install.js')], {stdio: 'inherit'});
<|endoftext|>


<commit_before>var Item = require('../item');
var assert = require('assert');
var Vec3 = require('vec3').Vec3;
var windows = require('../windows');

module.exports = inject;

// ms to wait before clicking on a tool so the server can send the new
// damage information
var DIG_CLICK_TIMEOUT = 500;

function inject(bot) {
  var nextActionNumber = 0;
  var windowClickQueue = [];

  // 0-8, null = uninitialized
  // which quick bar slot is selected
  bot.quickBarSlot = null;
  bot.inventory = new windows.InventoryWindow(0, "Inventory", 44);
  bot.currentWindow = null;
  bot.heldItem = null;

  function activateItem() {
    bot._client.write('block_place', {
      location: new Vec3(-1, 255, -1),
      direction: -1,
      heldItem: Item.toNotch(bot.heldItem),
      cursorX: -1,
      cursorY: -1,
      cursorZ: -1
    });
  }

  function deactivateItem() {
    bot._client.write('block_dig', {
      status: 5,
      location: new Vec3(0, 0, 0),
      face: 5
    });
  }

  function putSelectedItemRange(start, end, window, slot, cb) {
    // put the selected item back indow the slot range in window

    // try to put it in an item that already exists and just increase
    // the count.
    tryToJoin();

    function tryToJoin() {
      if(!window.selectedItem) {
        cb();
        return;
      }
      var item = window.findItemRange(start, end, window.selectedItem.type,
        window.selectedItem.metadata, true);
      if(item) {
        clickWindow(item.slot, 0, 0, onClick);
      } else {
        tryToFindEmpty();
      }

      function onClick(err) {
        if(err) {
          cb(err);
        } else {
          tryToJoin();
        }
      }
    }

    function tryToFindEmpty() {
      var emptySlot = window.firstEmptySlotRange(start, end);
      if(emptySlot == null) {
        // if there is still some leftover and slot is not null, click slot
        if(slot == null) {
          tossLeftover();
        } else {
          clickWindow(slot, 0, 0, tossLeftover);
        }
      } else {
        clickWindow(emptySlot, 0, 0, cb);
      }
    }

    function tossLeftover() {
      if(window.selectedItem) {
        clickWindow(-999, 0, 0, cb);
      } else {
        cb();
      }
    }
  }

  function activateBlock(block) {
    // TODO: tell the server that we are not sneaking while doing this
    bot.lookAt(block.position);
    // swing arm animation
    bot._client.write('arm_animation', {});
    // place block message
    bot._client.write('block_place', {
      location: block.position,
      direction: 1,
      heldItem: Item.toNotch(bot.heldItem),
      cursorX: 8,
      cursorY: 8,
      cursorZ: 8
    });
  }

  function transfer(options, cb) {
    var window = options.window || bot.currentWindow || bot.inventory;
    var itemType = options.itemType;
    var metadata = options.metadata;
    var count = options.count == null ? 1 : options.count;
    cb = cb || noop;
    var firstSourceSlot = null;

    // ranges
    var sourceStart = options.sourceStart;
    var destStart = options.destStart;
    assert.notEqual(sourceStart, null);
    assert.notEqual(destStart, null);
    var sourceEnd = options.sourceEnd == null ? sourceStart + 1 : options.sourceEnd;
    var destEnd = options.destEnd == null ? destStart + 1 : options.destEnd;

    transferOne();

    function transferOne() {
      if(count === 0) {
        putSelectedItemRange(sourceStart, sourceEnd, window, firstSourceSlot, cb);
        return;
      }
      if(!window.selectedItem || window.selectedItem.type !== itemType ||
        (metadata != null && window.selectedItem.metadata !== metadata)) {
        // we are not holding the item we need. click it.
        var sourceItem = window.findItemRange(sourceStart, sourceEnd, itemType, metadata);
        if(!sourceItem) return cb(new Error("missing source item " + itemType + ":" + metadata + " in (" + sourceStart + "," + sourceEnd + ")"));
        if(firstSourceSlot == null) firstSourceSlot = sourceItem.slot;
        // number of item that can be moved from that slot
        var sourceItemCount = sourceItem.count;
        clickWindow(sourceItem.slot, 0, 0, function(err) {
          if(err) {
            cb(err);
          } else {
            clickDest();
          }
        });
      } else {
        clickDest();
      }

      function clickDest() {
        assert.notEqual(window.selectedItem.type, null);
        assert.notEqual(window.selectedItem.metadata, null);
        var destItem, destSlot;
        // special case for tossing
        if(destStart === -999) {
          destSlot = -999;
        } else {
          // find a non full item that we can drop into
          destItem = window.findItemRange(destStart, destEnd,
            window.selectedItem.type, window.selectedItem.metadata, true);
          // if that didn't work find an empty slot to drop into
          destSlot = destItem ? destItem.slot :
            window.firstEmptySlotRange(destStart, destEnd);
          // if that didn't work, give up
          if(destSlot == null) {
            cb(new Error("destination full"));
            return;
          }
        }
        // move the maximum number of item that can be moved
        var destSlotCount = destSlot.count ? destSlot.count : 0;
        var movedItems = Math.min(64 - destSlotCount, sourceItemCount);
        // if the number of item the left click moves is less than the number of item we want to move
        // several at the same time (left click)
        if(movedItems <= count) {
          clickWindow(destSlot, 0, 0, function(err) {
            if(err) {
              cb(err);
            } else {
              // update the number of item that can be moved at the source slot (sourceItemCount)
              sourceItemCount -= movedItems;
              // and the number of item we want to move (count)
              count -= movedItems;
              transferOne();
            }
          });
        }
        // one by one (right click)
        else {
          clickWindow(destSlot, 1, 0, function(err) {
            if(err) {
              cb(err);
            } else {
              count -= 1;
              transferOne();
            }
          });
        }
      }
    }
  }

  function openBlock(block, Class) {
    var session = new Class();
    session.close = close;
    bot.once("windowOpen", onWindowOpen);
    bot.activateBlock(block);
    return session;
    function onWindowOpen(window) {
      if(window.type !== Class.windowType) return;
      session.window = window;
      bot.once("windowClose", onClose);
      bot.on("setSlot:" + window.id, onSetSlot);
      session.emit("open");
    }

    function close() {
      assert.notEqual(session.window, null);
      closeWindow(session.window);
    }

    function onClose() {
      bot.removeListener("setSlot:" + session.window.id, onSetSlot);
      session.window = null;
      session.emit("close");
    }

    function onSetSlot(oldItem, newItem) {
      if(!Item.equal(oldItem, newItem)) {
        session.emit("updateSlot", oldItem, newItem);
      }
    }
  }

  function placeBlock(referenceBlock, faceVector) {
    assert.ok(bot.heldItem, "must be holding an item to place a block");
    // TODO: tell the server that we are sneaking while doing this
    var pos = referenceBlock.position;
    bot._client.write('block_place', {
      location: pos,
      direction: vectorToDirection(faceVector),
      heldItem: Item.toNotch(bot.heldItem),
      cursorX: 8,
      cursorY: 8,
      cursorZ: 8
    });
    // update it immediately; the server will correct us if it did not work.
    var dest = pos.plus(faceVector);
    bot._updateBlock(dest, bot.heldItem.type, bot.heldItem.metadata);
  }

  function createActionNumber() {
    return nextActionNumber++;
  }

  function updateHeldItem() {
    bot.heldItem = bot.inventory.slots[bot.QUICK_BAR_START + bot.quickBarSlot];
  }

  function closeWindow(window) {
    bot._client.write('close_window', {
      windowId: window.id
    });
    bot.currentWindow = null;
    bot.emit("windowClose", window);
  }

  function confirmTransaction(windowId, actionId, accepted) {
    // drop the queue entries for all the clicks that the server did not send
    // transaction packets for.
    var click = windowClickQueue.shift();
    assert.ok(click.id <= actionId);
    while(actionId > click.id) {
      onAccepted();
      click = windowClickQueue.shift();
    }
    assert.ok(click);

    if(accepted) {
      onAccepted();
    } else {
      onRejected();
    }
    updateHeldItem();

    function onAccepted() {
      var window = windowId === 0 ? bot.inventory : bot.currentWindow;
      if(!window || window.id !== click.windowId) return;
      window.acceptClick(click);
      bot.emit("confirmTransaction" + click.id, true);
    }

    function onRejected() {
      bot._client.write('transaction', {
        windowId: 0,
        action: click.id,
        accepted: false
      });
      bot.emit("confirmTransaction" + click.id, false);
    }
  }

  function clickWindow(slot, mouseButton, mode, cb) {
    // if you click on the quick bar and have dug recently,
    // wait a bit
    if(slot >= bot.QUICK_BAR_START && bot.lastDigTime != null) {
      var timeSinceLastDig = new Date() - bot.lastDigTime;
      if(timeSinceLastDig < DIG_CLICK_TIMEOUT) {
        setTimeout(function() {
          clickWindow(slot, mouseButton, mode, cb);
        }, DIG_CLICK_TIMEOUT - timeSinceLastDig);
        return;
      }
    }
    cb = cb || noop;
    var window = bot.currentWindow || bot.inventory;

    assert.ok(mouseButton === 0 || mouseButton === 1);
    assert.strictEqual(mode, 0);
    var actionId = createActionNumber();

    var click = {
      slot: slot,
      mouseButton: mouseButton,
      mode: mode,
      id: actionId,
      windowId: window.id
    };
    windowClickQueue.push(click);
    bot._client.write('window_click', {
      windowId: window.id,
      slot: slot,
      mouseButton: mouseButton,
      action: actionId,
      mode: mode,
      item: slot === -999 ? {blockId: -1} : Item.toNotch(window.slots[slot])
    });
    bot.once("confirmTransaction" + actionId, function(success) {
      if(success) {
        cb();
      } else {
        cb(new Error("Server rejected transaction."));
      }
    });
    // notchian servers are assholes and only confirm certain transactions.
    if(!window.transactionRequiresConfirmation(click)) {
      // jump the gun and accept the click
      confirmTransaction(window.id, actionId, true);
    }
  }

  function putAway(slot, cb) {
    clickWindow(slot, 0, 0, function(err) {
      if(err) return cb(err);
      var window = bot.currentWindow || bot.inventory;
      var start = window.inventorySlotStart;
      var end = start + windows.INVENTORY_SLOT_COUNT;
      putSelectedItemRange(start, end, window, null, cb);
    });
  }

  function moveSlotItem(sourceSlot, destSlot, cb) {
    clickWindow(sourceSlot, 0, 0, function(err) {
      if(err) return cb(err);
      clickWindow(destSlot, 0, 0, function(err) {
        // if we're holding an item, put it back where the source item was.
        // otherwise we're done.
        if(err) {
          cb(err);
        } else if(bot.inventory.selectedItem) {
          clickWindow(sourceSlot, 0, 0, cb);
        } else {
          cb();
        }
      });
    });
  }

  bot._client.on('transaction', function(packet) {
    // confirm transaction
    confirmTransaction(packet.windowId, packet.action, packet.accepted);
  });

  bot._client.on('held_item_slot', function(packet) {
    // held item change
    bot.quickBarSlot = packet.slot;
    updateHeldItem();
  });
  bot._client.on('open_window', function(packet) {
    // open window
    bot.currentWindow = windows.createWindow(packet.windowId,
      packet.inventoryType, packet.windowTitle, packet.slotCount);
    // don't emit windowOpen until we have the slot data
    var window = bot.currentWindow;
    bot.once("setWindowItems:" + window.id, function() {
      bot.emit("windowOpen", window);
    });
  });
  bot._client.on('close_window', function(packet) {
    // close window
    var oldWindow = bot.currentWindow;
    bot.currentWindow = null;
    bot.emit("windowClose", oldWindow);
  });
  bot._client.on('set_slot', function(packet) {
    // set slot
    var window = packet.windowId === 0 ? bot.inventory : bot.currentWindow;
    if(!window || window.id !== packet.windowId) return;
    var newItem = Item.fromNotch(packet.item);
    var oldItem = window.slots[packet.slot];
    window.updateSlot(packet.slot, newItem);
    updateHeldItem();
    bot.emit("setSlot:" + window.id, oldItem, newItem);
  });
  bot._client.on('window_items', function(packet) {
    // set window items
    var window = packet.windowId === 0 ? bot.inventory : bot.currentWindow;
    if(!window || window.id !== packet.windowId) return;
    var i, item;
    for(i = 0; i < packet.items.length; ++i) {
      item = Item.fromNotch(packet.items[i]);
      window.updateSlot(i, item);
    }
    updateHeldItem();
    bot.emit("setWindowItems:" + window.id);
  });

  bot.activateBlock = activateBlock;
  bot.placeBlock = placeBlock;
  bot.activateItem = activateItem;
  bot.deactivateItem = deactivateItem;

  // not really in the public API
  bot.clickWindow = clickWindow;
  bot.putSelectedItemRange = putSelectedItemRange;
  bot.putAway = putAway;
  bot.closeWindow = closeWindow;
  bot.transfer = transfer;
  bot.openBlock = openBlock;
  bot.moveSlotItem = moveSlotItem;
  bot.updateHeldItem = updateHeldItem;

}

function noop(err) {
  if(err) throw err;
}

function vectorToDirection(v) {
  if(v.y < 0) {
    return 0;
  } else if(v.y > 0) {
    return 1;
  } else if(v.z < 0) {
    return 2;
  } else if(v.z > 0) {
    return 3;
  } else if(v.x < 0) {
    return 4;
  } else if(v.x > 0) {
    return 5;
  }
  assert.ok(false, "invalid direction vector " + v);
}
<commit_msg>updates bot.entity.heldItem when bot.heldItem is updated bot.heldItem should probably be removed for coherence but it's already used by some projects using mineflayer, so let's keep both for now<commit_after>var Item = require('../item');
var assert = require('assert');
var Vec3 = require('vec3').Vec3;
var windows = require('../windows');

module.exports = inject;

// ms to wait before clicking on a tool so the server can send the new
// damage information
var DIG_CLICK_TIMEOUT = 500;

function inject(bot) {
  var nextActionNumber = 0;
  var windowClickQueue = [];

  // 0-8, null = uninitialized
  // which quick bar slot is selected
  bot.quickBarSlot = null;
  bot.inventory = new windows.InventoryWindow(0, "Inventory", 44);
  bot.currentWindow = null;
  bot.heldItem = null;

  function activateItem() {
    bot._client.write('block_place', {
      location: new Vec3(-1, 255, -1),
      direction: -1,
      heldItem: Item.toNotch(bot.heldItem),
      cursorX: -1,
      cursorY: -1,
      cursorZ: -1
    });
  }

  function deactivateItem() {
    bot._client.write('block_dig', {
      status: 5,
      location: new Vec3(0, 0, 0),
      face: 5
    });
  }

  function putSelectedItemRange(start, end, window, slot, cb) {
    // put the selected item back indow the slot range in window

    // try to put it in an item that already exists and just increase
    // the count.
    tryToJoin();

    function tryToJoin() {
      if(!window.selectedItem) {
        cb();
        return;
      }
      var item = window.findItemRange(start, end, window.selectedItem.type,
        window.selectedItem.metadata, true);
      if(item) {
        clickWindow(item.slot, 0, 0, onClick);
      } else {
        tryToFindEmpty();
      }

      function onClick(err) {
        if(err) {
          cb(err);
        } else {
          tryToJoin();
        }
      }
    }

    function tryToFindEmpty() {
      var emptySlot = window.firstEmptySlotRange(start, end);
      if(emptySlot == null) {
        // if there is still some leftover and slot is not null, click slot
        if(slot == null) {
          tossLeftover();
        } else {
          clickWindow(slot, 0, 0, tossLeftover);
        }
      } else {
        clickWindow(emptySlot, 0, 0, cb);
      }
    }

    function tossLeftover() {
      if(window.selectedItem) {
        clickWindow(-999, 0, 0, cb);
      } else {
        cb();
      }
    }
  }

  function activateBlock(block) {
    // TODO: tell the server that we are not sneaking while doing this
    bot.lookAt(block.position);
    // swing arm animation
    bot._client.write('arm_animation', {});
    // place block message
    bot._client.write('block_place', {
      location: block.position,
      direction: 1,
      heldItem: Item.toNotch(bot.heldItem),
      cursorX: 8,
      cursorY: 8,
      cursorZ: 8
    });
  }

  function transfer(options, cb) {
    var window = options.window || bot.currentWindow || bot.inventory;
    var itemType = options.itemType;
    var metadata = options.metadata;
    var count = options.count == null ? 1 : options.count;
    cb = cb || noop;
    var firstSourceSlot = null;

    // ranges
    var sourceStart = options.sourceStart;
    var destStart = options.destStart;
    assert.notEqual(sourceStart, null);
    assert.notEqual(destStart, null);
    var sourceEnd = options.sourceEnd == null ? sourceStart + 1 : options.sourceEnd;
    var destEnd = options.destEnd == null ? destStart + 1 : options.destEnd;

    transferOne();

    function transferOne() {
      if(count === 0) {
        putSelectedItemRange(sourceStart, sourceEnd, window, firstSourceSlot, cb);
        return;
      }
      if(!window.selectedItem || window.selectedItem.type !== itemType ||
        (metadata != null && window.selectedItem.metadata !== metadata)) {
        // we are not holding the item we need. click it.
        var sourceItem = window.findItemRange(sourceStart, sourceEnd, itemType, metadata);
        if(!sourceItem) return cb(new Error("missing source item " + itemType + ":" + metadata + " in (" + sourceStart + "," + sourceEnd + ")"));
        if(firstSourceSlot == null) firstSourceSlot = sourceItem.slot;
        // number of item that can be moved from that slot
        var sourceItemCount = sourceItem.count;
        clickWindow(sourceItem.slot, 0, 0, function(err) {
          if(err) {
            cb(err);
          } else {
            clickDest();
          }
        });
      } else {
        clickDest();
      }

      function clickDest() {
        assert.notEqual(window.selectedItem.type, null);
        assert.notEqual(window.selectedItem.metadata, null);
        var destItem, destSlot;
        // special case for tossing
        if(destStart === -999) {
          destSlot = -999;
        } else {
          // find a non full item that we can drop into
          destItem = window.findItemRange(destStart, destEnd,
            window.selectedItem.type, window.selectedItem.metadata, true);
          // if that didn't work find an empty slot to drop into
          destSlot = destItem ? destItem.slot :
            window.firstEmptySlotRange(destStart, destEnd);
          // if that didn't work, give up
          if(destSlot == null) {
            cb(new Error("destination full"));
            return;
          }
        }
        // move the maximum number of item that can be moved
        var destSlotCount = destSlot.count ? destSlot.count : 0;
        var movedItems = Math.min(64 - destSlotCount, sourceItemCount);
        // if the number of item the left click moves is less than the number of item we want to move
        // several at the same time (left click)
        if(movedItems <= count) {
          clickWindow(destSlot, 0, 0, function(err) {
            if(err) {
              cb(err);
            } else {
              // update the number of item that can be moved at the source slot (sourceItemCount)
              sourceItemCount -= movedItems;
              // and the number of item we want to move (count)
              count -= movedItems;
              transferOne();
            }
          });
        }
        // one by one (right click)
        else {
          clickWindow(destSlot, 1, 0, function(err) {
            if(err) {
              cb(err);
            } else {
              count -= 1;
              transferOne();
            }
          });
        }
      }
    }
  }

  function openBlock(block, Class) {
    var session = new Class();
    session.close = close;
    bot.once("windowOpen", onWindowOpen);
    bot.activateBlock(block);
    return session;
    function onWindowOpen(window) {
      if(window.type !== Class.windowType) return;
      session.window = window;
      bot.once("windowClose", onClose);
      bot.on("setSlot:" + window.id, onSetSlot);
      session.emit("open");
    }

    function close() {
      assert.notEqual(session.window, null);
      closeWindow(session.window);
    }

    function onClose() {
      bot.removeListener("setSlot:" + session.window.id, onSetSlot);
      session.window = null;
      session.emit("close");
    }

    function onSetSlot(oldItem, newItem) {
      if(!Item.equal(oldItem, newItem)) {
        session.emit("updateSlot", oldItem, newItem);
      }
    }
  }

  function placeBlock(referenceBlock, faceVector) {
    assert.ok(bot.heldItem, "must be holding an item to place a block");
    // TODO: tell the server that we are sneaking while doing this
    var pos = referenceBlock.position;
    bot._client.write('block_place', {
      location: pos,
      direction: vectorToDirection(faceVector),
      heldItem: Item.toNotch(bot.heldItem),
      cursorX: 8,
      cursorY: 8,
      cursorZ: 8
    });
    // update it immediately; the server will correct us if it did not work.
    var dest = pos.plus(faceVector);
    bot._updateBlock(dest, bot.heldItem.type, bot.heldItem.metadata);
  }

  function createActionNumber() {
    return nextActionNumber++;
  }

  function updateHeldItem() {
    bot.heldItem = bot.inventory.slots[bot.QUICK_BAR_START + bot.quickBarSlot];
    bot.entity.heldItem = bot.heldItem;
  }

  function closeWindow(window) {
    bot._client.write('close_window', {
      windowId: window.id
    });
    bot.currentWindow = null;
    bot.emit("windowClose", window);
  }

  function confirmTransaction(windowId, actionId, accepted) {
    // drop the queue entries for all the clicks that the server did not send
    // transaction packets for.
    var click = windowClickQueue.shift();
    assert.ok(click.id <= actionId);
    while(actionId > click.id) {
      onAccepted();
      click = windowClickQueue.shift();
    }
    assert.ok(click);

    if(accepted) {
      onAccepted();
    } else {
      onRejected();
    }
    updateHeldItem();

    function onAccepted() {
      var window = windowId === 0 ? bot.inventory : bot.currentWindow;
      if(!window || window.id !== click.windowId) return;
      window.acceptClick(click);
      bot.emit("confirmTransaction" + click.id, true);
    }

    function onRejected() {
      bot._client.write('transaction', {
        windowId: 0,
        action: click.id,
        accepted: false
      });
      bot.emit("confirmTransaction" + click.id, false);
    }
  }

  function clickWindow(slot, mouseButton, mode, cb) {
    // if you click on the quick bar and have dug recently,
    // wait a bit
    if(slot >= bot.QUICK_BAR_START && bot.lastDigTime != null) {
      var timeSinceLastDig = new Date() - bot.lastDigTime;
      if(timeSinceLastDig < DIG_CLICK_TIMEOUT) {
        setTimeout(function() {
          clickWindow(slot, mouseButton, mode, cb);
        }, DIG_CLICK_TIMEOUT - timeSinceLastDig);
        return;
      }
    }
    cb = cb || noop;
    var window = bot.currentWindow || bot.inventory;

    assert.ok(mouseButton === 0 || mouseButton === 1);
    assert.strictEqual(mode, 0);
    var actionId = createActionNumber();

    var click = {
      slot: slot,
      mouseButton: mouseButton,
      mode: mode,
      id: actionId,
      windowId: window.id
    };
    windowClickQueue.push(click);
    bot._client.write('window_click', {
      windowId: window.id,
      slot: slot,
      mouseButton: mouseButton,
      action: actionId,
      mode: mode,
      item: slot === -999 ? {blockId: -1} : Item.toNotch(window.slots[slot])
    });
    bot.once("confirmTransaction" + actionId, function(success) {
      if(success) {
        cb();
      } else {
        cb(new Error("Server rejected transaction."));
      }
    });
    // notchian servers are assholes and only confirm certain transactions.
    if(!window.transactionRequiresConfirmation(click)) {
      // jump the gun and accept the click
      confirmTransaction(window.id, actionId, true);
    }
  }

  function putAway(slot, cb) {
    clickWindow(slot, 0, 0, function(err) {
      if(err) return cb(err);
      var window = bot.currentWindow || bot.inventory;
      var start = window.inventorySlotStart;
      var end = start + windows.INVENTORY_SLOT_COUNT;
      putSelectedItemRange(start, end, window, null, cb);
    });
  }

  function moveSlotItem(sourceSlot, destSlot, cb) {
    clickWindow(sourceSlot, 0, 0, function(err) {
      if(err) return cb(err);
      clickWindow(destSlot, 0, 0, function(err) {
        // if we're holding an item, put it back where the source item was.
        // otherwise we're done.
        if(err) {
          cb(err);
        } else if(bot.inventory.selectedItem) {
          clickWindow(sourceSlot, 0, 0, cb);
        } else {
          cb();
        }
      });
    });
  }

  bot._client.on('transaction', function(packet) {
    // confirm transaction
    confirmTransaction(packet.windowId, packet.action, packet.accepted);
  });

  bot._client.on('held_item_slot', function(packet) {
    // held item change
    bot.quickBarSlot = packet.slot;
    updateHeldItem();
  });
  bot._client.on('open_window', function(packet) {
    // open window
    bot.currentWindow = windows.createWindow(packet.windowId,
      packet.inventoryType, packet.windowTitle, packet.slotCount);
    // don't emit windowOpen until we have the slot data
    var window = bot.currentWindow;
    bot.once("setWindowItems:" + window.id, function() {
      bot.emit("windowOpen", window);
    });
  });
  bot._client.on('close_window', function(packet) {
    // close window
    var oldWindow = bot.currentWindow;
    bot.currentWindow = null;
    bot.emit("windowClose", oldWindow);
  });
  bot._client.on('set_slot', function(packet) {
    // set slot
    var window = packet.windowId === 0 ? bot.inventory : bot.currentWindow;
    if(!window || window.id !== packet.windowId) return;
    var newItem = Item.fromNotch(packet.item);
    var oldItem = window.slots[packet.slot];
    window.updateSlot(packet.slot, newItem);
    updateHeldItem();
    bot.emit("setSlot:" + window.id, oldItem, newItem);
  });
  bot._client.on('window_items', function(packet) {
    // set window items
    var window = packet.windowId === 0 ? bot.inventory : bot.currentWindow;
    if(!window || window.id !== packet.windowId) return;
    var i, item;
    for(i = 0; i < packet.items.length; ++i) {
      item = Item.fromNotch(packet.items[i]);
      window.updateSlot(i, item);
    }
    updateHeldItem();
    bot.emit("setWindowItems:" + window.id);
  });

  bot.activateBlock = activateBlock;
  bot.placeBlock = placeBlock;
  bot.activateItem = activateItem;
  bot.deactivateItem = deactivateItem;

  // not really in the public API
  bot.clickWindow = clickWindow;
  bot.putSelectedItemRange = putSelectedItemRange;
  bot.putAway = putAway;
  bot.closeWindow = closeWindow;
  bot.transfer = transfer;
  bot.openBlock = openBlock;
  bot.moveSlotItem = moveSlotItem;
  bot.updateHeldItem = updateHeldItem;

}

function noop(err) {
  if(err) throw err;
}

function vectorToDirection(v) {
  if(v.y < 0) {
    return 0;
  } else if(v.y > 0) {
    return 1;
  } else if(v.z < 0) {
    return 2;
  } else if(v.z > 0) {
    return 3;
  } else if(v.x < 0) {
    return 4;
  } else if(v.x > 0) {
    return 5;
  }
  assert.ok(false, "invalid direction vector " + v);
}
<|endoftext|>