<commit_before>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, window, $, brackets */

/**
 * Manages layout of panels surrounding the editor area, and size of the editor area (but not its contents).
 *
 * Updates panel sizes when the window is resized. Maintains the max resizing limits for panels, based on
 * currently available window size.
 *
 * Events:
 *    - editorAreaResize -- When editor-holder's size changes for any reason (including panel show/hide
 *              panel resize, or the window resize).
 *              The 2nd arg is the new editor-holder height.
 *              The 3rd arg is a refreshHint flag for internal EditorManager use.
 */
define(function (require, exports, module) {
    "use strict";

    var AppInit                 = require("utils/AppInit"),
        Resizer                 = require("utils/Resizer");

    /** @type {jQueryObject} The ".content" vertical stack (editor + all header/footer panels) */
    var $windowContent;

    /** @type {jQueryObject} The "#editor-holder": has only one visible child, the current CodeMirror
        instance (or the no-editor placeholder) */
    var $editorHolder;

    /** @type {boolean} Have we already started listening for the end of the ongoing window resize? */
    var windowResizing = false;

    /**
     * Calculates the available height for the full-size Editor (or the no-editor placeholder),
     * accounting for the current size of all visible panels, toolbar, & status bar.
     * @return {number}
     */
    function calcEditorHeight() {
        var availableHt = $windowContent.height();

        $editorHolder.siblings().each(function (i, elem) {
            var $elem = $(elem);
            if ($elem.css("display") !== "none" && $elem.css("position") !== "absolute") {
                availableHt -= $elem.outerHeight();
            }
        });

        // Clip value to 0 (it could be negative if a panel wants more space than we have)
        return Math.max(availableHt, 0);
    }

    /** Updates panel resize limits to disallow making panels big enough to shrink editor area below 0 */
    function updateResizeLimits() {
        var editorAreaHeight = $editorHolder.height();

        $editorHolder.siblings().each(function (i, elem) {
            var $elem = $(elem);
            if ($elem.css("display") === "none") {
                $elem.data("maxsize", editorAreaHeight);
            } else {
                $elem.data("maxsize", editorAreaHeight + $elem.outerHeight());
            }
        });
    }

    /**
     * Calculates a new size for editor-holder and resizes it accordingly, then and dispatches the "editorAreaResize"
     * event. (The editors within are resized by EditorManager, in response to that event).
     *
     * @param {string=} refreshHint  One of "skip", "force", or undefined. See EditorManager docs.
     */
    function triggerEditorResize(refreshHint) {
        // Find how much space is left for the editor
        var editorAreaHeight = calcEditorHeight();

        $editorHolder.height(editorAreaHeight);  // affects size of "not-editor" placeholder as well

        // Resize editor to fill the space
        $(exports).trigger("editorAreaResize", [editorAreaHeight, refreshHint]);
    }

    /** Trigger editor area resize whenever the window is resized */
    function handleWindowResize() {
        // These are not initialized in Jasmine Spec Runner window until a test
        // is run that creates a mock document.
        if (!$windowContent || !$editorHolder) {
            return;
        }

        // Immediately adjust editor's height, but skip the refresh since CodeMirror will call refresh()
        // itself when it sees the window resize event
        // triggerEditorResize("skip");

        // FIXME (issue #4564) Workaround https://github.com/marijnh/CodeMirror/issues/1787
        triggerEditorResize();

        if (!windowResizing) {
            windowResizing = true;

            // We don't need any fancy debouncing here - we just need to react before the user can start
            // resizing any panels at the new window size. So just listen for first mousemove once the
            // window resize releases mouse capture.
            $(window.document).one("mousemove", function () {
                windowResizing = false;
                updateResizeLimits();
            });
        }
    }

    /** Trigger editor area resize whenever the given panel is shown/hidden/resized */
    function listenToResize($panel) {
        // Update editor height when shown/hidden, & continuously as panel is resized
        $panel.on("panelCollapsed panelExpanded panelResizeUpdate", function () {
            triggerEditorResize();
        });
        // Update max size of sibling panels when shown/hidden, & at *end* of resize gesture
        $panel.on("panelCollapsed panelExpanded panelResizeEnd", function () {
            updateResizeLimits();
        });
    }

    /**
     * Represents a panel below the editor area (a child of ".content").
     *
     * @param {!jQueryObject} $panel  The entire panel, including any chrome, already in the DOM.
     * @param {number=} minSize  Minimum height of panel in px.
     */
    function Panel($panel, minSize) {
        this.$panel = $panel;

        Resizer.makeResizable($panel[0], Resizer.DIRECTION_VERTICAL, Resizer.POSITION_TOP, minSize, false, undefined, true);
        listenToResize($panel);
    }

    /** @type {jQueryObject} */
    Panel.prototype.$panel = null;

    Panel.prototype.isVisible = function () {
        return this.$panel.is(":visible");
    };

    Panel.prototype.show = function () {
        Resizer.show(this.$panel[0]);
    };
    Panel.prototype.hide = function () {
        Resizer.hide(this.$panel[0]);
    };

    Panel.prototype.setVisible = function (visible) {
        if (visible) {
            Resizer.show(this.$panel[0]);
        } else {
            Resizer.hide(this.$panel[0]);
        }
    };

    /**
     * Creates a new resizabel panel beneath the editor area and above the status bar footer. Panel is initially invisible.
     * The panel's size & visibility are automatically saved & restored as a view-state preference.
     *
     * @param {!string} id  Unique id for this panel. Use package-style naming, e.g. "myextension.feature.panelname"
     * @param {!jQueryObject} $panel  DOM content to use as the panel. Need not be in the document yet. Must have an id
     *      attribute, for use as a preferences key.
     * @param {number=} minSize  Minimum height of panel in px.
     * @return {!Panel}
     */
    function createBottomPanel(id, $panel, minSize) {
        $panel.insertBefore("#status-bar");
        $panel.hide();
        updateResizeLimits();  // initialize panel's max size

        return new Panel($panel, minSize);
    }

    /**
     * Used by EditorManager to notify us of layout changes our normal panel/window listeners wouldn't detect.
     * For internal use only: most code should call EditorManager.resizeEditor().
     */
    function _notifyLayoutChange(refreshHint) {
        triggerEditorResize(refreshHint);
        updateResizeLimits();
    }

    // Attach to key parts of the overall UI, once created
    AppInit.htmlReady(function () {
        $windowContent = $(".content");
        $editorHolder = $("#editor-holder");

        // Sidebar is a special case: it isn't a Panel, and is not created dynamically. Need to explicitly
        // listen for resize here.
        listenToResize($("#sidebar"));
    });

    // Unit test only: allow passing in mock DOM notes, e.g. for use with SpecRunnerUtils.createMockEditor()
    function _setMockDOM($mockWindowContent, $mockEditorHolder) {
        $windowContent = $mockWindowContent;
        $editorHolder = $mockEditorHolder;
    }

    // If someone adds a panel in the .content stack the old way, make sure we still listen for resize/show/hide
    // (Resizer emits a deprecation warning for us - no need to log anything here)
    $(Resizer).on("deprecatedPanelAdded", function (event, $panel) {
        listenToResize($panel);
    });

    // Add this as a capture handler so we're guaranteed to run it before the editor does its own
    // refresh on resize.
    window.addEventListener("resize", handleWindowResize, true);

    // Define public API
    exports.createBottomPanel    = createBottomPanel;
    exports._notifyLayoutChange  = _notifyLayoutChange;
    exports._setMockDOM          = _setMockDOM;
});
<commit_msg>Fix comment typo<commit_after>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, window, $, brackets */

/**
 * Manages layout of panels surrounding the editor area, and size of the editor area (but not its contents).
 *
 * Updates panel sizes when the window is resized. Maintains the max resizing limits for panels, based on
 * currently available window size.
 *
 * Events:
 *    - editorAreaResize -- When editor-holder's size changes for any reason (including panel show/hide
 *              panel resize, or the window resize).
 *              The 2nd arg is the new editor-holder height.
 *              The 3rd arg is a refreshHint flag for internal EditorManager use.
 */
