<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2012, Ajax.org B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

// [WIP]

define(function(require, exports, module) {
"use strict";
var TokenIterator = require("../token_iterator").TokenIterator;

function is(token, type) {
    return token.type.lastIndexOf(type + ".xml") > -1;
}

// do not indent after singleton tags or <html>
exports.singletonTags = ["area", "base", "br", "col", "command", "embed", "hr", "html", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"];

// insert a line break after block level tags
exports.blockTags = ["article", "aside", "blockquote", "body", "div", "dl", "fieldset", "footer", "form", "head", "header", "html", "nav", "ol", "p", "script", "section", "style", "table", "tbody", "tfoot", "thead", "ul"];

exports.beautify = function(session) {
    var iterator = new TokenIterator(session, 0, 0);
    var token = iterator.getCurrentToken();
    var tabString = session.getTabString();
    var singletonTags = exports.singletonTags;
    var blockTags = exports.blockTags;
    var nextToken;
    var breakBefore = false;
    var spaceBefore = false;
    var spaceAfter = false;
    var code = "";
    var value = "";
    var tagName = "";
    var depth = 0;
    var lastDepth = 0;
    var lastIndent = 0;
    var indent = 0;
    var unindent = false;
    var roundDepth = 0;
    var onCaseLine = false;
    var row;
    var curRow = 0;
    var rowsToAdd = 0;
    var rowTokens = [];
    var abort = false;
    var i;
    var indentNextLine = false;
    var inTag = false;
    var inCSS = false;
    var inBlock = false;
    var inCase = false;

    var trimNext = function() {
        if (nextToken && nextToken.value && nextToken.type !== 'string.regexp')
            nextToken.value = nextToken.value.trim();
    };

    var trimLine = function() {
        code = code.replace(/ +$/, "");
    };

    while (token !== null) {
        curRow = iterator.getCurrentTokenRow();
        rowTokens = iterator.$rowTokens;
        nextToken = iterator.stepForward();

        if (typeof token !== "undefined") {
            value = token.value;
            unindent = false;

            // mode
            inCSS = (tagName === "style" || session.$modeId === "ace/mode/css");

            // in tag
            if (is(token, "tag-open"))
                inTag = true;

            if (is(token, "tag-close"))
                inTag = false;

            // are we in a block tag
            if (is(token, "tag-open") && nextToken)
                inBlock = (blockTags.indexOf(nextToken.value) !== -1);

            // comments
            if (is(token, "comment.start"))
                inBlock = true;
            else if (is(token, "comment.end"))
                inBlock = false;

            // html indentation
            if (is(token, "tag-open") && value === "</") {
                // line break before closing tag unless we just had one
                if (inBlock && !breakBefore && rowsToAdd < 1)
                    rowsToAdd++;

                if (inCSS)
                    rowsToAdd = 1;

                unindent = true;
                inBlock = false;
            }

            // line break before }
            if (!inTag && !rowsToAdd && token.type === "paren.rparen" && token.value.substr(0, 1) === "}") {
                rowsToAdd++;
            }

            // switch case
            onCaseLine = false;
            if (token.type === "keyword" && value.match(/^(case|default)$/)) {
                onCaseLine = true;
                inCase = true;
            } else if (token.type === "keyword" && value === "break")
                inCase = false;

            // add rows
            if (curRow !== row) {
                rowsToAdd = curRow;

                if (row)
                    rowsToAdd -= row;
            }

            if (rowsToAdd) {
                code = code.trimRight();
                for (; rowsToAdd > 0; rowsToAdd--)
                    code += "\n";

                breakBefore = true;

                // trim value if not in a comment or string
                if (!is(token, "comment") && !token.type.match(/^(comment|string)$/))
                   value = value.trimLeft();
            }

            if (value) {
                // whitespace
                if (token.type === "keyword" && value.match(/^(if|else|elseif|for|while|switch)$/)) {
                    spaceAfter = true;
                    trimNext();

                    // space before else, elseif
                    if (token.type === "keyword" && value.match(/^(else|elseif)$/)) {
                        if (code.match(/\}[\s]*$/)) {
                            code = code.trimRight();
                            breakBefore = false;
                            spaceBefore = true;
                        }
                    }
                // trim value after opening paren
                } else if (token.type === "paren.lparen") {
                    trimNext();

                    // whitespace after {
                    if (value.substr(-1) === "{") {
                        spaceAfter = true;
                        indentNextLine = false;

                        if(!inTag)
                            rowsToAdd = 1;
                    }

                    // ensure curly brace is preceeded by whitespace
                    if (value.substr(0, 1) === "{") {
                        spaceBefore = true;

                        // collapse square and curly brackets together
                        if (code.substr(-1) !== '[' && code.trimRight().substr(-1) === '[') {
                            spaceBefore = false;
                            code = code.trim();
                            breakBefore = false;
                        } else if (code.trimRight().substr(-1) === ')') {
                            code = code.trimRight();
                            breakBefore = false;
                        } else {
                            trimLine();
                        }
                    }
                // remove space before closing paren
                } else if (token.type === "paren.rparen") {
                    // ensure curly brace is preceeded by whitespace
                    if (value.substr(0, 1) === "}") {
                        if (code.trimRight().substr(-1) === '{') {
                            breakBefore = false;
                            code = code.trimRight();
                        } else {
                            spaceBefore = true;

                            if (inCSS)
                                rowsToAdd+=2;
                        }
                    }

                    // collapse square and curly brackets together
                    if (value.substr(0, 1) === "]") {
                        if (code.substr(-1) !== '}' && code.trimRight().substr(-1) === '}') {
                            breakBefore = false;
                            spaceBefore = false;
                            indent++;
                            code = code.trimRight();
                        }
                    }

                    // collapse round brackets together
                    if (value.substr(0, 1) === ")") {
                        if (code.substr(-1) !== '(' && code.trimRight().substr(-1) === '(') {
                            breakBefore = false;
                            spaceBefore = false;
                            indent++;
                            code = code.trimRight();
                        }
                    }

                    trimLine();
                // add spaces around conditional operators
                } else if ((token.type === "keyword.operator" || token.type === "keyword") && value.match(/^(=|==|===|!=|!==|&&|\|\||and|or|xor|\+=|.=|>|>=|<|<=|=>)$/)) {
                    code = code.trimRight();
                    spaceBefore = true;
                    spaceAfter = true;
                    trimNext();
                // remove space before semicolon
                } else if (token.type === "punctuation.operator" && value === ';') {
                    code = code.trimRight();
                    spaceAfter = true;
                    trimNext();

                    if (inCSS)
                        rowsToAdd++;
                // space after colon or comma
                } else if (token.type === "punctuation.operator" && value.match(/^(:|,)$/)) {
                    code = code.trimRight();
                    spaceAfter = true;
                    trimNext();
                // ensure space before php closing tag
                } else if (token.type === "support.php_tag" && value === "?>" && !breakBefore) {
                    code = code.trimRight();
                    spaceBefore = true;
                // remove excess space before HTML attribute
                } else if (is(token, "attribute-name") && code.substr(-1).match(/^\s$/)) {
                    spaceBefore = true;
                // remove space around attribute equals
                } else if (is(token, "attribute-equals")) {
                    trimLine();
                    trimNext();
                // remove space before HTML closing tag
                } else if (is(token, "tag-close")) {
                    trimLine();
                    if(value === "/>")
                        spaceBefore = true;
                }

                // unindent after paren
                if (token.type === "paren.rparen")
                    unindent = true;

                // add indent to code unless multiline string or comment
                if (breakBefore && !(token.type.match(/^(comment)$/) && !value.substr(0, 1).match(/^[/#]$/)) && !(token.type.match(/^(string)$/) && !value.substr(0, 1).match(/^['"]$/))) {

                    indent = lastIndent;

                    if(depth > lastDepth)
                        indent++;
                    else if(depth < lastDepth && depth < lastIndent)
                        indent--;

                    lastDepth = depth;
                    lastIndent = indent;

                    if(unindent)
                        indent--;

                    if (inCase && !onCaseLine)
                        indent++;

                    if (indentNextLine && !roundDepth) {
                        indent++;
                        indentNextLine = false;
                    }

                    for (i = 0; i < indent; i++)
                        code += tabString;
                }

                // unindent after paren
                if (token.type === "paren.rparen")
                    depth -= value.length;

                // indent after paren
                if (token.type === "paren.lparen")
                    depth += value.length;

                // indent one line after if or else
                if (token.type === "paren.lparen")
                    roundDepth += (value.match(/\(/g) || []).length;

                if (token.type === "keyword" && value.match(/^(if|else|elseif|for|while)$/)) {
                    indentNextLine = true;
                    roundDepth = 0;
                } else if (!roundDepth && value.trim() && token.type !== "comment")
                    indentNextLine = false;

                if (token.type === "paren.rparen")
                    roundDepth -= (value.match(/\)/g) || []).length;

                // add to code
                if (spaceBefore && !breakBefore) {
                    trimLine();
                    if (code.substr(-1) !== "\n")
                        code += " ";
                }

                code += value;

                if (spaceAfter)
                    code += " ";

                breakBefore = false;
                spaceBefore = false;
                spaceAfter = false;

                // line break after block tag or doctype
                if ((is(token, "tag-close") && (inBlock || blockTags.indexOf(tagName) !== -1)) || (is(token, "doctype") && value === ">")) {
                    // undo linebreak if tag is immediately closed
                    if (inBlock && nextToken && nextToken.value === "</")
                        rowsToAdd = -1;
                    else
                        rowsToAdd = 1;
                }

                // html indentation
                if (is(token, "tag-open") && value === "</")
                    depth--;

                // indent after opening tag
                if (is(token, "tag-open") && value === "<" && singletonTags.indexOf(nextToken.value) === -1)
                    depth++;

                // remove indent if unknown singleton
                if (is(token, "tag-name"))
                    tagName = value;

                if (is(token, "tag-close") && value === "/>" && singletonTags.indexOf(tagName) === -1)
                    depth--;

                row = curRow;
            }
        }

        token = nextToken;
    }

    code = code.trim();
    session.doc.setValue(code);
};

exports.commands = [{
    name: "beautify",
    exec: function(editor) {
        exports.beautify(editor.session);
    },
    bindKey: "Ctrl-Shift-B"
}];

});
<commit_msg>fix indent bug<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2012, Ajax.org B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

// [WIP]

define(function(require, exports, module) {
"use strict";
var TokenIterator = require("../token_iterator").TokenIterator;

function is(token, type) {
    return token.type.lastIndexOf(type + ".xml") > -1;
}

// do not indent after singleton tags or <html>
exports.singletonTags = ["area", "base", "br", "col", "command", "embed", "hr", "html", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"];

// insert a line break after block level tags
exports.blockTags = ["article", "aside", "blockquote", "body", "div", "dl", "fieldset", "footer", "form", "head", "header", "html", "nav", "ol", "p", "script", "section", "style", "table", "tbody", "tfoot", "thead", "ul"];

exports.beautify = function(session) {
    var iterator = new TokenIterator(session, 0, 0);
    var token = iterator.getCurrentToken();
    var tabString = session.getTabString();
    var singletonTags = exports.singletonTags;
    var blockTags = exports.blockTags;
    var nextToken;
    var breakBefore = false;
    var spaceBefore = false;
    var spaceAfter = false;
    var code = "";
    var value = "";
    var tagName = "";
    var depth = 0;
    var lastDepth = 0;
    var lastIndent = 0;
    var indent = 0;
    var unindent = false;
    var roundDepth = 0;
    var onCaseLine = false;
    var row;
    var curRow = 0;
    var rowsToAdd = 0;
    var rowTokens = [];
    var abort = false;
    var i;
    var indentNextLine = false;
    var inTag = false;
    var inCSS = false;
    var inBlock = false;
    var inCase = false;

    var trimNext = function() {
        if (nextToken && nextToken.value && nextToken.type !== 'string.regexp')
            nextToken.value = nextToken.value.trim();
    };

    var trimLine = function() {
        code = code.replace(/ +$/, "");
    };

    while (token !== null) {
        curRow = iterator.getCurrentTokenRow();
        rowTokens = iterator.$rowTokens;
        nextToken = iterator.stepForward();

        if (typeof token !== "undefined") {
            value = token.value;
            unindent = false;

            // mode
            inCSS = (tagName === "style" || session.$modeId === "ace/mode/css");

            // in tag
            if (is(token, "tag-open"))
                inTag = true;

            if (is(token, "tag-close"))
                inTag = false;

            // are we in a block tag
            if (is(token, "tag-open") && nextToken)
                inBlock = (blockTags.indexOf(nextToken.value) !== -1);

            // comments
            if (is(token, "comment.start"))
                inBlock = true;
            else if (is(token, "comment.end"))
                inBlock = false;

            // html indentation
            if (is(token, "tag-open") && value === "</") {
                // line break before closing tag unless we just had one
                if (inBlock && !breakBefore && rowsToAdd < 1)
                    rowsToAdd++;

                if (inCSS)
                    rowsToAdd = 1;

                unindent = true;
                inBlock = false;
            }

            // line break before }
            if (!inTag && !rowsToAdd && token.type === "paren.rparen" && token.value.substr(0, 1) === "}") {
                rowsToAdd++;
            }

            // switch case
            onCaseLine = false;
            if (token.type === "keyword" && value.match(/^(case|default)$/)) {
                onCaseLine = true;
                inCase = true;
            } else if (token.type === "keyword" && value === "break")
                inCase = false;

            // add rows
            if (curRow !== row) {
                rowsToAdd = curRow;

                if (row)
                    rowsToAdd -= row;
            }

            if (rowsToAdd) {
                code = code.trimRight();
                for (; rowsToAdd > 0; rowsToAdd--)
                    code += "\n";

                breakBefore = true;

                // trim value if not in a comment or string
                if (!is(token, "comment") && !token.type.match(/^(comment|string)$/))
                   value = value.trimLeft();
            }

            if (value) {
                // whitespace
                if (token.type === "keyword" && value.match(/^(if|else|elseif|for|while|switch)$/)) {
                    spaceAfter = true;
                    trimNext();

                    // space before else, elseif
                    if (token.type === "keyword" && value.match(/^(else|elseif)$/)) {
                        if (code.match(/\}[\s]*$/)) {
                            code = code.trimRight();
                            breakBefore = false;
                            spaceBefore = true;
                        }
                    }
                // trim value after opening paren
                } else if (token.type === "paren.lparen") {
                    trimNext();

                    // whitespace after {
                    if (value.substr(-1) === "{") {
                        spaceAfter = true;
                        indentNextLine = false;

                        if(!inTag)
                            rowsToAdd = 1;
                    }

                    // ensure curly brace is preceeded by whitespace
                    if (value.substr(0, 1) === "{") {
                        spaceBefore = true;

                        // collapse square and curly brackets together
                        if (code.substr(-1) !== '[' && code.trimRight().substr(-1) === '[') {
                            spaceBefore = false;
                            code = code.trim();
                            breakBefore = false;
                        } else if (code.trimRight().substr(-1) === ')') {
                            code = code.trimRight();
                            breakBefore = false;
                        } else {
                            trimLine();
                        }
                    }
                // remove space before closing paren
                } else if (token.type === "paren.rparen") {
                    // ensure curly brace is preceeded by whitespace
                    if (value.substr(0, 1) === "}") {
                        if (code.trimRight().substr(-1) === '{') {
                            breakBefore = false;
                            code = code.trimRight();
                        } else {
                            spaceBefore = true;

                            if (inCSS)
                                rowsToAdd+=2;
                        }
                    }

                    // collapse square and curly brackets together
                    if (value.substr(0, 1) === "]") {
                        if (code.substr(-1) !== '}' && code.trimRight().substr(-1) === '}') {
                            breakBefore = false;
                            spaceBefore = false;
                            indent++;
                            code = code.trimRight();
                        }
                    }

                    // collapse round brackets together
                    if (value.substr(0, 1) === ")") {
                        if (code.substr(-1) !== '(' && code.trimRight().substr(-1) === '(') {
                            breakBefore = false;
                            spaceBefore = false;
                            indent++;
                            code = code.trimRight();
                        }
                    }

                    trimLine();
                // add spaces around conditional operators
                } else if ((token.type === "keyword.operator" || token.type === "keyword") && value.match(/^(=|==|===|!=|!==|&&|\|\||and|or|xor|\+=|.=|>|>=|<|<=|=>)$/)) {
                    code = code.trimRight();
                    spaceBefore = true;
                    spaceAfter = true;
                    trimNext();
                // remove space before semicolon
                } else if (token.type === "punctuation.operator" && value === ';') {
                    code = code.trimRight();
                    spaceAfter = true;
                    trimNext();

                    if (inCSS)
                        rowsToAdd++;
                // space after colon or comma
                } else if (token.type === "punctuation.operator" && value.match(/^(:|,)$/)) {
                    code = code.trimRight();
                    spaceAfter = true;
                    trimNext();
                // ensure space before php closing tag
                } else if (token.type === "support.php_tag" && value === "?>" && !breakBefore) {
                    code = code.trimRight();
                    spaceBefore = true;
                // remove excess space before HTML attribute
                } else if (is(token, "attribute-name") && code.substr(-1).match(/^\s$/)) {
                    spaceBefore = true;
                // remove space around attribute equals
                } else if (is(token, "attribute-equals")) {
                    trimLine();
                    trimNext();
                // remove space before HTML closing tag
                } else if (is(token, "tag-close")) {
                    trimLine();
                    if(value === "/>")
                        spaceBefore = true;
                }

                // unindent after paren
                if (token.type === "paren.rparen")
                    unindent = true;

                // add indent to code unless multiline string or comment
                if (breakBefore && !(token.type.match(/^(comment)$/) && !value.substr(0, 1).match(/^[/#]$/)) && !(token.type.match(/^(string)$/) && !value.substr(0, 1).match(/^['"]$/))) {

                    indent = lastIndent;

                    if(depth > lastDepth)
                        indent++;
                    else if(depth < lastDepth)
                        indent--;

                    lastDepth = depth;
                    lastIndent = indent;

                    if(unindent)
                        indent--;

                    if (inCase && !onCaseLine)
                        indent++;

                    if (indentNextLine && !roundDepth) {
                        indent++;
                        indentNextLine = false;
                    }

                    for (i = 0; i < indent; i++)
                        code += tabString;
                }

                // unindent after paren
                if (token.type === "paren.rparen")
                    depth -= value.length;

                // indent after paren
                if (token.type === "paren.lparen")
                    depth += value.length;

                // indent one line after if or else
                if (token.type === "paren.lparen")
                    roundDepth += (value.match(/\(/g) || []).length;

                if (token.type === "keyword" && value.match(/^(if|else|elseif|for|while)$/)) {
                    indentNextLine = true;
                    roundDepth = 0;
                } else if (!roundDepth && value.trim() && token.type !== "comment")
                    indentNextLine = false;

                if (token.type === "paren.rparen")
                    roundDepth -= (value.match(/\)/g) || []).length;

                // add to code
                if (spaceBefore && !breakBefore) {
                    trimLine();
                    if (code.substr(-1) !== "\n")
                        code += " ";
                }

                code += value;

                if (spaceAfter)
                    code += " ";

                breakBefore = false;
                spaceBefore = false;
                spaceAfter = false;

                // line break after block tag or doctype
                if ((is(token, "tag-close") && (inBlock || blockTags.indexOf(tagName) !== -1)) || (is(token, "doctype") && value === ">")) {
                    // undo linebreak if tag is immediately closed
                    if (inBlock && nextToken && nextToken.value === "</")
                        rowsToAdd = -1;
                    else
                        rowsToAdd = 1;
                }

                // html indentation
                if (is(token, "tag-open") && value === "</")
                    depth--;

                // indent after opening tag
                if (is(token, "tag-open") && value === "<" && singletonTags.indexOf(nextToken.value) === -1)
                    depth++;

                // remove indent if unknown singleton
                if (is(token, "tag-name"))
                    tagName = value;

                if (is(token, "tag-close") && value === "/>" && singletonTags.indexOf(tagName) === -1)
                    depth--;

                row = curRow;
            }
        }

        token = nextToken;
    }

    code = code.trim();
    session.doc.setValue(code);
};

exports.commands = [{
    name: "beautify",
    exec: function(editor) {
        exports.beautify(editor.session);
    },
    bindKey: "Ctrl-Shift-B"
}];

});
<|endoftext|>


<commit_before>      var fs = require('fs'),
      __bind = require('./helpers').bindFunctionToScope,
extendsClass = require('./helpers').extendsClass,
  fsEachLine = require('./helpers').fsEachLine,
 fsJoinPaths = require('./helpers').fsJoinPaths;

/*
 * @class AssetDirectives
 */
var AssetDirectives = (function () {

  function AssetDirectives ( asset ) {

    /*
     * Initialize instance variables
     */

    this.asset = asset;

    /*
     * Bind scope of instance methods
     */

    this.parseDirectives = __bind( this.parseDirectives, this );

    this.directives = __bind( this.directives, this );

    this.assetFragments = __bind( this.assetFragments, this );

    /*
     * Return instance
     */

    return this;

  }

  AssetDirectives.prototype.parseDirectives = function () {

    var _directives = [];

    var DIRECTIVE_REGEX = /^[#\/]{1,2}=\s*(\S+)\s*(.*)$/;

    var _this = this;

    fsEachLine( this.asset.path, function ( line, stop ) {

      var m;

      // Ignore empty lines
      if ( line == null || line.match(/^\s*$/) ) return;

      // Directives must be the first thing in the file
      // Stop reading once the line isn't a directive
      if ( !( m = line.match( DIRECTIVE_REGEX ) ) ) return stop();

      var name = m[1];
      var args = m[2].split(' ');

      _directives.push( new AssetDirective( _this.asset, { name: name, args: args } ) );

    });

    this._directives = _directives;

    return _directives;

  }

  AssetDirectives.prototype.directives = function () {

    if ( this._directives == null ) {
      this.parseDirectives();
    }

    return this._directives;

  }

  AssetDirectives.prototype.assetFragments = function () { } // TODO

  return AssetDirectives;

})();

exports.AssetDirectives = AssetDirectives;

/*
 * @class AssetDirective
 */
var AssetDirective = (function () {

  function AssetDirective ( asset, directive_meta ) {

    var ref;

    switch ( directive_meta.name ) {

      case "require":
        ref = new AssetRequireDirective( asset, directive_meta );
        break;

      case "require_tree":
        ref = new AssetRequireTreeDirective( asset, directive_meta );
        break;

      case "require_self":
        ref = new AssetRequireSelfDirective( asset, directive_meta );
        break;

    }

    return ref;

  }

  return AssetDirective;

})();

exports.AssetDirective = AssetDirective;

/*
 * @class AssetDirectiveBase
 */
var AssetDirectiveBase = (function () {

  function AssetDirectiveBase ( asset, directive_meta ) {

    /*
     * Initialize instance variables
     */

    this.asset = asset;

    this.directive_meta = directive_meta;

    /*
     * Bind scope of instance methods
     */

    /*
     * Return instance
     */

    return this;

  }

  return AssetDirectiveBase;

})();

exports.AssetDirectiveBase = AssetDirectiveBase;

/*
 * @class AssetRequireDirective
 * @extends AssetDirectiveBase
 */
var AssetRequireDirective = (function ( _super ) {

  extendsClass( AssetRequireDirective, _super );

  function AssetRequireDirective ( asset, directive_meta ) {

    var ref = AssetRequireDirective.__super__.constructor.apply( this, arguments );

    ref.path = fsJoinPaths( asset.dir_path, directive_meta.args[0] );

    return ref;

  }

  return AssetRequireDirective;

})( AssetDirectiveBase );

exports.AssetRequireDirective = AssetRequireDirective;

/*
 * @class AssetRequireTreeDirective
 * @extends AssetDirectiveBase
 */
var AssetRequireTreeDirective = (function ( _super ) {

  extendsClass( AssetRequireTreeDirective, _super );

  function AssetRequireTreeDirective ( asset, directive_meta ) {

    var ref = AssetRequireDirective.__super__.constructor.apply( this, arguments );

    ref.path = fsJoinPaths( asset.dir_path, directive_meta.args[0] );

    return ref;

  }

  return AssetRequireTreeDirective;

})( AssetDirectiveBase );

exports.AssetRequireTreeDirective = AssetRequireTreeDirective;

/*
 * @class AssetRequireSelfDirective
 * @extends AssetDirectiveBase
 */
var AssetRequireSelfDirective = (function ( _super ) {

  extendsClass( AssetRequireSelfDirective, _super );

  function AssetRequireSelfDirective ( asset, directive_meta ) {

    var ref = AssetRequireDirective.__super__.constructor.apply( this, arguments );

    ref.path = asset.path;

    return ref;

  }

  return AssetRequireSelfDirective;

})( AssetDirectiveBase );

exports.AssetRequireSelfDirective = AssetRequireSelfDirective;

<commit_msg>Update asset directives to reference instances instead of paths<commit_after>      var fs = require('fs'),
      __bind = require('./helpers').bindFunctionToScope,
extendsClass = require('./helpers').extendsClass,
  fsEachLine = require('./helpers').fsEachLine,
 fsJoinPaths = require('./helpers').fsJoinPaths;

/*
 * @class AssetDirectives
 */
var AssetDirectives = (function () {

  function AssetDirectives ( asset ) {

    /*
     * Initialize instance variables
     */

    this.asset = asset;

    /*
     * Bind scope of instance methods
     */

    this.parseDirectives = __bind( this.parseDirectives, this );

    this.directives = __bind( this.directives, this );

    this.assetFragments = __bind( this.assetFragments, this );

    /*
     * Return instance
     */

    return this;

  }

  AssetDirectives.prototype.parseDirectives = function () {

    var _directives = [];

    var DIRECTIVE_REGEX = /^[#\/]{1,2}=\s*(\S+)\s*(.*)$/;

    var _this = this;

    fsEachLine( this.asset.path, function ( line, stop ) {

      var m;

      // Ignore empty lines
      if ( line == null || line.match(/^\s*$/) ) return;

      // Directives must be the first thing in the file
      // Stop reading once the line isn't a directive
      if ( !( m = line.match( DIRECTIVE_REGEX ) ) ) return stop();

      var name = m[1];
      var args = m[2].split(' ');

      _directives.push( new AssetDirective( _this.asset, { name: name, args: args } ) );

    });

    this._directives = _directives;

    return _directives;

  }

  AssetDirectives.prototype.directives = function () {

    if ( this._directives == null ) {
      this.parseDirectives();
    }

    return this._directives;

  }

  AssetDirectives.prototype.assetFragments = function () { } // TODO

  return AssetDirectives;

})();

exports.AssetDirectives = AssetDirectives;

/*
 * @class AssetDirective
 */
var AssetDirective = (function () {

  function AssetDirective ( asset, directive_meta ) {

    var ref;

    switch ( directive_meta.name ) {

      case "require":
        ref = new AssetRequireDirective( asset, directive_meta );
        break;

      case "require_tree":
        ref = new AssetRequireTreeDirective( asset, directive_meta );
        break;

      case "require_self":
        ref = new AssetRequireSelfDirective( asset, directive_meta );
        break;

    }

    return ref;

  }

  return AssetDirective;

})();

exports.AssetDirective = AssetDirective;

/*
 * @class AssetDirectiveBase
 */
var AssetDirectiveBase = (function () {

  function AssetDirectiveBase ( reference_asset, directive_meta ) {

    /*
     * Initialize instance variables
     */

    this.reference_asset = reference_asset;

    this.directive_meta = directive_meta;

    this.mapping = reference_asset.mapping;

    /*
     * Bind scope of instance methods
     */

    /*
     * Return instance
     */

    return this;

  }

  return AssetDirectiveBase;

})();

exports.AssetDirectiveBase = AssetDirectiveBase;

/*
 * @class AssetRequireDirective
 * @extends AssetDirectiveBase
 */
var AssetRequireDirective = (function ( _super ) {

  extendsClass( AssetRequireDirective, _super );

  function AssetRequireDirective ( reference_asset, directive_meta ) {

    var ref = AssetRequireDirective.__super__.constructor.apply( this, arguments );

    var name_or_path = directive_meta.args[0];

    var relative_path = fsJoinPaths( reference_asset.dir_path, directive_meta.args[0] );

    ref.item = ref.mapping.lookupAsset( relative_path ) || ref.mapping.lookupAsset( name_or_path );

    if ( ref.item == null ) {
      throw new Error( "Invalid directive: " + directive_meta.name + " " + directive_meta.args.join(' ') );
    }

    return ref;

  }

  return AssetRequireDirective;

})( AssetDirectiveBase );

exports.AssetRequireDirective = AssetRequireDirective;

/*
 * @class AssetRequireTreeDirective
 * @extends AssetDirectiveBase
 */
var AssetRequireTreeDirective = (function ( _super ) {

  extendsClass( AssetRequireTreeDirective, _super );

  function AssetRequireTreeDirective ( reference_asset, directive_meta ) {

    var ref = AssetRequireDirective.__super__.constructor.apply( this, arguments );

    var name_or_path = directive_meta.args[ 0 ];

    var relative_path = fsJoinPaths( reference_asset.dir_path, name_or_path );

    ref.item = ref.mapping.lookupDirectory( relative_path ) || ref.mapping.lookupDirectory( name_or_path );

    if ( ref.item == null ) {
      throw new Error( "Invalid directive: " + directive_meta.name + " " + directive_meta.args.join(' ') );
    }

    return ref;

  }

  return AssetRequireTreeDirective;

})( AssetDirectiveBase );

exports.AssetRequireTreeDirective = AssetRequireTreeDirective;

/*
 * @class AssetRequireSelfDirective
 * @extends AssetDirectiveBase
 */
var AssetRequireSelfDirective = (function ( _super ) {

  extendsClass( AssetRequireSelfDirective, _super );

  function AssetRequireSelfDirective ( reference_asset, directive_meta ) {

    var ref = AssetRequireDirective.__super__.constructor.apply( this, arguments );

    ref.item = reference_asset;

    return ref;

  }

  return AssetRequireSelfDirective;

})( AssetDirectiveBase );

exports.AssetRequireSelfDirective = AssetRequireSelfDirective;

<|endoftext|>


<commit_before>'use babel'

export class Rule {
  constructor(key, command, points, time = new Date()) {
    this.key     = key
    this.command = command
    this.points  = points
    this.time    = time
  }

  resetTime() {
    this.time = new Date()
  }
}

export const rules = [
  new Rule('zc', 'editor:fold-current-row'          , 2),
  new Rule('zo', 'editor:unfold-current-row'        , -2),

  new Rule('j' , 'vim-mode:move-down'               , -1),
  new Rule('h' , 'vim-mode:move-left'               , -1),
  new Rule('l' , 'vim-mode:move-right'              , -1),
  new Rule('k' , 'vim-mode:move-up'                 , -1),

  new Rule('w' , 'vim-mode:move-to-next-word'       , 4),
  new Rule('W' , 'vim-mode:move-to-next-whole-word' , 4),
  new Rule('b' , 'vim-mode:move-to-previous-word'   , 4),
  new Rule('B' , 'vim-mode:move-to-previous-whole-word', 4),

  new Rule('t' , 'vim-mode:till'                    , 6),
  new Rule('T' , 'vim-mode:till-backwards'          , 6),

  new Rule('gg', 'vim-mode:move-to-start-of-file'   , 2),
  new Rule('G' , 'vim-mode:move-to-line'            , 2),

  new Rule('L' , 'editor:move-to-end-of-line'       , 3),
  new Rule('A' , 'vim-mode:insert-after-end-of-line', 2),

  new Rule('n' , 'vim-mode:repeat-search'           , 3),
  new Rule('N' , 'vim-mode:repeat-search-backwards' , 3),

  new Rule('^d', 'vim-mode:scroll-half-screen-down' , 5),
  new Rule('^u', 'vim-mode:scroll-half-screen-up'   , 5),

  new Rule('u' , 'vim-mode:undo'                    , -5),
  new Rule('y' , 'vim-mode:yank'                    , 1),

  new Rule('^e', 'vim-mode:reset-normal-mode'       , 4),
  new Rule('.' , 'vim-mode:repeat'                  , 3)
]
<commit_msg>reduce the magnitude of points<commit_after>'use babel'

export class Rule {
  constructor(key, command, points, time = new Date()) {
    this.key     = key
    this.command = command
    this.points  = points
    this.time    = time
  }

  resetTime() {
    this.time = new Date()
  }
}

export const rules = [
  new Rule('zc', 'editor:fold-current-row'          , 2),
  new Rule('zo', 'editor:unfold-current-row'        , -2),

  new Rule('j' , 'vim-mode:move-down'               , -1),
  new Rule('h' , 'vim-mode:move-left'               , -1),
  new Rule('l' , 'vim-mode:move-right'              , -1),
  new Rule('k' , 'vim-mode:move-up'                 , -1),

  new Rule('w' , 'vim-mode:move-to-next-word'       , 2),
  new Rule('W' , 'vim-mode:move-to-next-whole-word' , 2),
  new Rule('b' , 'vim-mode:move-to-previous-word'   , 2),
  new Rule('B' , 'vim-mode:move-to-previous-whole-word', 2),

  new Rule('t' , 'vim-mode:till'                    , 3),
  new Rule('T' , 'vim-mode:till-backwards'          , 3),

  new Rule('gg', 'vim-mode:move-to-start-of-file'   , 1),
  new Rule('G' , 'vim-mode:move-to-line'            , 1),

  new Rule('L' , 'editor:move-to-end-of-line'       , 2),
  new Rule('A' , 'vim-mode:insert-after-end-of-line', 2),

  new Rule('n' , 'vim-mode:repeat-search'           , 3),
  new Rule('N' , 'vim-mode:repeat-search-backwards' , 3),

  new Rule('^d', 'vim-mode:scroll-half-screen-down' , 3),
  new Rule('^u', 'vim-mode:scroll-half-screen-up'   , 3),

  new Rule('u' , 'vim-mode:undo'                    , -5),
  new Rule('y' , 'vim-mode:yank'                    , 1),

  new Rule('^e', 'vim-mode:reset-normal-mode'       , 1),
  new Rule('.' , 'vim-mode:repeat'                  , 2),

  // Ex-mode doesn't call commands, does it raw
  //new Rule(':w', 'core:save'                        , 1),
  //new Rule(':w', 'pane:save-items'                  , 1),
  //new Rule(':w', 'window:save-all'                  , 1),

]
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import Select from 'react-select';
import 'react-select/dist/react-select.css';

import DashboardHelpers from '../DashboardHelpers';
import StudentsTable from '../StudentsTable';
import DashboardBarChart from '../DashboardBarChart';
import DashRangeButtons from '../DashRangeButtons';
import {latestNoteDateText} from '../../../helpers/latestNoteDateText';
import {sortByGrade} from '../../../helpers/SortHelpers';
import ExperimentalBanner from '../../../components/ExperimentalBanner';

class SchoolDisciplineDashboard extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      startDate: DashboardHelpers.schoolYearStart(),
      selectedChart: 'location',
      selectedCategory: null
    };
    this.setDate = this.setDate.bind(this);
    this.setStudentList = this.setStudentList.bind(this);
    this.resetStudentList = this.resetStudentList.bind(this);
    this.selectChart = this.selectChart.bind(this);
  }

  setDate(range) {
    this.setState({
      startDate: moment.unix(range[0]).format("YYYY-MM-DD")
    });
  }
  setStudentList(highchartsEvent) {
    this.setState({selectedCategory: highchartsEvent.point.category});
  }
  resetStudentList() {
    this.setState({selectedCategory: null});
  }
  selectChart(selection) {
    this.setState({selectedChart: selection.value, selectedCategory: null});
  }

  filterIncidentDates(incidents) {
    return incidents.filter((incident) => {
      return moment.utc(incident.occurred_at).isSameOrAfter(moment.utc(this.state.startDate));
    });
  }

  studentDisciplineIncidentCounts(incidentCategory) {
    let studentDisciplineIncidentCounts = {};
    const selectedChartData = this.getChartData(this.state.selectedChart);
    const incidents = incidentCategory ? selectedChartData.disciplineIncidents[incidentCategory] : this.props.schoolDisciplineEvents;
    this.filterIncidentDates(incidents).forEach((incident) => {
      studentDisciplineIncidentCounts[incident.student_id] = studentDisciplineIncidentCounts[incident.student_id] || 0;
      studentDisciplineIncidentCounts[incident.student_id]++;
    });
    return studentDisciplineIncidentCounts;
  }

  getChartData(selectedChart) {
    return {
      type: selectedChart,
      disciplineIncidents: _.groupBy(this.props.schoolDisciplineEvents, selectedChart),
      title: "Incidents by " + selectedChart};
  }

  sortChartKeys(chartKeys) {
    switch(this.state.selectedChart) {
    case 'time': return this.sortedTimes(chartKeys);
    case 'day': return this.sortedDays(chartKeys);
    case 'grade': return this.sortedGrades(chartKeys);
    case 'classroom': return this.sortedClassrooms(chartKeys);
    default: return chartKeys;
    }
  }

  sortedDays(chartKeys) {
    return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
  }

  sortedTimes(chartKeys) {
    //chartKeys will either contain a time like "4:00 pm", "10:00 am", or "Not Logged"
    return chartKeys.sort((a, b) => {
      if (a == "Not Logged") return -1;
      if (b == "Not Logged") return 1;
      return new Date('1970/01/01 ' + a) - new Date('1970/01/01 ' + b);
    });
  }

  sortedGrades(chartKeys) {
    return chartKeys.sort((a,b) => sortByGrade(a,b));

  }

  sortedClassrooms(chartKeys) {
    const chart = this.getChartData(this.state.selectedChart);
    return chartKeys.sort((a,b) => {
      return chart.disciplineIncidents[b].length - chart.disciplineIncidents[a].length;
    });
  }

  render() {
    const selectedChart = this.getChartData(this.state.selectedChart);
    const chartOptions = [
      {value: 'location', label: 'Location'},
      {value: 'time', label: 'Time'},
      {value: 'classroom', label: 'Classroom'},
      {value: 'grade', label: 'Grade'},
      {value: 'day', label: 'Day'},
      {value: 'offense', label: 'Offense'},
    ];

    return(

      <ExperimentalBanner />

            {this.renderRangeSelector()}
            {this.renderStudentDisciplineTable()}

                Incidents by:

              <Select
                value={this.state.selectedChart}
                onChange={this.selectChart}
                options={chartOptions}
                style={styles.dropdown}
              />

           {this.renderDisciplineChart(selectedChart)}

    );
  }

  renderDisciplineChart(selectedChart) {
    const categories = this.sortChartKeys(Object.keys(selectedChart.disciplineIncidents));
    const seriesData = categories.map((type) => {
      if (!selectedChart.disciplineIncidents[type]) return [];
      const incidents = this.filterIncidentDates(selectedChart.disciplineIncidents[type]);
      return [type, incidents.length];
    });

    return (
        <DashboardBarChart
          id = "Discipline"
          categories = {{categories: categories}}
          seriesData = {seriesData}
          titleText = {null}
          measureText = {'Number of Incidents'}
          tooltip = {{
            pointFormat: 'Total incidents: {point.y}'}}
          onColumnClick = {this.setStudentList}
          onBackgroundClick = {this.resetStudentList}/>
    );
  }

  renderStudentDisciplineTable() {
    const students = this.props.dashboardStudents;
    const studentDisciplineIncidentCounts = this.studentDisciplineIncidentCounts(this.state.selectedCategory);
    let rows =[];
    students.forEach((student) => {
      rows.push({
        id: student.id,
        first_name: student.first_name,
        last_name: student.last_name,
        last_sst_date_text: latestNoteDateText(300, student.sst_notes),
        events: studentDisciplineIncidentCounts[student.id] || 0,
        grade: student.grade
      });
    });

    return (
      <StudentsTable
        rows = {rows}
        selectedCategory = {this.state.selectedCategory}
        incidentType={"Incidents"}
        resetFn={this.resetStudentList}/>
    );
  }

  renderRangeSelector() {
    const ninetyDaysAgo = moment.utc().subtract(90, 'days').format("YYYY-MM-DD");
    const fortyFiveDaysAgo = moment.utc().subtract(45, 'days').format("YYYY-MM-DD");
    const schoolYearStart = DashboardHelpers.schoolYearStart();
    return (

        <DashRangeButtons
          schoolYearFilter={() => this.setState({startDate: schoolYearStart, selectedRange: 'School Year'})}
          ninetyDayFilter={() => this.setState({startDate: ninetyDaysAgo, selectedRange: '90 Days'})}
          fortyFiveDayFilter={() => this.setState({startDate: fortyFiveDaysAgo, selectedRange: '45 Days'})}/>

    );
  }
}

SchoolDisciplineDashboard.propTypes = {
  dashboardStudents: PropTypes.array.isRequired,
  schoolDisciplineEvents: PropTypes.arrayOf(PropTypes.shape({
    student_id: PropTypes.number.isRequired, //ID of student involved in incident
    location: PropTypes.string, //Place where incident occurred
    time: PropTypes.string, //Time of day for incident - NULL if no specific time recorded
    classroom: PropTypes.string, //Name of student's homeroom teacher
    student_grade: PropTypes.string, //Grade of student
    day: PropTypes.string, //Day of week on which incident occurred
    offense: PropTypes.string, //Specific type of incident
    student_race: PropTypes.string, //Race of student
    occurred_at: PropTypes.string, //Date for incident, used in filtering specific date ranges
    last_sst_date_text: PropTypes.string //Date of last SST meeting
  })).isRequired
};

export default SchoolDisciplineDashboard;

const styles = {
  graphTitle: {
    display: 'flex',
    justifyContent: 'center',
    marginTop: '20px'
  },
  titleText: {
    fontSize: '18px',
    marginRight: '10px',
    alignSelf: 'center'
  },
  dropdown: {
    width: '200px'
  }
};
<commit_msg>sorting correctly in all date ranges<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import Select from 'react-select';
import 'react-select/dist/react-select.css';

import DashboardHelpers from '../DashboardHelpers';
import StudentsTable from '../StudentsTable';
import DashboardBarChart from '../DashboardBarChart';
import DashRangeButtons from '../DashRangeButtons';
import {latestNoteDateText} from '../../../helpers/latestNoteDateText';
import {sortByGrade} from '../../../helpers/SortHelpers';
import ExperimentalBanner from '../../../components/ExperimentalBanner';

class SchoolDisciplineDashboard extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      startDate: DashboardHelpers.schoolYearStart(),
      selectedChart: 'location',
      selectedCategory: null
    };
    this.setStudentList = this.setStudentList.bind(this);
    this.resetStudentList = this.resetStudentList.bind(this);
    this.selectChart = this.selectChart.bind(this);
  }

  setStudentList(highchartsEvent) {
    this.setState({selectedCategory: highchartsEvent.point.category});
  }
  resetStudentList() {
    this.setState({selectedCategory: null});
  }
  selectChart(selection) {
    this.setState({selectedChart: selection.value, selectedCategory: null});
  }

  filterIncidentDates(incidentsArray) {
    return incidentsArray.filter((incident) => {
      return moment.utc(incident.occurred_at).isSameOrAfter(moment.utc(this.state.startDate));
    });
  }

  studentDisciplineIncidentCounts(incidentCategory) {
    let studentDisciplineIncidentCounts = {};
    const selectedChartData = this.getChartData(this.state.selectedChart);
    const incidents = incidentCategory ? selectedChartData.disciplineIncidents[incidentCategory] : this.props.schoolDisciplineEvents;
    this.filterIncidentDates(incidents).forEach((incident) => {
      studentDisciplineIncidentCounts[incident.student_id] = studentDisciplineIncidentCounts[incident.student_id] || 0;
      studentDisciplineIncidentCounts[incident.student_id]++;
    });
    return studentDisciplineIncidentCounts;
  }

  getChartData(selectedChart) {
    const filteredEvents = this.filterIncidentDates(this.props.schoolDisciplineEvents);
    return {
      type: selectedChart,
      disciplineIncidents: _.groupBy(filteredEvents, selectedChart),
      title: "Incidents by " + selectedChart};
  }

  sortChartKeys(chartKeys) {
    switch(this.state.selectedChart) {
    case 'time': return this.sortedTimes(chartKeys);
    case 'day': return this.sortedDays(chartKeys);
    case 'grade': return this.sortedGrades(chartKeys);
    case 'classroom': return this.sortedClassrooms(chartKeys);
    default: return chartKeys;
    }
  }

  sortedDays(chartKeys) {
    return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
  }

  sortedTimes(chartKeys) {
    //chartKeys will either contain a time like "4:00 pm", "10:00 am", or "Not Logged"
    return chartKeys.sort((a, b) => {
      if (a == "Not Logged") return -1;
      if (b == "Not Logged") return 1;
      return new Date('1970/01/01 ' + a) - new Date('1970/01/01 ' + b);
    });
  }

  sortedGrades(chartKeys) {
    return chartKeys.sort((a,b) => sortByGrade(a,b));
  }

  sortedClassrooms(chartKeys) {
    const chart = this.getChartData(this.state.selectedChart);
    return chartKeys.sort((a,b) => {
      return chart.disciplineIncidents[b].length - chart.disciplineIncidents[a].length;
    });
  }

  render() {
    const selectedChart = this.getChartData(this.state.selectedChart);
    const chartOptions = [
      {value: 'location', label: 'Location'},
      {value: 'time', label: 'Time'},
      {value: 'classroom', label: 'Classroom'},
      {value: 'grade', label: 'Grade'},
      {value: 'day', label: 'Day'},
      {value: 'offense', label: 'Offense'},
    ];

    return(

      <ExperimentalBanner />

            {this.renderRangeSelector()}
            {this.renderStudentDisciplineTable()}

                Incidents by:

              <Select
                value={this.state.selectedChart}
                onChange={this.selectChart}
                options={chartOptions}
                style={styles.dropdown}
              />

           {this.renderDisciplineChart(selectedChart)}

    );
  }

  renderDisciplineChart(selectedChart) {
    const categories = this.sortChartKeys(Object.keys(selectedChart.disciplineIncidents));
    const seriesData = categories.map((type) => {
      if (!selectedChart.disciplineIncidents[type]) return [];
      const incidents = this.filterIncidentDates(selectedChart.disciplineIncidents[type]);
      return [type, incidents.length];
    });

    return (
        <DashboardBarChart
          id = "Discipline"
          categories = {{categories: categories}}
          seriesData = {seriesData}
          titleText = {null}
          measureText = {'Number of Incidents'}
          tooltip = {{
            pointFormat: 'Total incidents: {point.y}'}}
          onColumnClick = {this.setStudentList}
          onBackgroundClick = {this.resetStudentList}/>
    );
  }

  renderStudentDisciplineTable() {
    const students = this.props.dashboardStudents;
    const studentDisciplineIncidentCounts = this.studentDisciplineIncidentCounts(this.state.selectedCategory);
    let rows =[];
    students.forEach((student) => {
      rows.push({
        id: student.id,
        first_name: student.first_name,
        last_name: student.last_name,
        last_sst_date_text: latestNoteDateText(300, student.sst_notes),
        events: studentDisciplineIncidentCounts[student.id] || 0,
        grade: student.grade
      });
    });

    return (
      <StudentsTable
        rows = {rows}
        selectedCategory = {this.state.selectedCategory}
        incidentType={"Incidents"}
        resetFn={this.resetStudentList}/>
    );
  }

  renderRangeSelector() {
    const ninetyDaysAgo = moment.utc().subtract(90, 'days').format("YYYY-MM-DD");
    const fortyFiveDaysAgo = moment.utc().subtract(45, 'days').format("YYYY-MM-DD");
    const schoolYearStart = DashboardHelpers.schoolYearStart();
    return (

        <DashRangeButtons
          schoolYearFilter={() => this.setState({startDate: schoolYearStart, selectedRange: 'School Year'})}
          ninetyDayFilter={() => this.setState({startDate: ninetyDaysAgo, selectedRange: '90 Days'})}
          fortyFiveDayFilter={() => this.setState({startDate: fortyFiveDaysAgo, selectedRange: '45 Days'})}/>

    );
  }
}

SchoolDisciplineDashboard.propTypes = {
  dashboardStudents: PropTypes.array.isRequired,
  schoolDisciplineEvents: PropTypes.arrayOf(PropTypes.shape({
    student_id: PropTypes.number.isRequired, //ID of student involved in incident
    location: PropTypes.string, //Place where incident occurred
    time: PropTypes.string, //Time of day for incident - NULL if no specific time recorded
    classroom: PropTypes.string, //Name of student's homeroom teacher
    student_grade: PropTypes.string, //Grade of student
    day: PropTypes.string, //Day of week on which incident occurred
    offense: PropTypes.string, //Specific type of incident
    student_race: PropTypes.string, //Race of student
    occurred_at: PropTypes.string, //Date for incident, used in filtering specific date ranges
    last_sst_date_text: PropTypes.string //Date of last SST meeting
  })).isRequired
};

export default SchoolDisciplineDashboard;

const styles = {
  graphTitle: {
    display: 'flex',
    justifyContent: 'center',
    marginTop: '20px'
  },
  titleText: {
    fontSize: '18px',
    marginRight: '10px',
    alignSelf: 'center'
  },
  dropdown: {
    width: '200px'
  }
};
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import Select from 'react-select';
import 'react-select/dist/react-select.css';

import DashboardHelpers from '../DashboardHelpers';
import StudentsTable from '../StudentsTable';
import DashboardBarChart from '../DashboardBarChart';
import DashRangeButtons from '../DashRangeButtons';
import {latestNoteDateText} from '../../../helpers/latestNoteDateText';
import {sortByGrade} from '../../../helpers/SortHelpers';

class SchoolDisciplineDashboard extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      startDate: DashboardHelpers.schoolYearStart(),
      selectedChart: 'location',
      selectedCategory: null
    };
    this.setDate = this.setDate.bind(this);
    this.setStudentList = this.setStudentList.bind(this);
    this.resetStudentList = this.resetStudentList.bind(this);
    this.selectChart = this.selectChart.bind(this);
  }

  setDate(range) {
    this.setState({
      startDate: moment.unix(range[0]).format("YYYY-MM-DD")
    });
  }
  setStudentList(highchartsEvent) {
    this.setState({selectedCategory: highchartsEvent.point.category});
  }
  resetStudentList() {
    this.setState({selectedCategory: null});
  }
  selectChart(selection) {
    this.setState({selectedChart: selection.value, selectedCategory: null});
  }

  filterIncidentDates(incidents) {
    return incidents.filter((incident) => {
      return moment.utc(incident.occurred_at).isSameOrAfter(moment.utc(this.state.startDate));
    });
  }

  studentDisciplineIncidentCounts(incidentCategory) {
    let studentDisciplineIncidentCounts = {};
    const selectedChartData = this.getChartData(this.state.selectedChart);
    const incidents = incidentCategory ? selectedChartData.disciplineIncidents[incidentCategory] : this.props.schoolDisciplineEvents;
    this.filterIncidentDates(incidents).forEach((incident) => {
      studentDisciplineIncidentCounts[incident.student_id] = studentDisciplineIncidentCounts[incident.student_id] || 0;
      studentDisciplineIncidentCounts[incident.student_id]++;
    });
    return studentDisciplineIncidentCounts;
  }

  getChartData(selectedChart) {
    return {
      type: selectedChart,
      disciplineIncidents: _.groupBy(this.props.schoolDisciplineEvents, selectedChart),
      title: "Incidents by " + selectedChart};
  }

  sortChartKeys(chartKeys) {
    switch(this.state.selectedChart) {
    case 'time': return this.sortedTimes(chartKeys);
    case 'day': return this.sortedDays(chartKeys);
    case 'grade': return this.sortedGrades(chartKeys);
    case 'classroom': return this.sortedClassrooms(chartKeys);
    default: return chartKeys;
    }
  }

  sortedDays(chartKeys) {
    return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
  }

  sortedTimes(chartKeys) {
    //chartKeys will either contain a time like "4:00 pm", "10:00 am", or "Not Logged"
    return chartKeys.sort((a, b) => {
      if (a == "Not Logged") return -1;
      if (b == "Not Logged") return 1;
      return new Date('1970/01/01 ' + a) - new Date('1970/01/01 ' + b);
    });
  }

  sortedGrades(chartKeys) {
    return chartKeys.sort((a,b) => sortByGrade(a,b));

  }

  sortedClassrooms(chartKeys) {
    const chart = this.getChartData(this.state.selectedChart);
    return chartKeys.sort((a,b) => {
      return chart.disciplineIncidents[b].length - chart.disciplineIncidents[a].length;
    });
  }

  render() {
    const selectedChart = this.getChartData(this.state.selectedChart);
    const chartOptions = [
      {value: 'location', label: 'Location'},
      {value: 'time', label: 'Time'},
      {value: 'classroom', label: 'Classroom'},
      {value: 'grade', label: 'Grade'},
      {value: 'day', label: 'Day'},
      {value: 'offense', label: 'Offense'},
    ];

    return(

          {this.renderRangeSelector()}
          {this.renderStudentDisciplineTable()}

              Incidents by:

            <Select
              value={this.state.selectedChart}
              onChange={this.selectChart}
              options={chartOptions}
              style={styles.dropdown}
            />

         {this.renderDisciplineChart(selectedChart)}

    );
  }

  renderDisciplineChart(selectedChart) {
    const categories = this.sortChartKeys(Object.keys(selectedChart.disciplineIncidents));
    const seriesData = categories.map((type) => {
      if (!selectedChart.disciplineIncidents[type]) return [];
      const incidents = this.filterIncidentDates(selectedChart.disciplineIncidents[type]);
      return [type, incidents.length];
    });

    return (
        <DashboardBarChart
          id = "Discipline"
          categories = {{categories: categories}}
          seriesData = {seriesData}
          titleText = {null}
          measureText = {'Number of Incidents'}
          tooltip = {{
            pointFormat: 'Total incidents: {point.y}'}}
          onColumnClick = {this.setStudentList}
          onBackgroundClick = {this.resetStudentList}/>
    );
  }

  renderStudentDisciplineTable() {
    const students = this.props.dashboardStudents;
    const studentDisciplineIncidentCounts = this.studentDisciplineIncidentCounts(this.state.selectedCategory);
    let rows =[];
    students.forEach((student) => {
      rows.push({
        id: student.id,
        first_name: student.first_name,
        last_name: student.last_name,
        last_sst_date_text: latestNoteDateText(300, student.sst_notes),
        events: studentDisciplineIncidentCounts[student.id] || 0,
        grade: student.grade
      });
    });

    return (
      <StudentsTable
        rows = {rows}
        selectedCategory = {this.state.selectedCategory}
        incidentType={"Incidents"}
        resetFn={this.resetStudentList}/>
    );
  }

  renderRangeSelector() {
    const ninetyDaysAgo = moment.utc().subtract(90, 'days').format("YYYY-MM-DD");
    const fortyFiveDaysAgo = moment.utc().subtract(45, 'days').format("YYYY-MM-DD");
    const schoolYearStart = DashboardHelpers.schoolYearStart();
    return (

        <DashRangeButtons
          schoolYearFilter={() => this.setState({startDate: schoolYearStart, selectedRange: 'School Year'})}
          ninetyDayFilter={() => this.setState({startDate: ninetyDaysAgo, selectedRange: '90 Days'})}
          fortyFiveDayFilter={() => this.setState({startDate: fortyFiveDaysAgo, selectedRange: '45 Days'})}/>

    );
  }
}