define(function (require, exports, module) {
    "use strict";

    var AppInit                 = require("utils/AppInit"),
        Resizer                 = require("utils/Resizer");

    /** @type {jQueryObject} The ".content" vertical stack (editor + all header/footer panels) */
    var $windowContent;

    /** @type {jQueryObject} The "#editor-holder": has only one visible child, the current CodeMirror
        instance (or the no-editor placeholder) */
    var $editorHolder;

    /** @type {boolean} Have we already started listening for the end of the ongoing window resize? */
    var windowResizing = false;

    /**
     * Calculates the available height for the full-size Editor (or the no-editor placeholder),
     * accounting for the current size of all visible panels, toolbar, & status bar.
     * @return {number}
     */
    function calcEditorHeight() {
        var availableHt = $windowContent.height();

        $editorHolder.siblings().each(function (i, elem) {
            var $elem = $(elem);
            if ($elem.css("display") !== "none" && $elem.css("position") !== "absolute") {
                availableHt -= $elem.outerHeight();
            }
        });

        // Clip value to 0 (it could be negative if a panel wants more space than we have)
        return Math.max(availableHt, 0);
    }

    /** Updates panel resize limits to disallow making panels big enough to shrink editor area below 0 */
    function updateResizeLimits() {
        var editorAreaHeight = $editorHolder.height();

        $editorHolder.siblings().each(function (i, elem) {
            var $elem = $(elem);
            if ($elem.css("display") === "none") {
                $elem.data("maxsize", editorAreaHeight);
            } else {
                $elem.data("maxsize", editorAreaHeight + $elem.outerHeight());
            }
        });
    }

    /**
     * Calculates a new size for editor-holder and resizes it accordingly, then and dispatches the "editorAreaResize"
     * event. (The editors within are resized by EditorManager, in response to that event).
     *
     * @param {string=} refreshHint  One of "skip", "force", or undefined. See EditorManager docs.
     */
    function triggerEditorResize(refreshHint) {
        // Find how much space is left for the editor
        var editorAreaHeight = calcEditorHeight();

        $editorHolder.height(editorAreaHeight);  // affects size of "not-editor" placeholder as well

        // Resize editor to fill the space
        $(exports).trigger("editorAreaResize", [editorAreaHeight, refreshHint]);
    }

    /** Trigger editor area resize whenever the window is resized */
    function handleWindowResize() {
        // These are not initialized in Jasmine Spec Runner window until a test
        // is run that creates a mock document.
        if (!$windowContent || !$editorHolder) {
            return;
        }

        // Immediately adjust editor's height, but skip the refresh since CodeMirror will call refresh()
        // itself when it sees the window resize event
        // triggerEditorResize("skip");

        // FIXME (issue #4564) Workaround https://github.com/marijnh/CodeMirror/issues/1787
        triggerEditorResize();

        if (!windowResizing) {
            windowResizing = true;

            // We don't need any fancy debouncing here - we just need to react before the user can start
            // resizing any panels at the new window size. So just listen for first mousemove once the
            // window resize releases mouse capture.
            $(window.document).one("mousemove", function () {
                windowResizing = false;
                updateResizeLimits();
            });
        }
    }

    /** Trigger editor area resize whenever the given panel is shown/hidden/resized */
    function listenToResize($panel) {
        // Update editor height when shown/hidden, & continuously as panel is resized
        $panel.on("panelCollapsed panelExpanded panelResizeUpdate", function () {
            triggerEditorResize();
        });
        // Update max size of sibling panels when shown/hidden, & at *end* of resize gesture
        $panel.on("panelCollapsed panelExpanded panelResizeEnd", function () {
            updateResizeLimits();
        });
    }

    /**
     * Represents a panel below the editor area (a child of ".content").
     *
     * @param {!jQueryObject} $panel  The entire panel, including any chrome, already in the DOM.
     * @param {number=} minSize  Minimum height of panel in px.
     */
    function Panel($panel, minSize) {
        this.$panel = $panel;

        Resizer.makeResizable($panel[0], Resizer.DIRECTION_VERTICAL, Resizer.POSITION_TOP, minSize, false, undefined, true);
        listenToResize($panel);
    }

    /** @type {jQueryObject} */
    Panel.prototype.$panel = null;

    Panel.prototype.isVisible = function () {
        return this.$panel.is(":visible");
    };

    Panel.prototype.show = function () {
        Resizer.show(this.$panel[0]);
    };
    Panel.prototype.hide = function () {
        Resizer.hide(this.$panel[0]);
    };

    Panel.prototype.setVisible = function (visible) {
        if (visible) {
            Resizer.show(this.$panel[0]);
        } else {
            Resizer.hide(this.$panel[0]);
        }
    };

    /**
     * Creates a new resizable panel beneath the editor area and above the status bar footer. Panel is initially invisible.
     * The panel's size & visibility are automatically saved & restored as a view-state preference.
     *
     * @param {!string} id  Unique id for this panel. Use package-style naming, e.g. "myextension.feature.panelname"
     * @param {!jQueryObject} $panel  DOM content to use as the panel. Need not be in the document yet. Must have an id
     *      attribute, for use as a preferences key.
     * @param {number=} minSize  Minimum height of panel in px.
     * @return {!Panel}
     */
    function createBottomPanel(id, $panel, minSize) {
        $panel.insertBefore("#status-bar");
        $panel.hide();
        updateResizeLimits();  // initialize panel's max size

        return new Panel($panel, minSize);
    }

    /**
     * Used by EditorManager to notify us of layout changes our normal panel/window listeners wouldn't detect.
     * For internal use only: most code should call EditorManager.resizeEditor().
     */
    function _notifyLayoutChange(refreshHint) {
        triggerEditorResize(refreshHint);
        updateResizeLimits();
    }

    // Attach to key parts of the overall UI, once created
    AppInit.htmlReady(function () {
        $windowContent = $(".content");
        $editorHolder = $("#editor-holder");

        // Sidebar is a special case: it isn't a Panel, and is not created dynamically. Need to explicitly
        // listen for resize here.
        listenToResize($("#sidebar"));
    });

    // Unit test only: allow passing in mock DOM notes, e.g. for use with SpecRunnerUtils.createMockEditor()
    function _setMockDOM($mockWindowContent, $mockEditorHolder) {
        $windowContent = $mockWindowContent;
        $editorHolder = $mockEditorHolder;
    }

    // If someone adds a panel in the .content stack the old way, make sure we still listen for resize/show/hide
    // (Resizer emits a deprecation warning for us - no need to log anything here)
    $(Resizer).on("deprecatedPanelAdded", function (event, $panel) {
        listenToResize($panel);
    });

    // Add this as a capture handler so we're guaranteed to run it before the editor does its own
    // refresh on resize.
    window.addEventListener("resize", handleWindowResize, true);

    // Define public API
    exports.createBottomPanel    = createBottomPanel;
    exports._notifyLayoutChange  = _notifyLayoutChange;
    exports._setMockDOM          = _setMockDOM;
});
<|endoftext|>


<commit_before>import { matches } from 'utils/dom';
import Mustache from './shared/Mustache';
import insertHtml from './triple/insertHtml';
import { decodeCharacterReferences } from 'utils/html';

export default class Triple extends Mustache {
	constructor ( options ) {
		super( options );
	}

	detach () {
		const docFrag = document.createDocumentFragment();
		this.nodes.forEach( node => docFrag.appendChild( node ) );
		return docFrag;
	}

	find ( selector ) {
		const len = this.nodes.length;
		let i;

		for ( i = 0; i < len; i += 1 ) {
			const node = this.nodes[i];

			if ( node.nodeType !== 1 ) continue;

			if ( matches( node, selector ) ) return node;

			const queryResult = node.querySelector( selector );
			if ( queryResult ) return queryResult;
		}

		return null;
	}

	findAll ( selector, query ) {
		const len = this.nodes.length;
		let i;

		for ( i = 0; i < len; i += 1 ) {
			const node = this.nodes[i];

			if ( node.nodeType !== 1 ) continue;

			if ( query.test( node ) ) query.add( node );

			const queryAllResult = node.querySelectorAll( selector );
			if ( queryAllResult ) {
				const numNodes = queryAllResult.length;
				let j;

				for ( j = 0; j < numNodes; j += 1 ) {
					query.add( queryAllResult[j] );
				}
			}
		}
	}

	findComponent () {
		return null;
	}

	firstNode () {
		return this.nodes[0];
	}

	rebind () {
		throw new Error( 'Triple$rebind is not yet implemented!' ); // TODO test
	}

	render ( target ) {
		const html = this.model ? this.model.get() : '';
		this.nodes = insertHtml( html, this.parentFragment.findParentNode(), target );
	}

	toString () {
		return this.model && this.model.get() != null ? decodeCharacterReferences( '' + this.model.get() ) : '';
	}

	unrender () {
		this.nodes.forEach( node => node.parentNode.removeChild( node ) );
	}

	update () {
		if ( this.dirty ) {
			this.unrender();
			const docFrag = document.createDocumentFragment();
			this.render( docFrag );

			const parentNode = this.parentFragment.findParentNode();
			const anchor = this.parentFragment.findNextNode( this );

			parentNode.insertBefore( docFrag, anchor );

			this.dirty = false;
		}
	}
}
<commit_msg>Triple$rebind is a noop<commit_after>import { matches } from 'utils/dom';
import Mustache from './shared/Mustache';
import insertHtml from './triple/insertHtml';
import { decodeCharacterReferences } from 'utils/html';

export default class Triple extends Mustache {
	constructor ( options ) {
		super( options );
	}

	detach () {
		const docFrag = document.createDocumentFragment();
		this.nodes.forEach( node => docFrag.appendChild( node ) );
		return docFrag;
	}

	find ( selector ) {
		const len = this.nodes.length;
		let i;

		for ( i = 0; i < len; i += 1 ) {
			const node = this.nodes[i];

			if ( node.nodeType !== 1 ) continue;

			if ( matches( node, selector ) ) return node;

			const queryResult = node.querySelector( selector );
			if ( queryResult ) return queryResult;
		}

		return null;
	}

	findAll ( selector, query ) {
		const len = this.nodes.length;
		let i;

		for ( i = 0; i < len; i += 1 ) {
			const node = this.nodes[i];

			if ( node.nodeType !== 1 ) continue;

			if ( query.test( node ) ) query.add( node );

			const queryAllResult = node.querySelectorAll( selector );
			if ( queryAllResult ) {
				const numNodes = queryAllResult.length;
				let j;

				for ( j = 0; j < numNodes; j += 1 ) {
					query.add( queryAllResult[j] );
				}
			}
		}
	}

	findComponent () {
		return null;
	}

	firstNode () {
		return this.nodes[0];
	}

	rebind () {
		// noop
	}

	render ( target ) {
		const html = this.model ? this.model.get() : '';
		this.nodes = insertHtml( html, this.parentFragment.findParentNode(), target );
	}

	toString () {
		return this.model && this.model.get() != null ? decodeCharacterReferences( '' + this.model.get() ) : '';
	}

	unrender () {
		this.nodes.forEach( node => node.parentNode.removeChild( node ) );
	}

	update () {
		if ( this.dirty ) {
			this.unrender();
			const docFrag = document.createDocumentFragment();
			this.render( docFrag );

			const parentNode = this.parentFragment.findParentNode();
			const anchor = this.parentFragment.findNextNode( this );

			parentNode.insertBefore( docFrag, anchor );

			this.dirty = false;
		}
	}
}
<|endoftext|>


<commit_before>var OverviewBox;
var view = require("backbone-viewj");
var mouse = require("mouse-pos");
var selection = require("../g/selection/Selection");
var jbone = require("jbone");
var _ = require("underscore");