SchoolDisciplineDashboard.propTypes = {
  dashboardStudents: PropTypes.array.isRequired,
  schoolDisciplineEvents: PropTypes.arrayOf(PropTypes.shape({
    student_id: PropTypes.number.isRequired, //ID of student involved in incident
    location: PropTypes.string, //Place where incident occurred
    time: PropTypes.string, //Time of day for incident - NULL if no specific time recorded
    classroom: PropTypes.string, //Name of student's homeroom teacher
    student_grade: PropTypes.string, //Grade of student
    day: PropTypes.string, //Day of week on which incident occurred
    offense: PropTypes.string, //Specific type of incident
    student_race: PropTypes.string, //Race of student
    occurred_at: PropTypes.string, //Date for incident, used in filtering specific date ranges
    last_sst_date_text: PropTypes.string //Date of last SST meeting
  })).isRequired
};

export default SchoolDisciplineDashboard;

const styles = {
  graphTitle: {
    display: 'flex',
    justifyContent: 'center',
    marginTop: '20px'
  },
  titleText: {
    fontSize: '18px',
    marginRight: '10px',
    alignSelf: 'center'
  },
  dropdown: {
    width: '200px'
  }
};
<commit_msg>adding experimental banner<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import Select from 'react-select';
import 'react-select/dist/react-select.css';