module.exports = OverviewBox = view.extend({

  className: "biojs_msa_overviewbox",
  tagName: "canvas",

  initialize: function(data) {
    this.g = data.g;
    this.listenTo( this.g.zoomer,"change:boxRectWidth change:boxRectHeight change:overviewboxPaddingTop", this.rerender);
    this.listenTo(this.g.selcol, "add reset change", this.rerender);
    this.listenTo(this.g.columns, "change:hidden", this.rerender);
    this.listenTo(this.g.colorscheme, "change:showLowerCase", this.rerender);
    this.listenTo(this.model, "change", _.debounce(this.rerender, 5));

    // color
    this.color = this.g.colorscheme.getSelectedScheme();
    this.listenTo(this.g.colorscheme, "change:scheme", function() {
      this.color = this.g.colorscheme.getSelectedScheme();
      return this.rerender();
    });
    return this.dragStart = [];
  },

  events:
    {click: "_onclick",
    mousedown: "_onmousedown"
    },

  rerender: function() {
    if (!this.g.config.get("manualRendering")) {
      return this.render();
    }
  },

  render: function() {
    this._createCanvas();
    this.el.textContent = "overview";
    this.el.style.marginTop = this.g.zoomer.get("overviewboxPaddingTop");

    // background bg for non-drawed area
    this.ctx.fillStyle = "#999999";
    this.ctx.fillRect(0,0,this.el.width,this.el.height);

    var rectWidth = this.g.zoomer.get("boxRectWidth");
    var rectHeight = this.g.zoomer.get("boxRectHeight");
    var hidden = this.g.columns.get("hidden");
    var showLowerCase = this.g.colorscheme.get("showLowerCase");

    var y = -rectHeight;
    var end = this.model.length - 1;
    for (var i = 0; 0 < end ? i <= end : i >= end; 0 < end ? i++ : i++) {
      var seq = this.model.at(i).get("seq");
      var x = 0;
      y = y + rectHeight;

      if (this.model.at(i).get("hidden")) {
        // hidden seq
        console.log(this.model.at(i).get("hidden"));
        this.ctx.fillStyle = "grey";
        this.ctx.fillRect(0,y,seq.length * rectWidth,rectHeight);
        continue;
      }

      var end1 = seq.length - 1;
      for (var j = 0; 0 < end1 ? j <= end1 : j >= end1; 0 < end1 ? j++ : j++) {
        var c = seq[j];
        // todo: optional uppercasing
        if (showLowerCase) { c = c.toUpperCase(); }
        var color = this.color.getColor(c, {pos: j});

        if (hidden.indexOf(j) >= 0) {
          color = "grey";
        }

        if ((typeof color !== "undefined" && color !== null)) {
          this.ctx.fillStyle = color;
          this.ctx.fillRect(x,y,rectWidth,rectHeight);
        }

        x = x + rectWidth;
      }
    }

    return this._drawSelection();
  },

  _drawSelection: function() {
    // hide during selection
    if (this.dragStart.length > 0 && !this.prolongSelection) { return; }

    var rectWidth = this.g.zoomer.get("boxRectWidth");
    var rectHeight = this.g.zoomer.get("boxRectHeight");
    var maxHeight = rectHeight * this.model.length;
    this.ctx.fillStyle = "#666666";
    this.ctx.globalAlpha = 0.9;
    var end = this.g.selcol.length - 1;
    for (var i = 0; 0 < end ? i <= end : i >= end; 0 < end ? i++ : i++) {
      var sel = this.g.selcol.at(i);
      if(!sel) continue;
      if (sel.get('type') === 'column') {
        this.ctx.fillRect( rectWidth * sel.get('xStart'),0,rectWidth *
        (sel.get('xEnd') - sel.get('xStart') + 1),maxHeight
        );
      } else if (sel.get('type') === 'row') {
        var seq = (this.model.filter(function(el) { return el.get('id') === sel.get('seqId'); }))[0];
        var pos = this.model.indexOf(seq);
        this.ctx.fillRect(0,rectHeight * pos, rectWidth * seq.get('seq').length, rectHeight);
      } else if (sel.get('type') === 'pos') {
        seq = (this.model.filter(function(el) { return el.get('id') === sel.get('seqId'); }))[0];
        pos = this.model.indexOf(seq);
        this.ctx.fillRect(rectWidth * sel.get('xStart'),rectHeight * pos, rectWidth * (sel.get('xEnd') - sel.get('xStart') + 1), rectHeight);
      }
    }

    return this.ctx.globalAlpha = 1;
  },

  _onclick: function(evt) {
    return this.g.trigger("meta:click", {seqId: this.model.get("id", {evt:evt})});
  },

  _onmousemove: function(e) {
    // duplicate events
    if (this.dragStart.length === 0) { return; }

    this.render();
    this.ctx.fillStyle = "#666666";
    this.ctx.globalAlpha = 0.9;

    var rect = this._calcSelection( mouse.abs(e) );
    this.ctx.fillRect(rect[0][0],rect[1][0],rect[0][1] - rect[0][0], rect[1][1] - rect[1][0]);

    // abort selection events of the browser
    e.preventDefault();
    return e.stopPropagation();
  },

  // start the selection mode
  _onmousedown: function(e) {
    this.dragStart = mouse.abs(e);
    this.dragStartRel = mouse.rel(e);

    if (e.ctrlKey || e.metaKey) {
      this.prolongSelection = true;
    } else {
      this.prolongSelection = false;
    }
    // enable global listeners
    jbone(document.body).on('mousemove.overmove', (e) => this._onmousemove(e));
    jbone(document.body).on('mouseup.overup', (e) => this._onmouseup(e));
    return this.dragStart;
  },

  // calculates the current selection
  _calcSelection: function(dragMove) {
    // relative to first click
    var dragRel = [dragMove[0] - this.dragStart[0], dragMove[1] - this.dragStart[1]];

    // relative to target
    for (var i = 0; i <= 1; i++) {
      dragRel[i] = this.dragStartRel[i] + dragRel[i];
    }

    // 0:x, 1: y
    var rect = [[this.dragStartRel[0], dragRel[0]], [this.dragStartRel[1], dragRel[1]]];

    // swap the coordinates if needed
    for (var i = 0; i <= 1; i++) {
      if (rect[i][1] < rect[i][0]) {
        rect[i] = [rect[i][1], rect[i][0]];
      }

      // lower limit
      rect[i][0] = Math.max(rect[i][0], 0);
    }

    return rect;
  },

  _endSelection: function(dragEnd) {
    // remove listeners
    jbone(document.body).off('.overmove');
    jbone(document.body).off('.overup');

    // duplicate events
    if (this.dragStart.length === 0) { return; }

    var rect = this._calcSelection(dragEnd);

    // x
    for (var i = 0; i <= 1; i++) {
      rect[0][i] = Math.floor( rect[0][i] / this.g.zoomer.get("boxRectWidth"));
    }

    // y
    for (var i = 0; i <= 1; i++) {
      rect[1][i] = Math.floor( rect[1][i] / this.g.zoomer.get("boxRectHeight") );
    }

    // upper limit
    rect[0][1] = Math.min(this.model.getMaxLength() - 1, rect[0][1]);
    rect[1][1] = Math.min(this.model.length - 1, rect[1][1]);

    // select
    var selis = [];
    for (var j = rect[1][0]; rect[1][0] < rect[1][1] ? j <= rect[1][1] : j >= rect[1][1]; rect[1][0] < rect[1][1] ? j++ : j++) {
      var args = {seqId: this.model.at(j).get('id'), xStart: rect[0][0], xEnd: rect[0][1]};
      selis.push(new selection.possel(args));
    }

    // reset
    this.dragStart = [];
    // look for ctrl key
    if (this.prolongSelection) {
      this.g.selcol.add(selis);
    } else {
      this.g.selcol.reset(selis);
    }

    // safety check + update offset
    this.g.zoomer.setLeftOffset(rect[0][0]);
    return this.g.zoomer.setTopOffset(rect[1][0]);
  },

  // ends the selection mode
  _onmouseup: function(e) {
    return this._endSelection(mouse.abs(e));
  },

  _onmouseout: function(e) {
    return this._endSelection(mouse.abs(e));
  },

 // init the canvas
  _createCanvas: function() {
    var rectWidth = this.g.zoomer.get("boxRectWidth");
    var rectHeight = this.g.zoomer.get("boxRectHeight");

    this.el.height = this.model.length * rectHeight;
    this.el.width = this.model.getMaxLength() * rectWidth;
    this.ctx = this.el.getContext("2d");
    this.el.style.overflow = "scroll";
    return this.el.style.cursor = "crosshair";
  }
});
<commit_msg>fix #116 (typo in loop conditions)<commit_after>var OverviewBox;
var view = require("backbone-viewj");
var mouse = require("mouse-pos");
var selection = require("../g/selection/Selection");
var jbone = require("jbone");
var _ = require("underscore");