import DashboardHelpers from '../DashboardHelpers';
import StudentsTable from '../StudentsTable';
import DashboardBarChart from '../DashboardBarChart';
import DashRangeButtons from '../DashRangeButtons';
import {latestNoteDateText} from '../../../helpers/latestNoteDateText';
import {sortByGrade} from '../../../helpers/SortHelpers';
import ExperimentalBanner from '../../../components/ExperimentalBanner';

class SchoolDisciplineDashboard extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      startDate: DashboardHelpers.schoolYearStart(),
      selectedChart: 'location',
      selectedCategory: null
    };
    this.setDate = this.setDate.bind(this);
    this.setStudentList = this.setStudentList.bind(this);
    this.resetStudentList = this.resetStudentList.bind(this);
    this.selectChart = this.selectChart.bind(this);
  }

  setDate(range) {
    this.setState({
      startDate: moment.unix(range[0]).format("YYYY-MM-DD")
    });
  }
  setStudentList(highchartsEvent) {
    this.setState({selectedCategory: highchartsEvent.point.category});
  }
  resetStudentList() {
    this.setState({selectedCategory: null});
  }
  selectChart(selection) {
    this.setState({selectedChart: selection.value, selectedCategory: null});
  }

  filterIncidentDates(incidents) {
    return incidents.filter((incident) => {
      return moment.utc(incident.occurred_at).isSameOrAfter(moment.utc(this.state.startDate));
    });
  }

  studentDisciplineIncidentCounts(incidentCategory) {
    let studentDisciplineIncidentCounts = {};
    const selectedChartData = this.getChartData(this.state.selectedChart);
    const incidents = incidentCategory ? selectedChartData.disciplineIncidents[incidentCategory] : this.props.schoolDisciplineEvents;
    this.filterIncidentDates(incidents).forEach((incident) => {
      studentDisciplineIncidentCounts[incident.student_id] = studentDisciplineIncidentCounts[incident.student_id] || 0;
      studentDisciplineIncidentCounts[incident.student_id]++;
    });
    return studentDisciplineIncidentCounts;
  }

  getChartData(selectedChart) {
    return {
      type: selectedChart,
      disciplineIncidents: _.groupBy(this.props.schoolDisciplineEvents, selectedChart),
      title: "Incidents by " + selectedChart};
  }

  sortChartKeys(chartKeys) {
    switch(this.state.selectedChart) {
    case 'time': return this.sortedTimes(chartKeys);
    case 'day': return this.sortedDays(chartKeys);
    case 'grade': return this.sortedGrades(chartKeys);
    case 'classroom': return this.sortedClassrooms(chartKeys);
    default: return chartKeys;
    }
  }

  sortedDays(chartKeys) {
    return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
  }

  sortedTimes(chartKeys) {
    //chartKeys will either contain a time like "4:00 pm", "10:00 am", or "Not Logged"
    return chartKeys.sort((a, b) => {
      if (a == "Not Logged") return -1;
      if (b == "Not Logged") return 1;
      return new Date('1970/01/01 ' + a) - new Date('1970/01/01 ' + b);
    });
  }

  sortedGrades(chartKeys) {
    return chartKeys.sort((a,b) => sortByGrade(a,b));

  }

  sortedClassrooms(chartKeys) {
    const chart = this.getChartData(this.state.selectedChart);
    return chartKeys.sort((a,b) => {
      return chart.disciplineIncidents[b].length - chart.disciplineIncidents[a].length;
    });
  }

  render() {
    const selectedChart = this.getChartData(this.state.selectedChart);
    const chartOptions = [
      {value: 'location', label: 'Location'},
      {value: 'time', label: 'Time'},
      {value: 'classroom', label: 'Classroom'},
      {value: 'grade', label: 'Grade'},
      {value: 'day', label: 'Day'},
      {value: 'offense', label: 'Offense'},
    ];

    return(

      <ExperimentalBanner />

            {this.renderRangeSelector()}
            {this.renderStudentDisciplineTable()}

                Incidents by:

              <Select
                value={this.state.selectedChart}
                onChange={this.selectChart}
                options={chartOptions}
                style={styles.dropdown}
              />

           {this.renderDisciplineChart(selectedChart)}

    );
  }

  renderDisciplineChart(selectedChart) {
    const categories = this.sortChartKeys(Object.keys(selectedChart.disciplineIncidents));
    const seriesData = categories.map((type) => {
      if (!selectedChart.disciplineIncidents[type]) return [];
      const incidents = this.filterIncidentDates(selectedChart.disciplineIncidents[type]);
      return [type, incidents.length];
    });

    return (
        <DashboardBarChart
          id = "Discipline"
          categories = {{categories: categories}}
          seriesData = {seriesData}
          titleText = {null}
          measureText = {'Number of Incidents'}
          tooltip = {{
            pointFormat: 'Total incidents: {point.y}'}}
          onColumnClick = {this.setStudentList}
          onBackgroundClick = {this.resetStudentList}/>
    );
  }

  renderStudentDisciplineTable() {
    const students = this.props.dashboardStudents;
    const studentDisciplineIncidentCounts = this.studentDisciplineIncidentCounts(this.state.selectedCategory);
    let rows =[];
    students.forEach((student) => {
      rows.push({
        id: student.id,
        first_name: student.first_name,
        last_name: student.last_name,
        last_sst_date_text: latestNoteDateText(300, student.sst_notes),
        events: studentDisciplineIncidentCounts[student.id] || 0,
        grade: student.grade
      });
    });

    return (
      <StudentsTable
        rows = {rows}
        selectedCategory = {this.state.selectedCategory}
        incidentType={"Incidents"}
        resetFn={this.resetStudentList}/>
    );
  }

  renderRangeSelector() {
    const ninetyDaysAgo = moment.utc().subtract(90, 'days').format("YYYY-MM-DD");
    const fortyFiveDaysAgo = moment.utc().subtract(45, 'days').format("YYYY-MM-DD");
    const schoolYearStart = DashboardHelpers.schoolYearStart();
    return (

        <DashRangeButtons
          schoolYearFilter={() => this.setState({startDate: schoolYearStart, selectedRange: 'School Year'})}
          ninetyDayFilter={() => this.setState({startDate: ninetyDaysAgo, selectedRange: '90 Days'})}
          fortyFiveDayFilter={() => this.setState({startDate: fortyFiveDaysAgo, selectedRange: '45 Days'})}/>

    );
  }
}

SchoolDisciplineDashboard.propTypes = {
  dashboardStudents: PropTypes.array.isRequired,
  schoolDisciplineEvents: PropTypes.arrayOf(PropTypes.shape({
    student_id: PropTypes.number.isRequired, //ID of student involved in incident
    location: PropTypes.string, //Place where incident occurred
    time: PropTypes.string, //Time of day for incident - NULL if no specific time recorded
    classroom: PropTypes.string, //Name of student's homeroom teacher
    student_grade: PropTypes.string, //Grade of student
    day: PropTypes.string, //Day of week on which incident occurred
    offense: PropTypes.string, //Specific type of incident
    student_race: PropTypes.string, //Race of student
    occurred_at: PropTypes.string, //Date for incident, used in filtering specific date ranges
    last_sst_date_text: PropTypes.string //Date of last SST meeting
  })).isRequired
};

export default SchoolDisciplineDashboard;

const styles = {
  graphTitle: {
    display: 'flex',
    justifyContent: 'center',
    marginTop: '20px'
  },
  titleText: {
    fontSize: '18px',
    marginRight: '10px',
    alignSelf: 'center'
  },
  dropdown: {
    width: '200px'
  }
};
<|endoftext|>


<commit_before>'use strict'

const {BrowserWindow, MenuItem, webContents} = require('electron')
const EventEmitter = require('events').EventEmitter
const v8Util = process.atomBinding('v8_util')
const bindings = process.atomBinding('menu')