module.exports = OverviewBox = view.extend({

  className: "biojs_msa_overviewbox",
  tagName: "canvas",

  initialize: function(data) {
    this.g = data.g;
    this.listenTo( this.g.zoomer,"change:boxRectWidth change:boxRectHeight change:overviewboxPaddingTop", this.rerender);
    this.listenTo(this.g.selcol, "add reset change", this.rerender);
    this.listenTo(this.g.columns, "change:hidden", this.rerender);
    this.listenTo(this.g.colorscheme, "change:showLowerCase", this.rerender);
    this.listenTo(this.model, "change", _.debounce(this.rerender, 5));

    // color
    this.color = this.g.colorscheme.getSelectedScheme();
    this.listenTo(this.g.colorscheme, "change:scheme", function() {
      this.color = this.g.colorscheme.getSelectedScheme();
      return this.rerender();
    });
    return this.dragStart = [];
  },

  events:
    {click: "_onclick",
    mousedown: "_onmousedown"
    },

  rerender: function() {
    if (!this.g.config.get("manualRendering")) {
      return this.render();
    }
  },

  render: function() {
    this._createCanvas();
    this.el.textContent = "overview";
    this.el.style.marginTop = this.g.zoomer.get("overviewboxPaddingTop");

    // background bg for non-drawed area
    this.ctx.fillStyle = "#999999";
    this.ctx.fillRect(0,0,this.el.width,this.el.height);

    var rectWidth = this.g.zoomer.get("boxRectWidth");
    var rectHeight = this.g.zoomer.get("boxRectHeight");
    var hidden = this.g.columns.get("hidden");
    var showLowerCase = this.g.colorscheme.get("showLowerCase");

    var y = -rectHeight;
    var end = this.model.length - 1;
    for (var i = 0; 0 < end ? i <= end : i >= end; 0 < end ? i++ : i--) {
      var seq = this.model.at(i).get("seq");
      var x = 0;
      y = y + rectHeight;

      if (this.model.at(i).get("hidden")) {
        // hidden seq
        console.log(this.model.at(i).get("hidden"));
        this.ctx.fillStyle = "grey";
        this.ctx.fillRect(0,y,seq.length * rectWidth,rectHeight);
        continue;
      }

      var end1 = seq.length - 1;
      for (var j = 0; 0 < end1 ? j <= end1 : j >= end1; 0 < end1 ? j++ : j--) {
        var c = seq[j];
        // todo: optional uppercasing
        if (showLowerCase) { c = c.toUpperCase(); }
        var color = this.color.getColor(c, {pos: j});

        if (hidden.indexOf(j) >= 0) {
          color = "grey";
        }

        if ((typeof color !== "undefined" && color !== null)) {
          this.ctx.fillStyle = color;
          this.ctx.fillRect(x,y,rectWidth,rectHeight);
        }

        x = x + rectWidth;
      }
    }

    return this._drawSelection();
  },

  _drawSelection: function() {
    // hide during selection
    if (this.dragStart.length > 0 && !this.prolongSelection) { return; }

    var rectWidth = this.g.zoomer.get("boxRectWidth");
    var rectHeight = this.g.zoomer.get("boxRectHeight");
    var maxHeight = rectHeight * this.model.length;
    this.ctx.fillStyle = "#666666";
    this.ctx.globalAlpha = 0.9;
    if ( this.g.selcol.length == 0 ) {
      console.log( "" );
      //return;
    }
    var end = this.g.selcol.length - 1;
    for (var i = 0; 0 < end ? i <= end : i >= end; 0 < end ? i++ : i--) {
      var sel = this.g.selcol.at(i);
      if(!sel) continue;
      if (sel.get('type') === 'column') {
        this.ctx.fillRect( rectWidth * sel.get('xStart'),0,rectWidth *
        (sel.get('xEnd') - sel.get('xStart') + 1),maxHeight
        );
      } else if (sel.get('type') === 'row') {
        var seq = (this.model.filter(function(el) { return el.get('id') === sel.get('seqId'); }))[0];
        var pos = this.model.indexOf(seq);
        this.ctx.fillRect(0,rectHeight * pos, rectWidth * seq.get('seq').length, rectHeight);
      } else if (sel.get('type') === 'pos') {
        seq = (this.model.filter(function(el) { return el.get('id') === sel.get('seqId'); }))[0];
        pos = this.model.indexOf(seq);
        this.ctx.fillRect(rectWidth * sel.get('xStart'),rectHeight * pos, rectWidth * (sel.get('xEnd') - sel.get('xStart') + 1), rectHeight);
      }
    }

    return this.ctx.globalAlpha = 1;
  },

  _onclick: function(evt) {
    return this.g.trigger("meta:click", {seqId: this.model.get("id", {evt:evt})});
  },

  _onmousemove: function(e) {
    // duplicate events
    if (this.dragStart.length === 0) { return; }

    this.render();
    this.ctx.fillStyle = "#666666";
    this.ctx.globalAlpha = 0.9;

    var rect = this._calcSelection( mouse.abs(e) );
    this.ctx.fillRect(rect[0][0],rect[1][0],rect[0][1] - rect[0][0], rect[1][1] - rect[1][0]);

    // abort selection events of the browser
    e.preventDefault();
    return e.stopPropagation();
  },

  // start the selection mode
  _onmousedown: function(e) {
    this.dragStart = mouse.abs(e);
    this.dragStartRel = mouse.rel(e);

    if (e.ctrlKey || e.metaKey) {
      this.prolongSelection = true;
    } else {
      this.prolongSelection = false;
    }
    // enable global listeners
    jbone(document.body).on('mousemove.overmove', (e) => this._onmousemove(e));
    jbone(document.body).on('mouseup.overup', (e) => this._onmouseup(e));
    return this.dragStart;
  },

  // calculates the current selection
  _calcSelection: function(dragMove) {
    // relative to first click
    var dragRel = [dragMove[0] - this.dragStart[0], dragMove[1] - this.dragStart[1]];

    // relative to target
    for (var i = 0; i <= 1; i++) {
      dragRel[i] = this.dragStartRel[i] + dragRel[i];
    }

    // 0:x, 1: y
    var rect = [[this.dragStartRel[0], dragRel[0]], [this.dragStartRel[1], dragRel[1]]];

    // swap the coordinates if needed
    for (var i = 0; i <= 1; i++) {
      if (rect[i][1] < rect[i][0]) {
        rect[i] = [rect[i][1], rect[i][0]];
      }

      // lower limit
      rect[i][0] = Math.max(rect[i][0], 0);
    }

    return rect;
  },

  _endSelection: function(dragEnd) {
    // remove listeners
    jbone(document.body).off('.overmove');
    jbone(document.body).off('.overup');

    // duplicate events
    if (this.dragStart.length === 0) { return; }

    var rect = this._calcSelection(dragEnd);

    // x
    for (var i = 0; i <= 1; i++) {
      rect[0][i] = Math.floor( rect[0][i] / this.g.zoomer.get("boxRectWidth"));
    }

    // y
    for (var i = 0; i <= 1; i++) {
      rect[1][i] = Math.floor( rect[1][i] / this.g.zoomer.get("boxRectHeight") );
    }

    // upper limit
    rect[0][1] = Math.min(this.model.getMaxLength() - 1, rect[0][1]);
    rect[1][1] = Math.min(this.model.length - 1, rect[1][1]);

    // select
    var selis = [];
    for (var j = rect[1][0]; rect[1][0] < rect[1][1] ? j <= rect[1][1] : j >= rect[1][1]; rect[1][0] < rect[1][1] ? j++ : j++) {
      var args = {seqId: this.model.at(j).get('id'), xStart: rect[0][0], xEnd: rect[0][1]};
      selis.push(new selection.possel(args));
    }

    // reset
    this.dragStart = [];
    // look for ctrl key
    if (this.prolongSelection) {
      this.g.selcol.add(selis);
    } else {
      this.g.selcol.reset(selis);
    }

    // safety check + update offset
    this.g.zoomer.setLeftOffset(rect[0][0]);
    return this.g.zoomer.setTopOffset(rect[1][0]);
  },

  // ends the selection mode
  _onmouseup: function(e) {
    return this._endSelection(mouse.abs(e));
  },

  _onmouseout: function(e) {
    return this._endSelection(mouse.abs(e));
  },

 // init the canvas
  _createCanvas: function() {
    var rectWidth = this.g.zoomer.get("boxRectWidth");
    var rectHeight = this.g.zoomer.get("boxRectHeight");

    this.el.height = this.model.length * rectHeight;
    this.el.width = this.model.getMaxLength() * rectWidth;
    this.ctx = this.el.getContext("2d");
    this.el.style.overflow = "scroll";
    return this.el.style.cursor = "crosshair";
  }
});
<|endoftext|>


<commit_before>/**
 * Creates the yaws-object and the socketConnect method
 * @returns {undefined}
 */