const {Menu} = bindings
let applicationMenu = null
let groupIdIndex = 0

Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype)

/* Instance Methods */

Menu.prototype._init = function () {
  this.commandsMap = {}
  this.groupsMap = {}
  this.items = []
  this.delegate = {
    isCommandIdChecked: id => this.commandsMap[id] ? this.commandsMap[id].checked : undefined,
    isCommandIdEnabled: id => this.commandsMap[id] ? this.commandsMap[id].enabled : undefined,
    isCommandIdVisible: id => this.commandsMap[id] ? this.commandsMap[id].visible : undefined,
    getAcceleratorForCommandId: (id, useDefaultAccelerator) => {
      const command = this.commandsMap[id]
      if (!command) return
      if (command.accelerator) return command.accelerator
      if (useDefaultAccelerator) return command.getDefaultRoleAccelerator()
    },
    getIconForCommandId: id => this.commandsMap[id] ? this.commandsMap[id].icon : undefined,
    executeCommand: (event, id) => {
      const command = this.commandsMap[id]
      if (!command) return
      command.click(event, BrowserWindow.getFocusedWindow(), webContents.getFocusedWebContents())
    },
    menuWillShow: () => {
      // Ensure radio groups have at least one menu item seleted
      for (const id in this.groupsMap) {
        const found = this.groupsMap[id].find(item => item.checked) || null
        if (!found) v8Util.setHiddenValue(this.groupsMap[id][0], 'checked', true)
      }
    }
  }
}

Menu.prototype.popup = function (window, x, y, positioningItem) {
  let [newX, newY, newPosition, newWindow] = [x, y, positioningItem, window]

  // menu.popup(x, y, positioningItem)
  if (!window) {
    // shift over values
    if (typeof window !== 'object' || window.constructor !== BrowserWindow) {
      [newPosition, newY, newX, newWindow] = [y, x, window, null]
    }
  }

  // menu.popup(window, {})
  if (x && typeof x === 'object') {
    const opts = x
    newX = opts.x
    newY = opts.y
    newPosition = opts.positioningItem
  }

  // set defaults
  if (!window) newWindow = BrowserWindow.getFocusedWindow()
  if (typeof newX !== 'number') newX = -1
  if (typeof newY !== 'number') newY = -1
  if (typeof newPosition !== 'number') newPosition = -1

  this.popupAt(newWindow, newX, newY, newPosition)
}

Menu.prototype.closePopup = function (window) {
  if (!window || window.constructor !== BrowserWindow) {
    window = BrowserWindow.getFocusedWindow()
  }
  if (window) this.closePopupAt(window.id)
}

Menu.prototype.getMenuItemById = function (id) {
  const items = this.items

  let found = items.find(item => item.id === id) || null
  for (let i = 0; !found && i < items.length; i++) {
    if (items[i].submenu) {
      found = items[i].submenu.getMenuItemById(id)
    }
  }
  return found
}