(function () {
    "use strict";
    if (!("yaws" in window)) {
        window.yaws = {};
    }
    /**
     *
     * @param {function} onMessage
     * @param {object} options optional
     * @param {string} url optional
     * @returns {Websocket}
     */
    window.yaws.socketConnect = function (onMessage, options, url) {
        /**
         * returns the Websocket that provides the best reliability of the avaible ones
         * @param {string} url optional
         * @param {object} options optional
         * @returns {Websocket}
         */
        var getSocket = function (url, options) {
            if (!("Websocket" in window)) {
                throw new Error("No Websocket Implementation found");
            }
            /**
             * returns the value given or a default if that doesn't exist
             * @param {object} options optional
             * @param {string} key optional
             * @returns {number}
             */
            var getValue = function (options, key) {
                var canUseProperty = function (object, property) {
                    return object && typeof object === "object" && property && object[property] && typeof object[property] === "number";
                };
                var defaults = {
                    timeout: 5000,
                    maxInterval: 60000
                };
                if (!canUseProperty(options, key)) {
                    return defaults[key];
                }
                if (parseInt(options[key], 10) !== options[key]) {
                    return parseInt(options[key] * 1000, 10);
                }
                return options[key];
            };
            /**
             * returns an url to connect to. If one is supplied replaces placeholders for path and protocol
             * @param {string} url optional
             * @returns {string}
             */
            var getUrl = function(url) {
                /**
                 * returns basic location information as a object containing protocol, path and domain
                 * @returns {object}
                 **/
                var getLocation = function() {
                    if("protocol" in window.location && "path" in window.location && "domainname" in window.location) {
                        return {
                            protocol: "ws"+(window.location.protocol === "https"?"s":""),
                            domain: window.location.domainname,
                            path: window.location.path
                        };
                    }
                    var location={
                        protocol: "ws"+(window.location.href.match(/^https:\/\//i)?"s":""),
                        path: window.location.href.replace(/^https?:\/\/(.*?)(\?.*?)?(#.*?)?$/i,"$1"),
                        domain: ""
                    };
                    location.domain = location.path.replace(/\/.*/,"").replace(/:.*/,"");
                    location.path = location.path.replace(/^.*?(\/|$)/,"$1");
                    return location;
                };
                if(!url || typeof url !== "string") {
                    url = "{protocol}://{domain}{path}";
                }
                var location = getLocation();
                return url.replace("{protocol}",location.protocol).replace("{path}",location.path).replace("{domain}",location.domain);
            };
            if ("RobustWebSocket" in window) {
                /*global RobustWebSocket*/
                var ws = new RobustWebSocket(getUrl(url), [], {
                    timeout: getValue(options, "timeout"),
                    shouldReconnect: function (event, ws) {
                        if (event.code === 1008 || event.code === 1011) {
                            return null;
                        }
                        return Math.min(Math.pow(1.05, (ws.attempts + 2) * ws.attempts / 2) * 500, ws.maxInterval);
                    }
                });
                ws.maxInterval = getValue(options, "maxInterval");
                return ws;
            }
            if ("ReconnectingWebSocket" in window) {
                /*global ReconnectingWebSocket*/
                return new ReconnectingWebSocket(getUrl(url), [], {
                    maxReconnectInterval: getValue(options, "maxInterval"),
                    reconnectDecay: 2.5,
                    timeoutInterval: getValue(options, "timeout")
                });
            }
            /*global Websocket*/
            return new Websocket(getUrl(url), []);
        };
        /**
         * returns a function handling the binary data returned by the socket
         * @returns {Function}
         */
        var getOnMessage = function () {
            /**
             * handles the Blob or ArrayBuffer returned by the socket
             * @param {Event} event
             * @returns {undefined}
             */
            return function (event) {
                if (typeof event.data !== "object") {
                    throw new Error("Got something (" + (typeof event.data) + ") that shouldn't be returned by the socket.");
                }
                if (event.data instanceof ArrayBuffer) {
                    event.data = new Blob([event.data]);
                }
                if (event.data instanceof Blob) {
                    event.target.onMessageHandler(event.data);
                    return;
                }
                throw new Error("Got an object of a type that shouldn't be returned by the socket.");
            };
        };
        if (typeof onMessage !== "function") {
            throw new Error("The first parameter 'onMessage' is required to be a function accepting a single parameter of type Blob.");
        }
        var Socket = getSocket(url, options);
        Socket.onMessageHandler = onMessage;
        Socket.onmessage = getOnMessage();
        return Socket;
    };
})();
<commit_msg>Update yawsSocketConnect.js<commit_after>/**
 * Creates the yaws-object and the socketConnect method
 * @returns {undefined}
 */
(function () {
    "use strict";
    if (!("yaws" in window)) {
        window.yaws = {};
    }
    /**
     *
     * @param {function} onMessage
     * @param {object} options optional
     * @param {string} url optional
     * @returns {Websocket}
     */
    window.yaws.socketConnect = function (onMessage, options, url) {
        /**
         * returns the Websocket that provides the best reliability of the avaible ones
         * @param {string} url optional
         * @param {object} options optional
         * @returns {Websocket}
         */
        var getSocket = function (url, options) {
            if (!("Websocket" in window)) {
                throw new Error("No Websocket Implementation found");
            }
            /**
             * returns the value given or a default if that doesn't exist
             * @param {object} options optional
             * @param {string} key optional
             * @returns {number}
             */
            var getValue = function (options, key) {
                var canUseProperty = function (object, property) {
                    return object && typeof object === "object" && property && object[property] && typeof object[property] === "number";
                };
                var defaults = {
                    timeout: 5000,
                    maxInterval: 60000
                };
                if (!canUseProperty(options, key)) {
                    return defaults[key];
                }
                if (parseInt(options[key], 10) !== options[key]) {
                    return parseInt(options[key] * 1000, 10);
                }
                return options[key];
            };
            /**
             * returns an url to connect to. If one is supplied replaces placeholders for path and protocol
             * @param {string} url optional
             * @returns {string}
             */
            var getUrl = function(url) {
                /**
                 * returns basic location information as a object containing protocol, path and domain
                 * @returns {object}
                 **/
                var getLocation = function() {
                    if("protocol" in window.location && "path" in window.location && "domainname" in window.location) {
                        return {
                            protocol: "ws"+(window.location.protocol === "https"?"s":""),
                            domain: window.location.domainname,
                            path: window.location.path
                        };
                    }
                    var location={
                        protocol: "ws"+(window.location.href.match(/^https:\/\//i)?"s":""),
                        path: window.location.href.replace(/^https?:\/\/(.*?)(\?.*?)?(#.*?)?$/i,"$1"),
                        domain: ""
                    };
                    location.domain = location.path.replace(/\/.*/,"").replace(/:.*/,"");
                    location.path = location.path.replace(/^.*?(\/|$)/,"$1");
                    return location;
                };
                if(!url || typeof url !== "string") {
                    url = "{protocol}://{domain}{path}";
                }
                var location = getLocation();
                return url.replace("{protocol}",location.protocol).replace("{path}",location.path).replace("{domain}",location.domain);
            };
            if ("RobustWebSocket" in window) {
                /*global RobustWebSocket*/
                var ws = new RobustWebSocket(getUrl(url), [], {
                    timeout: getValue(options, "timeout"),
                    shouldReconnect: function (event, ws) {
                        if (event.code === 1008 || event.code === 1011) {
                            return null;
                        }
                        var calculated = Math.pow(1.05, (ws.attempts + 2) * ws.attempts / 2) * 500;
                        if(Number.isNaN(calculated) || caluculated === Infinity) {
                            return ws.maxInterval;
                        }
                        return Math.min(calculated , ws.maxInterval);
                    }
                });
                ws.maxInterval = getValue(options, "maxInterval");
                return ws;
            }
            if ("ReconnectingWebSocket" in window) {
                /*global ReconnectingWebSocket*/
                return new ReconnectingWebSocket(getUrl(url), [], {
                    maxReconnectInterval: getValue(options, "maxInterval"),
                    reconnectDecay: 2.5,
                    timeoutInterval: getValue(options, "timeout")
                });
            }
            /*global Websocket*/
            return new Websocket(getUrl(url), []);
        };
        /**
         * returns a function handling the binary data returned by the socket
         * @returns {Function}
         */
        var getOnMessage = function () {
            /**
             * handles the Blob or ArrayBuffer returned by the socket
             * @param {Event} event
             * @returns {undefined}
             */
            return function (event) {
                if (typeof event.data !== "object") {
                    throw new Error("Got something (" + (typeof event.data) + ") that shouldn't be returned by the socket.");
                }
                if (event.data instanceof ArrayBuffer) {
                    event.data = new Blob([event.data]);
                }
                if (event.data instanceof Blob) {
                    event.target.onMessageHandler(event.data);
                    return;
                }
                throw new Error("Got an object of a type that shouldn't be returned by the socket.");
            };
        };
        if (typeof onMessage !== "function") {
            throw new Error("The first parameter 'onMessage' is required to be a function accepting a single parameter of type Blob.");
        }
        var Socket = getSocket(url, options);
        Socket.onMessageHandler = onMessage;
        Socket.onmessage = getOnMessage();
        return Socket;
    };
})();
<|endoftext|>


<commit_before>/**
 * Creates the yaws-object and the socketConnect method
 * @returns {undefined}
 */
(function () {
    "use strict";
    if (!("yaws" in window)) {
        window.yaws = {};
    }
    /**
     *
     * @param {function} onMessage
     * @param {object} options optional
     * @param {string} url optional
     * @returns {Websocket}
     */
    window.yaws.socketConnect = function (onMessage, options, url) {
        /**
         * returns the Websocket that provides the best reliability of the avaible ones
         * @param {string} url optional
         * @param {object} options optional
         * @returns {Websocket}
         */
        var getSocket = function (url, options) {
            if (!("Websocket" in window)) {
                throw "No Websocket Implementation found";
            }
            /**
             * returns the value given or a default if that doesn't exist
             * @param {object} options optional
             * @param {string} key optional
             * @returns {number}
             */
            var getValue = function (options, key) {
                var canUseProperty = function (object, property) {
                    return object && typeof object === "object" && property && object[property] && typeof object[property] === "number";
                };
                var defaults = {
                    timeout: 5000,
                    maxInterval: 60000
                };
                if (!canUseProperty(options, key)) {
                    if (key in defaults) {
                        return defaults[key];
                    }
                    throw "No default defined for " + key + ".";
                }
                if (parseInt(options[key], 10) !== options[key]) {
                    return parseInt(options[key] * 1000, 10);
                }
                return options[key];
            };
            /**
             * returns an url to connect to. If one is supplied replaces placeholders for path and protocol
             * @param {string} url optional
             * @returns {string}
             */
            var getUrl = function(url) {
                /**
                 * returns basic location information as a object containing protocol, path and domain
                 * @returns {object}
                 **/
                var getLocation = function() {
                    if("protocol" in window.location && "path" in window.location && "domainname" in window.location) {
                        return {
                            protocol: "ws"+(window.location.protocol === "https"?"s":""),
                            domain: window.location.domainname,
                            path: window.location.path
                        };
                    }
                    var location={
                        protocol: "ws"+(window.location.href.match(/^https:\/\//i)?"s":""),
                        path: window.location.href.replace(/^https?:\/\/(.*?)(\?.*?)?(#.*?)?/i,"$1"),
                        domain: ""
                    };
                    location.domain = location.path.replace(/\/.*/,"").replace(/:.*/,"");
                    location.path = location.path.replace(/^.*?(\/|$)/,"$1");
                    return location;
                };
                if(!url || typeof url !== "string") {
                    url = "{protocol}://{domain}{path}";
                }
                var location = getLocation();
                return url.replace("{protocol}",location.protocol).replace("{path}",location.path).replace("{domain}",location.domain);
            };
            if ("RobustWebSocket" in window) {
                /*global RobustWebSocket*/
                var ws = new RobustWebSocket(getUrl(url), [], {
                    timeout: getValue(options, "timeout"),
                    shouldReconnect: function (event, ws) {
                        if (event.code === 1008 || event.code === 1011) {
                            return null;
                        }
                        return Math.min(Math.pow(1.05, (ws.attempts + 2) * ws.attempts / 2) * 500, ws.maxInterval);
                    }
                });
                ws.maxInterval = getValue(options, "maxInterval");
                return ws;
            }
            if ("ReconnectingWebSocket" in window) {
                /*global ReconnectingWebSocket*/
                return new ReconnectingWebSocket(getUrl(url), [], {
                    maxReconnectInterval: getValue(options, "maxInterval"),
                    reconnectDecay: 2.5,
                    timeoutInterval: getValue(options, "timeout")
                });
            }
            /*global Websocket*/
            return new Websocket(getUrl(url), []);
        };
        /**
         * returns a function handling the binary data returned by the socket
         * @returns {Function}
         */
        var getOnMessage = function () {
            if ("FileReader" in window) {
                /**
                 * handles the Blob or ArrayBuffer returned by the socket
                 * @param {Event} event
                 * @returns {undefined}
                 */
                return function (event) {
                    if (typeof event.data !== "object") {
                        throw "Got something (" + (typeof event.data) + ") that shouldn't be returned by the socket.";
                    }
                    if (event.data instanceof Blob) {
                        event.target.onMessageHandler(event.data);
                        return;
                    }
                    if (event.data instanceof ArrayBuffer) {
                        var reader = new FileReader();
                        reader.onLoadHandler = event.target.onMessageHandler;
                        reader.onload = function (event) {
                            event.target.onLoadHandler(event.target.result);
                        };
                        reader.readAsArrayBuffer(event.data);
                        return;
                    }
                    throw "Got an object of a type that shouldn't be returned by the socket.";
                };
            }
            /**
             * handles the Blob returned by the socket
             * @param {Event} event
             * @returns {undefined}
             */
            return function (event) {
                if (typeof event.data !== "object") {
                    throw "Got something (" + (typeof event.data) + ") that shouldn't be returned by the socket.";
                }
                if (event.data instanceof Blob) {
                    event.target.onMessageHandler(event.data);
                    return;
                }
                throw "Got an object of a type that shouldn't be returned by the socket.";
            };
        };
        if (typeof onMessage !== "function") {
            throw "The first parameter 'onMessage' is required to be a function accepting a single parameter of type Blob.";
        }
        var Socket = getSocket(url, options);
        Socket.onMessageHandler = onMessage;
        Socket.onmessage = getOnMessage();
        return Socket;
    };
})();
<commit_msg>Update yawsSocketConnect.js<commit_after>/**
 * Creates the yaws-object and the socketConnect method
 * @returns {undefined}
 */
(function () {
    "use strict";
    if (!("yaws" in window)) {
        window.yaws = {};
    }
    /**
     *
     * @param {function} onMessage
     * @param {object} options optional
     * @param {string} url optional
     * @returns {Websocket}
     */
    window.yaws.socketConnect = function (onMessage, options, url) {
        /**
         * returns the Websocket that provides the best reliability of the avaible ones
         * @param {string} url optional
         * @param {object} options optional
         * @returns {Websocket}
         */
        var getSocket = function (url, options) {
            if (!("Websocket" in window)) {
                throw "No Websocket Implementation found";
            }
            /**
             * returns the value given or a default if that doesn't exist
             * @param {object} options optional
             * @param {string} key optional
             * @returns {number}
             */
            var getValue = function (options, key) {
                var canUseProperty = function (object, property) {
                    return object && typeof object === "object" && property && object[property] && typeof object[property] === "number";
                };
                var defaults = {
                    timeout: 5000,
                    maxInterval: 60000
                };
                if (!canUseProperty(options, key)) {
                    if (key in defaults) {
                        return defaults[key];
                    }
                    throw "No default defined for " + key + ".";
                }
                if (parseInt(options[key], 10) !== options[key]) {
                    return parseInt(options[key] * 1000, 10);
                }
                return options[key];
            };
            /**
             * returns an url to connect to. If one is supplied replaces placeholders for path and protocol
             * @param {string} url optional
             * @returns {string}
             */
            var getUrl = function(url) {
                /**
                 * returns basic location information as a object containing protocol, path and domain
                 * @returns {object}
                 **/
                var getLocation = function() {
                    if("protocol" in window.location && "path" in window.location && "domainname" in window.location) {
                        return {
                            protocol: "ws"+(window.location.protocol === "https"?"s":""),
                            domain: window.location.domainname,
                            path: window.location.path
                        };
                    }
                    var location={
                        protocol: "ws"+(window.location.href.match(/^https:\/\//i)?"s":""),
                        path: window.location.href.replace(/^https?:\/\/(.*?)(\?.*?)?(#.*?)?$/i,"$1"),
                        domain: ""
                    };
                    location.domain = location.path.replace(/\/.*/,"").replace(/:.*/,"");
                    location.path = location.path.replace(/^.*?(\/|$)/,"$1");
                    return location;
                };
                if(!url || typeof url !== "string") {
                    url = "{protocol}://{domain}{path}";
                }
                var location = getLocation();
                return url.replace("{protocol}",location.protocol).replace("{path}",location.path).replace("{domain}",location.domain);
            };
            if ("RobustWebSocket" in window) {
                /*global RobustWebSocket*/
                var ws = new RobustWebSocket(getUrl(url), [], {
                    timeout: getValue(options, "timeout"),
                    shouldReconnect: function (event, ws) {
                        if (event.code === 1008 || event.code === 1011) {
                            return null;
                        }
                        return Math.min(Math.pow(1.05, (ws.attempts + 2) * ws.attempts / 2) * 500, ws.maxInterval);
                    }
                });
                ws.maxInterval = getValue(options, "maxInterval");
                return ws;
            }
            if ("ReconnectingWebSocket" in window) {
                /*global ReconnectingWebSocket*/
                return new ReconnectingWebSocket(getUrl(url), [], {
                    maxReconnectInterval: getValue(options, "maxInterval"),
                    reconnectDecay: 2.5,
                    timeoutInterval: getValue(options, "timeout")
                });
            }
            /*global Websocket*/
            return new Websocket(getUrl(url), []);
        };
        /**
         * returns a function handling the binary data returned by the socket
         * @returns {Function}
         */
        var getOnMessage = function () {
            if ("FileReader" in window) {
                /**
                 * handles the Blob or ArrayBuffer returned by the socket
                 * @param {Event} event
                 * @returns {undefined}
                 */
                return function (event) {
                    if (typeof event.data !== "object") {
                        throw "Got something (" + (typeof event.data) + ") that shouldn't be returned by the socket.";
                    }
                    if (event.data instanceof Blob) {
                        event.target.onMessageHandler(event.data);
                        return;
                    }
                    if (event.data instanceof ArrayBuffer) {
                        var reader = new FileReader();
                        reader.onLoadHandler = event.target.onMessageHandler;
                        reader.onload = function (event) {
                            event.target.onLoadHandler(event.target.result);
                        };
                        reader.readAsArrayBuffer(event.data);
                        return;
                    }
                    throw "Got an object of a type that shouldn't be returned by the socket.";
                };
            }
            /**
             * handles the Blob returned by the socket
             * @param {Event} event
             * @returns {undefined}
             */
            return function (event) {
                if (typeof event.data !== "object") {
                    throw "Got something (" + (typeof event.data) + ") that shouldn't be returned by the socket.";
                }
                if (event.data instanceof Blob) {
                    event.target.onMessageHandler(event.data);
                    return;
                }
                throw "Got an object of a type that shouldn't be returned by the socket.";
            };
        };
        if (typeof onMessage !== "function") {
            throw "The first parameter 'onMessage' is required to be a function accepting a single parameter of type Blob.";
        }
        var Socket = getSocket(url, options);
        Socket.onMessageHandler = onMessage;
        Socket.onmessage = getOnMessage();
        return Socket;
    };
})();
<|endoftext|>


<commit_before>const discord = require('discord.js');
const Command = require('./command');
const CommandGroup = require('./command-group');
const CommandBuilder = require('./command-builder');
const CommandMessage = require('./command-message');

/** Handles registration and searching of commands and groups */
class CommandRegistry {
	/** @param {CommandoClient} [client] - Client to use  */
	constructor(client) {
		/**
		 * The client this registry is for
		 * @type {CommandoClient}
		 */
		this.client = client;

		/**
		 * Registered commands
		 * @type {Collection<string, Command>}
		 */
		this.commands = new discord.Collection();

		/**
		 * Registered command groups
		 * @type {Collection<string, Command>}
		 */
		this.groups = new discord.Collection();

		/**
		 * Registered objects for the eval command
		 * @type {Object}
		 */
		this.evalObjects = {};

		/**
		 * Fully resolved path to the bot's commands directory
		 * @type {?string}
		 */
		this.commandsPath = null;
	}

	/**
	 * Registers a single group
	 * @param {CommandGroup|function|string[]|string} group - A CommandGroup instance, a constructor,
	 * an array of [ID, Name], or the group ID
	 * @param {string} [name] name - Name for the group (if the first argument is the group ID)
	 * @return {CommandRegistry}
	 * @see {@link CommandRegistry#registerGroups}
	 */
	registerGroup(group, name) {
		if(typeof group === 'string') return this.registerGroups([[group, name]]);
		return this.registerGroups([group]);
	}

	/**
	 * Registers multiple groups
	 * @param {CommandGroup[]|function[]|Array<Array<string>>} groups - An array of CommandGroup instances, constructors,
	 * or arrays of [ID, Name]
	 * @return {CommandRegistry}
	 */
	registerGroups(groups) {
		if(!Array.isArray(groups)) throw new TypeError('Groups must be an array.');
		for(let group of groups) {
			if(typeof group === 'function') group = new CommandGroup(this.client);
			else if(Array.isArray(group)) group = new CommandGroup(this.client, ...group);
			else if(!(group instanceof CommandGroup)) group = new CommandGroup(this, group.id, group.name, group.commands);

			const existing = this.groups.find(grp => grp.id === group.id);
			if(existing) {
				existing.name = group.name;
				this.client.emit('debug', `Group ${group.id} is already registered; renamed it to "${group.name}".`);
			} else {
				this.groups.set(group.id, group);
				/**
				 * Emitted when a group is registered
				 * @event CommandoClient#groupRegister
				 * @param {CommandGroup} group - Group that was registered
				 * @param {CommandRegistry} registry - Registry that the group was registered to
				 */
				this.client.emit('groupRegister', group, this);
				this.client.emit('debug', `Registered group ${group.id}.`);
			}
		}
		return this;
	}

	/**
	 * Registers a single command
	 * @param {Command|CommandBuilder|function} command - Either a Command instance, or a constructor for one
	 * @return {CommandRegistry}
	 * @see {@link CommandRegistry#registerCommands}
	 */
	registerCommand(command) {
		return this.registerCommands([command]);
	}

	/**
	 * Registers multiple commands
	 * @param {Command[]|CommandBuilder[]|function[]} commands - An array of Command instances or constructors
	 * @return {CommandRegistry}
	 */
	registerCommands(commands) {
		if(!Array.isArray(commands)) throw new TypeError('Commands must be an array.');
		for(let command of commands) {
			if(typeof command === 'function') command = new command(this.client); // eslint-disable-line new-cap
			else if(command instanceof CommandBuilder) command = command.command;

			// Verify that it's an actual command
			if(!command || !(command instanceof Command)) {
				this.client.emit('warn', 'Attempting to register an invalid command object: ${command}; skipping.');
				continue;
			}

			// Make sure there aren't any conflicts
			if(this.commands.some(cmd => cmd.name === command.name || cmd.aliases.includes(command.name))) {
				throw new Error(`A command with the name/alias "${command.name}" is already registered.`);
			}
			for(const alias of command.aliases) {
				if(this.commands.some(cmd => cmd.name === alias || cmd.aliases.some(ali => ali === alias))) {
					throw new Error(`A command with the name/alias "${alias}" is already registered.`);
				}
			}
			const group = this.groups.find(grp => grp.id === command.groupID);
			if(!group) throw new Error(`Group "${command.groupID}" is not registered.`);
			if(group.commands.some(cmd => cmd.memberName === command.memberName)) {
				throw new Error(`A command with the member name "${command.memberName}" is already registered in ${group.id}`);
			}

			// Add the command
			command.group = group;
			group.commands.set(command.name, command);
			this.commands.set(command.name, command);
			/**
			 * Emitted when a command is registered
			 * @event CommandoClient#commandRegister
			 * @param {CommandGroup} command - Command that was registered
			 * @param {CommandRegistry} registry - Registry that the command was registered to
			 */
			this.client.emit('commandRegister', command, this);
			this.client.emit('debug', `Registered command ${group.id}:${command.memberName}.`);
		}

		return this;
	}

	/**
	 * Registers all commands in a given directory. The files must export a Command class constructor or instance,
	 * or a CommandBuilder instance.
	 * @param {string|RequireAllOptions} options - The path to the directory, or a require-all options object
	 * @return {CommandRegistry}
	 */
	registerCommandsIn(options) {
		const obj = require('require-all')(options);
		const commands = [];
		for(const group of Object.values(obj)) {
			for(const command of Object.values(group)) commands.push(command);
		}
		if(typeof options === 'string' && !this.commandsPath) this.commandsPath = options;
		return this.registerCommands(commands);
	}

	/**
	 * Registers both the default groups and commands
	 * @return {CommandRegistry}
	 */
	registerDefaults() {
		this.registerDefaultGroups();
		this.registerDefaultCommands();
		return this;
	}

	/**
	 * Registers the default groups
	 * @return {CommandRegistry}
	 */
	registerDefaultGroups() {
		return this.registerGroups([
			['commands', 'Commands', true],
			['util', 'Utility']
		]);
	}

	/**
	 * Registers the default commands to the registry
	 * @param {Object} [options] - Object specifying what commands to register
	 * @param {boolean} [options.help=true] - Whether or not to register the built-in help command
	 * @param {boolean} [options.prefix=true] - Whether or not to register the built-in prefix command
	 * @param {boolean} [options.eval_=true] - Whether or not to register the built-in eval command
	 * @param {boolean} [options.ping=true] - Whether or not to register the built-in ping command
	 * @param {boolean} [options.commandState=true] - Whether or not to register the built-in command state commands
	 * (enable, disable, reload, list groups)
	 * @return {CommandRegistry}
	 */
	registerDefaultCommands({ help = true, prefix = true, ping = true, eval_ = true, commandState = true } = {}) {
		if(help) this.registerCommand(require('./commands/util/help'));
		if(prefix) this.registerCommand(require('./commands/util/prefix'));
		if(ping) this.registerCommand(require('./commands/util/ping'));
		if(eval_) this.registerCommand(require('./commands/util/eval'));
		if(commandState) {
			this.registerCommands([
				require('./commands/commands/groups'),
				require('./commands/commands/enable'),
				require('./commands/commands/disable'),
				require('./commands/commands/reload'),
				require('./commands/commands/load'),
				require('./commands/commands/unload')
			]);
		}
		return this;
	}

	/**
	 * Reregisters a command (does not support changing name, group, or memberName)
	 * @param {Command|CommandBuilder|function} command - New command
	 * @param {Command} oldCommand - Old command
	 */
	reregisterCommand(command, oldCommand) {
		if(typeof command === 'function') command = new command(this.client); // eslint-disable-line new-cap
		else if(command instanceof CommandBuilder) command = command.command;
		if(command.name !== oldCommand.name) throw new Error('Command name cannot change.');
		if(command.groupID !== oldCommand.groupID) throw new Error('Command group cannot change.');
		if(command.memberName !== oldCommand.memberName) throw new Error('Command memberName cannot change.');
		command.group = this.resolveGroup(command.groupID);
		command.group.commands.set(command.name, command);
		this.commands.set(command.name, command);
		/**
		 * Emitted when a command is reregistered
		 * @event CommandoClient#commandReregister
		 * @param {Command} newCommand - New command
		 * @param {Command} oldCommand - Old command
		 */
		this.client.emit('commandReregister', command, oldCommand);
		this.client.emit('debug', `Reregistered command ${command.groupID}:${command.memberName}.`);
	}

	/**
	 * Unregisters a command
	 * @param {Command} command - Command to unregister
	 */
	unregisterCommand(command) {
		this.commands.delete(command.name);
		command.group.commands.delete(command.name);
		/**
		 * Emitted when a command is unregistered
		 * @event CommandoClient#commandUnregister
		 * @param {Command} command - Command that was unregistered
		 */
		this.client.emit('commandUnregister', command);
		this.client.emit('debug', `Unregistered command ${command.groupID}:${command.memberName}.`);
	}

	/**
	 * Registers a single object to be usable by the eval command
	 * @param {string} key - The key for the object
	 * @param {Object} obj - The object
	 * @return {CommandRegistry}
	 * @see {@link CommandRegistry#registerEvalObjects}
	 */
	registerEvalObject(key, obj) {
		const registerObj = {};
		registerObj[key] = obj;
		return this.registerEvalObjects(registerObj);
	}

	/**
	 * Registers multiple objects to be usable by the eval command
	 * @param {Object} obj - An object of keys: values
	 * @return {CommandRegistry}
	 */
	registerEvalObjects(obj) {
		Object.assign(this.evalObjects, obj);
		return this;
	}

	/**
	 * Create a command builder
	 * @param {CommandInfo} [info] - The command information
	 * @param {CommandBuilderFunctions} [funcs] - The command functions to set
	 * @return {CommandBuilder} The builder
	 */
	buildCommand(info = null, funcs = null) {
		return new CommandBuilder(this, info, funcs);
	}

	/**
	 * Finds all groups that match the search string
	 * @param {string} [searchString] - The string to search for
	 * @param {boolean} [exact=false] - Whether the search should be exact
	 * @return {CommandGroup[]} All groups that are found
	 */
	findGroups(searchString = null, exact = false) {
		if(!searchString) return this.groups;

		// Find all matches
		const lcSearch = searchString.toLowerCase();
		const matchedGroups = this.groups.filterArray(
			exact ? groupFilterExact(lcSearch) : groupFilterInexact(lcSearch)
		);
		if(exact) return matchedGroups;

		// See if there's an exact match
		for(const group of matchedGroups) {
			if(group.name.toLowerCase() === lcSearch || group.id === lcSearch) return [group];
		}
		return matchedGroups;
	}

	/**
	 * A CommandGroupResolvable can be:
	 * * A CommandGroup
	 * * A group ID
	 * @typedef {CommandGroup|string} CommandGroupResolvable
	 */

	/**
	 * Resolves a CommandGroupResolvable to a CommandGroup object
	 * @param {CommandGroupResolvable} group - The group to resolve
	 * @return {CommandGroup} The resolved CommandGroup
	 */
	resolveGroup(group) {
		if(group instanceof CommandGroup) return group;
		if(typeof group === 'string') {
			const groups = this.findGroups(group, true);
			if(groups.length === 1) return groups[0];
		}
		throw new Error('Unable to resolve group.');
	}

	/**
	 * Finds all commands that match the search string
	 * @param {string} [searchString] - The string to search for
	 * @param {boolean} [exact=false] - Whether the search should be exact
	 * @param {Message} [message] - The message to check usability against
	 * @return {Command[]} All commands that are found
	 */
	findCommands(searchString = null, exact = false, message = null) {
		if(!searchString) return message ? this.commands.filterArray(cmd => cmd.isUsable(message)) : this.commands;

		// Find all matches
		const lcSearch = searchString.toLowerCase();
		const matchedCommands = this.commands.filterArray(
			exact ? commandFilterExact(lcSearch) : commandFilterInexact(lcSearch)
		);
		if(exact) return matchedCommands;

		// See if there's an exact match
		for(const command of matchedCommands) {
			if(command.name === lcSearch || (command.aliases && command.aliases.some(ali => ali === lcSearch))) {
				return [command];
			}
		}

		return matchedCommands;
	}

	/**
	 * A CommandResolvable can be:
	 * * A Command
	 * * A command name
	 * * A CommandMessage
	 * @typedef {Command|string} CommandResolvable
	 */

	/**
	 * Resolves a CommandResolvable to a Command object
	 * @param {CommandResolvable} command - The command to resolve
	 * @return {Command} The resolved Command
	 */
	resolveCommand(command) {
		if(command instanceof Command) return command;
		if(command instanceof CommandMessage) return command.command;
		if(typeof command === 'string') {
			const commands = this.findCommands(command, true);
			if(commands.length === 1) return commands[0];
		}
		throw new Error('Unable to resolve command.');
	}
}

function groupFilterExact(search) {
	return grp => grp.id === search || grp.name.toLowerCase() === search;
}

function groupFilterInexact(search) {
	return grp => grp.id.includes(search) || grp.name.toLowerCase().includes(search);
}

function commandFilterExact(search) {
	return cmd => cmd.name === search ||
		(cmd.aliases && cmd.aliases.some(ali => ali === search)) ||
		`${cmd.groupID}:${cmd.memberName}` === search;
}

function commandFilterInexact(search) {
	return cmd => cmd.name.includes(search) ||
		`${cmd.groupID}:${cmd.memberName}` === search ||
		(cmd.aliases && cmd.aliases.some(ali => ali.includes(search)));
}

module.exports = CommandRegistry;
<commit_msg>Simplify a line<commit_after>const discord = require('discord.js');
const Command = require('./command');
const CommandGroup = require('./command-group');
const CommandBuilder = require('./command-builder');
const CommandMessage = require('./command-message');

/** Handles registration and searching of commands and groups */
class CommandRegistry {
	/** @param {CommandoClient} [client] - Client to use  */
	constructor(client) {
		/**
		 * The client this registry is for
		 * @type {CommandoClient}
		 */
		this.client = client;

		/**
		 * Registered commands
		 * @type {Collection<string, Command>}
		 */
		this.commands = new discord.Collection();

		/**
		 * Registered command groups
		 * @type {Collection<string, Command>}
		 */
		this.groups = new discord.Collection();

		/**
		 * Registered objects for the eval command
		 * @type {Object}
		 */
		this.evalObjects = {};

		/**
		 * Fully resolved path to the bot's commands directory
		 * @type {?string}
		 */
		this.commandsPath = null;
	}

	/**
	 * Registers a single group
	 * @param {CommandGroup|function|string[]|string} group - A CommandGroup instance, a constructor,
	 * an array of [ID, Name], or the group ID
	 * @param {string} [name] name - Name for the group (if the first argument is the group ID)
	 * @return {CommandRegistry}
	 * @see {@link CommandRegistry#registerGroups}
	 */
	registerGroup(group, name) {
		if(typeof group === 'string') return this.registerGroups([[group, name]]);
		return this.registerGroups([group]);
	}

	/**
	 * Registers multiple groups
	 * @param {CommandGroup[]|function[]|Array<Array<string>>} groups - An array of CommandGroup instances, constructors,
	 * or arrays of [ID, Name]
	 * @return {CommandRegistry}
	 */
	registerGroups(groups) {
		if(!Array.isArray(groups)) throw new TypeError('Groups must be an array.');
		for(let group of groups) {
			if(typeof group === 'function') group = new CommandGroup(this.client);
			else if(Array.isArray(group)) group = new CommandGroup(this.client, ...group);
			else if(!(group instanceof CommandGroup)) group = new CommandGroup(this, group.id, group.name, group.commands);

			const existing = this.groups.find(grp => grp.id === group.id);
			if(existing) {
				existing.name = group.name;
				this.client.emit('debug', `Group ${group.id} is already registered; renamed it to "${group.name}".`);
			} else {
				this.groups.set(group.id, group);
				/**
				 * Emitted when a group is registered
				 * @event CommandoClient#groupRegister
				 * @param {CommandGroup} group - Group that was registered
				 * @param {CommandRegistry} registry - Registry that the group was registered to
				 */
				this.client.emit('groupRegister', group, this);
				this.client.emit('debug', `Registered group ${group.id}.`);
			}
		}
		return this;
	}

	/**
	 * Registers a single command
	 * @param {Command|CommandBuilder|function} command - Either a Command instance, or a constructor for one
	 * @return {CommandRegistry}
	 * @see {@link CommandRegistry#registerCommands}
	 */
	registerCommand(command) {
		return this.registerCommands([command]);
	}

	/**
	 * Registers multiple commands
	 * @param {Command[]|CommandBuilder[]|function[]} commands - An array of Command instances or constructors
	 * @return {CommandRegistry}
	 */
	registerCommands(commands) {
		if(!Array.isArray(commands)) throw new TypeError('Commands must be an array.');
		for(let command of commands) {
			if(typeof command === 'function') command = new command(this.client); // eslint-disable-line new-cap
			else if(command instanceof CommandBuilder) command = command.command;

			// Verify that it's an actual command
			if(!(command instanceof Command)) {
				this.client.emit('warn', 'Attempting to register an invalid command object: ${command}; skipping.');
				continue;
			}

			// Make sure there aren't any conflicts
			if(this.commands.some(cmd => cmd.name === command.name || cmd.aliases.includes(command.name))) {
				throw new Error(`A command with the name/alias "${command.name}" is already registered.`);
			}
			for(const alias of command.aliases) {
				if(this.commands.some(cmd => cmd.name === alias || cmd.aliases.some(ali => ali === alias))) {
					throw new Error(`A command with the name/alias "${alias}" is already registered.`);
				}
			}
			const group = this.groups.find(grp => grp.id === command.groupID);
			if(!group) throw new Error(`Group "${command.groupID}" is not registered.`);
			if(group.commands.some(cmd => cmd.memberName === command.memberName)) {
				throw new Error(`A command with the member name "${command.memberName}" is already registered in ${group.id}`);
			}

			// Add the command
			command.group = group;
			group.commands.set(command.name, command);
			this.commands.set(command.name, command);
			/**
			 * Emitted when a command is registered
			 * @event CommandoClient#commandRegister
			 * @param {CommandGroup} command - Command that was registered
			 * @param {CommandRegistry} registry - Registry that the command was registered to
			 */
			this.client.emit('commandRegister', command, this);
			this.client.emit('debug', `Registered command ${group.id}:${command.memberName}.`);
		}

		return this;
	}

	/**
	 * Registers all commands in a given directory. The files must export a Command class constructor or instance,
	 * or a CommandBuilder instance.
	 * @param {string|RequireAllOptions} options - The path to the directory, or a require-all options object
	 * @return {CommandRegistry}
	 */
	registerCommandsIn(options) {
		const obj = require('require-all')(options);
		const commands = [];
		for(const group of Object.values(obj)) {
			for(const command of Object.values(group)) commands.push(command);
		}
		if(typeof options === 'string' && !this.commandsPath) this.commandsPath = options;
		return this.registerCommands(commands);
	}

	/**
	 * Registers both the default groups and commands
	 * @return {CommandRegistry}
	 */
	registerDefaults() {
		this.registerDefaultGroups();
		this.registerDefaultCommands();
		return this;
	}

	/**
	 * Registers the default groups
	 * @return {CommandRegistry}
	 */
	registerDefaultGroups() {
		return this.registerGroups([
			['commands', 'Commands', true],
			['util', 'Utility']
		]);
	}

	/**
	 * Registers the default commands to the registry
	 * @param {Object} [options] - Object specifying what commands to register
	 * @param {boolean} [options.help=true] - Whether or not to register the built-in help command
	 * @param {boolean} [options.prefix=true] - Whether or not to register the built-in prefix command
	 * @param {boolean} [options.eval_=true] - Whether or not to register the built-in eval command
	 * @param {boolean} [options.ping=true] - Whether or not to register the built-in ping command
	 * @param {boolean} [options.commandState=true] - Whether or not to register the built-in command state commands
	 * (enable, disable, reload, list groups)
	 * @return {CommandRegistry}
	 */
	registerDefaultCommands({ help = true, prefix = true, ping = true, eval_ = true, commandState = true } = {}) {
		if(help) this.registerCommand(require('./commands/util/help'));
		if(prefix) this.registerCommand(require('./commands/util/prefix'));
		if(ping) this.registerCommand(require('./commands/util/ping'));
		if(eval_) this.registerCommand(require('./commands/util/eval'));
		if(commandState) {
			this.registerCommands([
				require('./commands/commands/groups'),
				require('./commands/commands/enable'),
				require('./commands/commands/disable'),
				require('./commands/commands/reload'),
				require('./commands/commands/load'),
				require('./commands/commands/unload')
			]);
		}
		return this;
	}

	/**
	 * Reregisters a command (does not support changing name, group, or memberName)
	 * @param {Command|CommandBuilder|function} command - New command
	 * @param {Command} oldCommand - Old command
	 */
	reregisterCommand(command, oldCommand) {
		if(typeof command === 'function') command = new command(this.client); // eslint-disable-line new-cap
		else if(command instanceof CommandBuilder) command = command.command;
		if(command.name !== oldCommand.name) throw new Error('Command name cannot change.');
		if(command.groupID !== oldCommand.groupID) throw new Error('Command group cannot change.');
		if(command.memberName !== oldCommand.memberName) throw new Error('Command memberName cannot change.');
		command.group = this.resolveGroup(command.groupID);
		command.group.commands.set(command.name, command);
		this.commands.set(command.name, command);
		/**
		 * Emitted when a command is reregistered
		 * @event CommandoClient#commandReregister
		 * @param {Command} newCommand - New command
		 * @param {Command} oldCommand - Old command
		 */
		this.client.emit('commandReregister', command, oldCommand);
		this.client.emit('debug', `Reregistered command ${command.groupID}:${command.memberName}.`);
	}

	/**
	 * Unregisters a command
	 * @param {Command} command - Command to unregister
	 */
	unregisterCommand(command) {
		this.commands.delete(command.name);
		command.group.commands.delete(command.name);
		/**
		 * Emitted when a command is unregistered
		 * @event CommandoClient#commandUnregister
		 * @param {Command} command - Command that was unregistered
		 */
		this.client.emit('commandUnregister', command);
		this.client.emit('debug', `Unregistered command ${command.groupID}:${command.memberName}.`);
	}

	/**
	 * Registers a single object to be usable by the eval command
	 * @param {string} key - The key for the object
	 * @param {Object} obj - The object
	 * @return {CommandRegistry}
	 * @see {@link CommandRegistry#registerEvalObjects}
	 */
	registerEvalObject(key, obj) {
		const registerObj = {};
		registerObj[key] = obj;
		return this.registerEvalObjects(registerObj);
	}

	/**
	 * Registers multiple objects to be usable by the eval command
	 * @param {Object} obj - An object of keys: values
	 * @return {CommandRegistry}
	 */
	registerEvalObjects(obj) {
		Object.assign(this.evalObjects, obj);
		return this;
	}

	/**
	 * Create a command builder
	 * @param {CommandInfo} [info] - The command information
	 * @param {CommandBuilderFunctions} [funcs] - The command functions to set
	 * @return {CommandBuilder} The builder
	 */
	buildCommand(info = null, funcs = null) {
		return new CommandBuilder(this, info, funcs);
	}

	/**
	 * Finds all groups that match the search string
	 * @param {string} [searchString] - The string to search for
	 * @param {boolean} [exact=false] - Whether the search should be exact
	 * @return {CommandGroup[]} All groups that are found
	 */
	findGroups(searchString = null, exact = false) {
		if(!searchString) return this.groups;

		// Find all matches
		const lcSearch = searchString.toLowerCase();
		const matchedGroups = this.groups.filterArray(
			exact ? groupFilterExact(lcSearch) : groupFilterInexact(lcSearch)
		);
		if(exact) return matchedGroups;

		// See if there's an exact match
		for(const group of matchedGroups) {
			if(group.name.toLowerCase() === lcSearch || group.id === lcSearch) return [group];
		}
		return matchedGroups;
	}

	/**
	 * A CommandGroupResolvable can be:
	 * * A CommandGroup
	 * * A group ID
	 * @typedef {CommandGroup|string} CommandGroupResolvable
	 */

	/**
	 * Resolves a CommandGroupResolvable to a CommandGroup object
	 * @param {CommandGroupResolvable} group - The group to resolve
	 * @return {CommandGroup} The resolved CommandGroup
	 */
	resolveGroup(group) {
		if(group instanceof CommandGroup) return group;
		if(typeof group === 'string') {
			const groups = this.findGroups(group, true);
			if(groups.length === 1) return groups[0];
		}
		throw new Error('Unable to resolve group.');
	}

	/**
	 * Finds all commands that match the search string
	 * @param {string} [searchString] - The string to search for
	 * @param {boolean} [exact=false] - Whether the search should be exact
	 * @param {Message} [message] - The message to check usability against
	 * @return {Command[]} All commands that are found
	 */
	findCommands(searchString = null, exact = false, message = null) {
		if(!searchString) return message ? this.commands.filterArray(cmd => cmd.isUsable(message)) : this.commands;

		// Find all matches
		const lcSearch = searchString.toLowerCase();
		const matchedCommands = this.commands.filterArray(
			exact ? commandFilterExact(lcSearch) : commandFilterInexact(lcSearch)
		);
		if(exact) return matchedCommands;

		// See if there's an exact match
		for(const command of matchedCommands) {
			if(command.name === lcSearch || (command.aliases && command.aliases.some(ali => ali === lcSearch))) {
				return [command];
			}
		}

		return matchedCommands;
	}

	/**
	 * A CommandResolvable can be:
	 * * A Command
	 * * A command name
	 * * A CommandMessage
	 * @typedef {Command|string} CommandResolvable
	 */

	/**
	 * Resolves a CommandResolvable to a Command object
	 * @param {CommandResolvable} command - The command to resolve
	 * @return {Command} The resolved Command
	 */
	resolveCommand(command) {
		if(command instanceof Command) return command;
		if(command instanceof CommandMessage) return command.command;
		if(typeof command === 'string') {
			const commands = this.findCommands(command, true);
			if(commands.length === 1) return commands[0];
		}
		throw new Error('Unable to resolve command.');
	}
}

function groupFilterExact(search) {
	return grp => grp.id === search || grp.name.toLowerCase() === search;
}

function groupFilterInexact(search) {
	return grp => grp.id.includes(search) || grp.name.toLowerCase().includes(search);
}

function commandFilterExact(search) {
	return cmd => cmd.name === search ||
		(cmd.aliases && cmd.aliases.some(ali => ali === search)) ||
		`${cmd.groupID}:${cmd.memberName}` === search;
}

function commandFilterInexact(search) {
	return cmd => cmd.name.includes(search) ||
		`${cmd.groupID}:${cmd.memberName}` === search ||
		(cmd.aliases && cmd.aliases.some(ali => ali.includes(search)));
}

module.exports = CommandRegistry;
<|endoftext|>