Menu.prototype.append = function (item) {
  return this.insert(this.getItemCount(), item)
}

Menu.prototype.insert = function (pos, item) {
  if ((item ? item.constructor : void 0) !== MenuItem) {
    throw new TypeError('Invalid item')
  }

  // insert item depending on its type
  insertItemByType.call(this, item, pos)

  // set item properties
  if (item.sublabel) this.setSublabel(pos, item.sublabel)
  if (item.icon) this.setIcon(pos, item.icon)
  if (item.role) this.setRole(pos, item.role)

  // Make menu accessable to items.
  item.overrideReadOnlyProperty('menu', this)

  // Remember the items.
  this.items.splice(pos, 0, item)
  this.commandsMap[item.commandId] = item
}

Menu.prototype._callMenuWillShow = function () {
  if (this.delegate) this.delegate.menuWillShow()
  this.items.forEach(item => {
    if (item.submenu) item.submenu._callMenuWillShow()
  })
}

/* Static Methods */

Menu.getApplicationMenu = () => applicationMenu

Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder

// set application menu with a preexisting menu
Menu.setApplicationMenu = function (menu) {
  if (menu && menu.constructor !== Menu) {
    throw new TypeError('Invalid menu')
  }

  applicationMenu = menu
  if (process.platform === 'darwin') {
    if (!menu) return
    menu._callMenuWillShow()
    bindings.setApplicationMenu(menu)
  } else {
    const windows = BrowserWindow.getAllWindows()
    return windows.map(w => w.setMenu(menu))
  }
}

Menu.buildFromTemplate = function (template) {
  if (!(template instanceof Array)) {
    throw new TypeError('Invalid template for Menu')
  }

  const menu = new Menu()
  const positioned = []
  let idx = 0

  // sort template by position
  template.forEach(item => {
    idx = (item.position) ? indexToInsertByPosition(positioned, item.position) : idx += 1
    positioned.splice(idx, 0, item)
  })

  // add each item from positioned menu to application menu
  positioned.forEach((item) => {
    if (typeof item !== 'object') {
      throw new TypeError('Invalid template for MenuItem')
    }
    menu.append(new MenuItem(item))
  })

  return menu
}

/* Helper Functions */

// Search between separators to find a radio menu item and return its group id
function generateGroupId (items, pos) {
  if (pos > 0) {
    for (let idx = pos - 1; idx >= 0; idx--) {
      if (items[idx].type === 'radio') return items[idx].groupId
      if (items[idx].type === 'separator') break
    }
  } else if (pos < items.length) {
    for (let idx = pos; idx <= items.length - 1; idx++) {
      if (items[idx].type === 'radio') return items[idx].groupId
      if (items[idx].type === 'separator') break
    }
  }
  groupIdIndex += 1
  return groupIdIndex
}

function indexOfItemById (items, id) {
  const foundItem = items.find(item => item.id === id) || -1
  return items.indexOf(foundItem)
}

function indexToInsertByPosition (items, position) {
  if (!position) return items.length

  const [query, id] = position.split('=') // parse query and id from position
  const idx = indexOfItemById(items, id) // calculate initial index of item

  // warn if query doesn't exist
  if (idx === -1 && query !== 'endof') {
    console.warn(`Item with id ${id} is not found`)
    return items.length
  }

  // compute new index based on query
  const queries = {
    after: (index) => {
      index += 1
      return index
    },
    endof: (index) => {
      if (index === -1) {
        items.push({id, type: 'separator'})
        index = items.length - 1
      }

      index += 1
      while (index < items.length && items[index].type !== 'separator') index += 1
      return index
    }
  }

  // return new index if needed, or original indexOfItemById
  return (query in queries) ? queries[query](idx) : idx
}

function insertItemByType (item, pos) {
  const types = {
    normal: () => this.insertItem(pos, item.commandId, item.label),
    checkbox: () => this.insertCheckItem(pos, item.commandId, item.label),
    separator: () => this.insertSeparator(pos),
    submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
    radio: () => {
      // Grouping radio menu items
      item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos))
      if (this.groupsMap[item.groupId] == null) {
        this.groupsMap[item.groupId] = []
      }
      this.groupsMap[item.groupId].push(item)

      // Setting a radio menu item should flip other items in the group.
      v8Util.setHiddenValue(item, 'checked', item.checked)
      Object.defineProperty(item, 'checked', {
        enumerable: true,
        get: () => v8Util.getHiddenValue(item, 'checked'),
        set: () => {
          this.groupsMap[item.groupId].forEach(other => {
            if (other !== item) v8Util.setHiddenValue(other, 'checked', false)
          })
          v8Util.setHiddenValue(item, 'checked', true)
        }
      })
      this.insertRadioItem(pos, item.commandId, item.label, item.groupId)
    }
  }
  types[item.type]()
}

module.exports = Menu
<commit_msg>:wrench: Always find a window (or error)<commit_after>'use strict'

const {BrowserWindow, MenuItem, webContents} = require('electron')
const EventEmitter = require('events').EventEmitter
const v8Util = process.atomBinding('v8_util')
const bindings = process.atomBinding('menu')

const {Menu} = bindings
let applicationMenu = null
let groupIdIndex = 0

Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype)

/* Instance Methods */

Menu.prototype._init = function () {
  this.commandsMap = {}
  this.groupsMap = {}
  this.items = []
  this.delegate = {
    isCommandIdChecked: id => this.commandsMap[id] ? this.commandsMap[id].checked : undefined,
    isCommandIdEnabled: id => this.commandsMap[id] ? this.commandsMap[id].enabled : undefined,
    isCommandIdVisible: id => this.commandsMap[id] ? this.commandsMap[id].visible : undefined,
    getAcceleratorForCommandId: (id, useDefaultAccelerator) => {
      const command = this.commandsMap[id]
      if (!command) return
      if (command.accelerator) return command.accelerator
      if (useDefaultAccelerator) return command.getDefaultRoleAccelerator()
    },
    getIconForCommandId: id => this.commandsMap[id] ? this.commandsMap[id].icon : undefined,
    executeCommand: (event, id) => {
      const command = this.commandsMap[id]
      if (!command) return
      command.click(event, BrowserWindow.getFocusedWindow(), webContents.getFocusedWebContents())
    },
    menuWillShow: () => {
      // Ensure radio groups have at least one menu item seleted
      for (const id in this.groupsMap) {
        const found = this.groupsMap[id].find(item => item.checked) || null
        if (!found) v8Util.setHiddenValue(this.groupsMap[id][0], 'checked', true)
      }
    }
  }
}

Menu.prototype.popup = function (window, x, y, positioningItem) {
  let [newX, newY, newPosition, newWindow] = [x, y, positioningItem, window]

  // menu.popup(x, y, positioningItem)
  if (!window) {
    // shift over values
    if (typeof window !== 'object' || window.constructor !== BrowserWindow) {
      [newPosition, newY, newX, newWindow] = [y, x, window, null]
    }
  }

  // menu.popup(window, {})
  if (x && typeof x === 'object') {
    const opts = x
    newX = opts.x
    newY = opts.y
    newPosition = opts.positioningItem
  }

  // set defaults
  if (typeof newX !== 'number') newX = -1
  if (typeof newY !== 'number') newY = -1
  if (typeof newPosition !== 'number') newPosition = -1
  if (!newWindow || (newWindow && newWindow.constructor !== BrowserWindow)) {
    newWindow = BrowserWindow.getFocusedWindow()

    // No window focused?
    if (!newWindow) {
      const browserWindows = BrowserWindow.getAllWindows()

      if (browserWindows && browserWindows.length > 0) {
        newWindow = browserWindows[0]
      } else {
        throw new Error(`Cannot open Menu without a BrowserWindow present`)
      }
    }
  }

  this.popupAt(newWindow, newX, newY, newPosition)
}

Menu.prototype.closePopup = function (window) {
  if (!window || window.constructor !== BrowserWindow) {
    window = BrowserWindow.getFocusedWindow()
  }
  if (window) this.closePopupAt(window.id)
}

Menu.prototype.getMenuItemById = function (id) {
  const items = this.items

  let found = items.find(item => item.id === id) || null
  for (let i = 0; !found && i < items.length; i++) {
    if (items[i].submenu) {
      found = items[i].submenu.getMenuItemById(id)
    }
  }
  return found
}

Menu.prototype.append = function (item) {
  return this.insert(this.getItemCount(), item)
}

Menu.prototype.insert = function (pos, item) {
  if ((item ? item.constructor : void 0) !== MenuItem) {
    throw new TypeError('Invalid item')
  }

  // insert item depending on its type
  insertItemByType.call(this, item, pos)

  // set item properties
  if (item.sublabel) this.setSublabel(pos, item.sublabel)
  if (item.icon) this.setIcon(pos, item.icon)
  if (item.role) this.setRole(pos, item.role)

  // Make menu accessable to items.
  item.overrideReadOnlyProperty('menu', this)

  // Remember the items.
  this.items.splice(pos, 0, item)
  this.commandsMap[item.commandId] = item
}

Menu.prototype._callMenuWillShow = function () {
  if (this.delegate) this.delegate.menuWillShow()
  this.items.forEach(item => {
    if (item.submenu) item.submenu._callMenuWillShow()
  })
}

/* Static Methods */

Menu.getApplicationMenu = () => applicationMenu

Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder

// set application menu with a preexisting menu
Menu.setApplicationMenu = function (menu) {
  if (menu && menu.constructor !== Menu) {
    throw new TypeError('Invalid menu')
  }

  applicationMenu = menu
  if (process.platform === 'darwin') {
    if (!menu) return
    menu._callMenuWillShow()
    bindings.setApplicationMenu(menu)
  } else {
    const windows = BrowserWindow.getAllWindows()
    return windows.map(w => w.setMenu(menu))
  }
}

Menu.buildFromTemplate = function (template) {
  if (!(template instanceof Array)) {
    throw new TypeError('Invalid template for Menu')
  }

  const menu = new Menu()
  const positioned = []
  let idx = 0

  // sort template by position
  template.forEach(item => {
    idx = (item.position) ? indexToInsertByPosition(positioned, item.position) : idx += 1
    positioned.splice(idx, 0, item)
  })

  // add each item from positioned menu to application menu
  positioned.forEach((item) => {
    if (typeof item !== 'object') {
      throw new TypeError('Invalid template for MenuItem')
    }
    menu.append(new MenuItem(item))
  })

  return menu
}

/* Helper Functions */

// Search between separators to find a radio menu item and return its group id
function generateGroupId (items, pos) {
  if (pos > 0) {
    for (let idx = pos - 1; idx >= 0; idx--) {
      if (items[idx].type === 'radio') return items[idx].groupId
      if (items[idx].type === 'separator') break
    }
  } else if (pos < items.length) {
    for (let idx = pos; idx <= items.length - 1; idx++) {
      if (items[idx].type === 'radio') return items[idx].groupId
      if (items[idx].type === 'separator') break
    }
  }
  groupIdIndex += 1
  return groupIdIndex
}

function indexOfItemById (items, id) {
  const foundItem = items.find(item => item.id === id) || -1
  return items.indexOf(foundItem)
}

function indexToInsertByPosition (items, position) {
  if (!position) return items.length

  const [query, id] = position.split('=') // parse query and id from position
  const idx = indexOfItemById(items, id) // calculate initial index of item

  // warn if query doesn't exist
  if (idx === -1 && query !== 'endof') {
    console.warn(`Item with id ${id} is not found`)
    return items.length
  }

  // compute new index based on query
  const queries = {
    after: (index) => {
      index += 1
      return index
    },
    endof: (index) => {
      if (index === -1) {
        items.push({id, type: 'separator'})
        index = items.length - 1
      }

      index += 1
      while (index < items.length && items[index].type !== 'separator') index += 1
      return index
    }
  }

  // return new index if needed, or original indexOfItemById
  return (query in queries) ? queries[query](idx) : idx
}

function insertItemByType (item, pos) {
  const types = {
    normal: () => this.insertItem(pos, item.commandId, item.label),
    checkbox: () => this.insertCheckItem(pos, item.commandId, item.label),
    separator: () => this.insertSeparator(pos),
    submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
    radio: () => {
      // Grouping radio menu items
      item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos))
      if (this.groupsMap[item.groupId] == null) {
        this.groupsMap[item.groupId] = []
      }
      this.groupsMap[item.groupId].push(item)

      // Setting a radio menu item should flip other items in the group.
      v8Util.setHiddenValue(item, 'checked', item.checked)
      Object.defineProperty(item, 'checked', {
        enumerable: true,
        get: () => v8Util.getHiddenValue(item, 'checked'),
        set: () => {
          this.groupsMap[item.groupId].forEach(other => {
            if (other !== item) v8Util.setHiddenValue(other, 'checked', false)
          })
          v8Util.setHiddenValue(item, 'checked', true)
        }
      })
      this.insertRadioItem(pos, item.commandId, item.label, item.groupId)
    }
  }
  types[item.type]()
}

module.exports = Menu
<|endoftext|>