<commit_before>/* eslint-env es6 */
/* eslint-disable global-require, no-process-env */
/* eslint quote-props: ["error", "as-needed"] */
/* global module, process, require */

/*!
 * Figuration
 * Copyright 2013-2019 CAST, Inc.
 * Licensed under MIT (https://github.com/cast-org/figuration/blob/master/LICENSE)
 */

module.exports = function(grunt) {
    'use strict';

    // Force use of Unix newlines
    grunt.util.linefeed = '\n';

    RegExp.quote = function(string) {
        return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
    };

    var saucekey = null;
    if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined') {
        saucekey = process.env.SAUCE_ACCESS_KEY;
    }

    var autoprefixer = require('autoprefixer');
    var flexbugs = require('postcss-flexbugs-fixes');
    var calc = require('postcss-calc');
    var sass = require('node-sass');

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        banner: '/*!\n' +
                ' * Figuration (v<%= pkg.version %>)\n' +
                ' * <%= pkg.homepage %>\n' +
                ' * Copyright 2013-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
                ' * Licensed under MIT (https://github.com/cast-org/figuration/blob/master/LICENSE)\n' +
                ' * -----\n' +
                ' * Portions Copyright 2011-<%= grunt.template.today("yyyy") %>  the Bootstrap Authors and Twitter, Inc.\n' +
                ' * Used under MIT License (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n' +
                ' */\n',
        jqueryCheck: 'if (typeof jQuery === \'undefined\') {\n' +
                     '  throw new Error(\'CAST Figuration\\\'s JavaScript requires jQuery\');\n' +
                     '}\n',
        jqueryVersionCheck: '(function($) {\n' +
                            '  var version = $.fn.jquery.split(\' \')[0].split(\'.\');\n' +
                            '  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] >= 4)) {\n' +
                            '    throw new Error(\'CAST Figuration\\\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0\');\n' +
                            '  }\n' +
                            '})(jQuery);\n\n',

        // File definitions
        // ==========
        jsCore: [
            'js/util.js',
            'js/drag.js',
            'js/collapse.js',
            'js/dropdown.js',
            'js/tab.js',
            'js/affix.js',
            'js/tooltip.js',
            'js/popover.js',
            'js/modal.js',
            'js/accordion.js',
            'js/tab-responsive.js',
            'js/slideshow.js',
            'js/scrollspy.js',
            'js/alert.js',
            'js/lazy.js',
            'js/slider.js',
            'js/img-compare.js',
            'js/equalize.js',
            'js/player.js',
            'js/common.js'
        ],

        jsDocs: [
            'docs/<%= pkg.version %>/assets/js/vendor/anchor.min.js',
            'docs/<%= pkg.version %>/assets/js/vendor/clipboard.min.js',
            'docs/<%= pkg.version %>/assets/js/vendor/holder.min.js',
            'docs/<%= pkg.version %>/assets/js/src/docs.js'
        ],

        // Task configs
        // ==========
        clean: {
            dist: 'dist',
            docs: 'docs/<%= pkg.version %>/dist',
            docscss: 'docs/<%= pkg.version %>/assets/css'
        },

        eslint: {
            options: {
                config: '.eslintrc.json'
            },
            grunt: {
                options: {
                    config: 'grunt/.eslintrc.json'
                },
                src: ['Gruntfile.js', 'grunt/*.js']
            },
            build: {
                options: {
                    config: 'build/.eslintrc.json'
                },
                src: 'build/*.js'
            },
            core: {
                src: '<%= jsCore %>'
            },
            test: {
                options: {
                    config: 'test/js/unit/.eslintrc.json'
                },
                src: 'test/js/unit/*.js'
            },
            docs : {
                src: ['docs/<%= pkg.version %>/assets/js/src/*.js', 'docs/<%= pkg.version %>/assets/js/*.js', '!docs/<%= pkg.version %>/assets/js/*.min.js']
            }
        },

        concat: {
            options: {
                banner: '<%= banner %>\n<%= jqueryCheck %>\n<%= jqueryVersionCheck %>',
                stripBanners: false
            },
            core: {
                src: '<%= jsCore %>',
                dest: 'dist/js/<%= pkg.name %>.js'
            }
        },

        uglify: {
            options: {
                mangle: true,
                output: {
                    comments: /^!|@preserve|@license|@cc_on/i
                }
            },
            core: {
                src: '<%= concat.core.dest %>',
                dest: 'dist/js/<%= pkg.name %>.min.js'
            },
            docs: {
                src: '<%= jsDocs %>',
                dest: 'docs/<%= pkg.version %>/assets/js/docs.min.js'
            }
        },

        copy: {
            docs: {
                expand: true,
                cwd: 'dist/',
                src: ['**/*'],
                dest: 'docs//<%= pkg.version %>/dist/'
            }
        },

        stylelint: {
            options: {
                configFile: '.stylelintrc'
            },
            core: {
                src: ['scss/**/*.scss']
            },
            docs: {
                src: ['docs/<%= pkg.version %>/assets/scss/*.scss']
            }
        },

        sass: {
            options: {
                implementation: sass,
                includePaths: ['scss'],
                precision: 6,
                sourceComments: false,
                sourceMap: true,
                outputStyle: 'expanded'
            },
            core: {
                files: {
                    'dist/css/<%= pkg.name %>.css': 'scss/<%= pkg.name %>.scss'
                }
            },
            docs: {
                files: {
                    'docs/<%= pkg.version %>/assets/css/docs.css': 'docs/<%= pkg.version %>/assets/scss/docs.scss'
                }
            }
        },

        postcss: {
            core: {
                options: {
                    map: true,
                    processors: [flexbugs, calc, autoprefixer]
                },
                src: ['dist/css/*.css', '!dist/css/*.min.css']
            },
            docs: {
                options: {
                    processors: [flexbugs, calc, autoprefixer]
                },
                src: ['docs/<%= pkg.version %>/assets/css/*.css', '!docs/<%= pkg.version %>/assets/css/*.min.css']
            }
        },

        rtlcss: {
            core: {
                opts: {
                    clean: false
                },
                expand: true,
                cwd: 'dist/css',
                src: ['*.css', '!*.min.css', '!*-rtl.css'],
                dest: 'dist/css',
                ext: '-rtl.css'
            },
            docs: {
                opts: {
                    clean: false
                },
                expand: true,
                cwd: 'docs/<%= pkg.version %>/assets/css',
                src: ['*.css', '!*.min.css', '!*-rtl.css'],
                dest: 'docs/<%= pkg.version %>/assets/css',
                ext: '-rtl.css'
            }
        },

        cssmin: {
            options: {
                report: 'gzip',
                specialComments: '*',
                sourceMap: true,
                advanced: false
            },
            core: {
                files: [
                    {
                        expand: true,
                        cwd: 'dist/css',
                        src: ['*.css', '!*.min.css'],
                        dest: 'dist/css',
                        ext: '.min.css'
                    }
                ]
            },
            docs: {
                files: [
                    {
                        expand: true,
                        cwd: 'docs/<%= pkg.version %>/assets/css',
                        src: ['*.css', '!*.min.css'],
                        dest: 'docs/<%= pkg.version %>/assets/css',
                        ext: '.min.css'
                    }
                ]
            }
        },

        jekyll: {
            options: {
                bundleExec: true,
                config: '_config.yml',
                incremental: false
            },
            docs: {},
            github: {
                options: {
                    raw: 'github: true'
                }
            }
        },

        htmllint: {
            options: {
                ignore: [
                    'Attribute "autocomplete" is only allowed when the input type is "color", "date", "datetime", "datetime-local", "email", "hidden", "month", "number", "password", "range", "search", "tel", "text", "time", "url", or "week".',
                    'Attribute "autocomplete" not allowed on element "button" at this point.',
                    'Attribute "focusable" not allowed on element "svg" at this point.',
                    'Consider using the "h1" element as a top-level heading only (all "h1" elements are treated as top-level headings by many screen readers and other tools).',
                    'Element "div" not allowed as child of element "progress" in this context. (Suppressing further errors from this subtree.)',
                    'Element "legend" not allowed as child of element "div" in this context. (Suppressing further errors from this subtree.)',
                    'Element "img" is missing required attribute "src".',
                    'The "color" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "date" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "datetime" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "datetime-local" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "month" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "time" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "week" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "main" role is unnecessary for element "main".'
                ]
            },
            docs: {
                src: ['_gh_pages/**/*.html']
            },
            test: {
                src: ['test/visual/*.html']
            }
        },

        watch: {
            src: {
                files: '<%= eslint.core.src %>',
                tasks: ['eslint:core', 'concat']
            },
            sass: {
                files: 'scss/**/*.scss',
                tasks: ['dist-css', 'docs']
            },
            docs: {
                files: 'docs/<%= pkg.version %>/assets/scss/**/*.scss',
                tasks: ['dist-css', 'docs']
            }
        },

        run: {
            npmJsTestKarma: {
                exec: 'npm run js-test-karma'
            },
            npmJsTestCloud: {
                exec: 'npm run js-test-cloud'
            }
        }
    });

    // Tasks
    // ==========
    // Load required plugins for tasks
    require('load-grunt-tasks')(grunt, {
        scope: 'devDependencies'
    });
    require('time-grunt')(grunt);

    // Default
    grunt.registerTask('default', ['clean:dist', 'test', 'docs']);

    // Test
    grunt.registerTask('test', ['dist-css', 'dist-js', 'test-css', 'test-js']);
    grunt.registerTask('test-css', ['stylelint:core']);
    grunt.registerTask('test-html', ['htmllint:test']);

    // Test - JS subtasks
    var jsTestTasks = ['eslint:core', 'eslint:test', 'eslint:grunt', 'eslint:build'];
    if (saucekey !== null && process.env.TEST_SAUCE === 'true') {
        jsTestTasks.push('run:npmJsTestCloud');
    } else {
        jsTestTasks.push('run:npmJsTestKarma');
    }
    grunt.registerTask('test-js', jsTestTasks);

    // CSS distribution
    grunt.registerTask('dist-css', ['sass:core', 'postcss:core', 'rtlcss:core', 'cssmin:core']);

    // JS distribution
    grunt.registerTask('dist-js', ['concat', 'uglify:core']);

    // Full distribution
    grunt.registerTask('dist', ['clean:dist', 'dist-css', 'dist-js']);

    // Docs tasks
    grunt.registerTask('docs-test-html', ['jekyll:docs', 'htmllint:docs']);
    grunt.registerTask('docs-test-css', ['stylelint:docs']);
    grunt.registerTask('docs-dist-css', ['sass:docs', 'postcss:docs', 'rtlcss:docs', 'cssmin:docs']);
    grunt.registerTask('docs-test-js', ['eslint:docs']);
    grunt.registerTask('docs-dist-js', ['uglify:docs']);
    grunt.registerTask('docs', ['docs-test-css', 'clean:docscss', 'docs-dist-css', 'docs-test-js', 'docs-dist-js', 'clean:docs', 'copy:docs']);
    grunt.registerTask('docs-github', ['jekyll:github']);
};
<commit_msg>Add maps to RTL css files (#481)<commit_after>/* eslint-env es6 */
/* eslint-disable global-require, no-process-env */
/* eslint quote-props: ["error", "as-needed"] */
/* global module, process, require */

/*!
 * Figuration
 * Copyright 2013-2019 CAST, Inc.
 * Licensed under MIT (https://github.com/cast-org/figuration/blob/master/LICENSE)
 */

module.exports = function(grunt) {
    'use strict';

    // Force use of Unix newlines
    grunt.util.linefeed = '\n';

    RegExp.quote = function(string) {
        return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
    };

    var saucekey = null;
    if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined') {
        saucekey = process.env.SAUCE_ACCESS_KEY;
    }

    var autoprefixer = require('autoprefixer');
    var flexbugs = require('postcss-flexbugs-fixes');
    var calc = require('postcss-calc');
    var sass = require('node-sass');

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        banner: '/*!\n' +
                ' * Figuration (v<%= pkg.version %>)\n' +
                ' * <%= pkg.homepage %>\n' +
                ' * Copyright 2013-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
                ' * Licensed under MIT (https://github.com/cast-org/figuration/blob/master/LICENSE)\n' +
                ' * -----\n' +
                ' * Portions Copyright 2011-<%= grunt.template.today("yyyy") %>  the Bootstrap Authors and Twitter, Inc.\n' +
                ' * Used under MIT License (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n' +
                ' */\n',
        jqueryCheck: 'if (typeof jQuery === \'undefined\') {\n' +
                     '  throw new Error(\'CAST Figuration\\\'s JavaScript requires jQuery\');\n' +
                     '}\n',
        jqueryVersionCheck: '(function($) {\n' +
                            '  var version = $.fn.jquery.split(\' \')[0].split(\'.\');\n' +
                            '  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] >= 4)) {\n' +
                            '    throw new Error(\'CAST Figuration\\\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0\');\n' +
                            '  }\n' +
                            '})(jQuery);\n\n',

        // File definitions
        // ==========
        jsCore: [
            'js/util.js',
            'js/drag.js',
            'js/collapse.js',
            'js/dropdown.js',
            'js/tab.js',
            'js/affix.js',
            'js/tooltip.js',
            'js/popover.js',
            'js/modal.js',
            'js/accordion.js',
            'js/tab-responsive.js',
            'js/slideshow.js',
            'js/scrollspy.js',
            'js/alert.js',
            'js/lazy.js',
            'js/slider.js',
            'js/img-compare.js',
            'js/equalize.js',
            'js/player.js',
            'js/common.js'
        ],

        jsDocs: [
            'docs/<%= pkg.version %>/assets/js/vendor/anchor.min.js',
            'docs/<%= pkg.version %>/assets/js/vendor/clipboard.min.js',
            'docs/<%= pkg.version %>/assets/js/vendor/holder.min.js',
            'docs/<%= pkg.version %>/assets/js/src/docs.js'
        ],

        // Task configs
        // ==========
        clean: {
            dist: 'dist',
            docs: 'docs/<%= pkg.version %>/dist',
            docscss: 'docs/<%= pkg.version %>/assets/css'
        },

        eslint: {
            options: {
                config: '.eslintrc.json'
            },
            grunt: {
                options: {
                    config: 'grunt/.eslintrc.json'
                },
                src: ['Gruntfile.js', 'grunt/*.js']
            },
            build: {
                options: {
                    config: 'build/.eslintrc.json'
                },
                src: 'build/*.js'
            },
            core: {
                src: '<%= jsCore %>'
            },
            test: {
                options: {
                    config: 'test/js/unit/.eslintrc.json'
                },
                src: 'test/js/unit/*.js'
            },
            docs : {
                src: ['docs/<%= pkg.version %>/assets/js/src/*.js', 'docs/<%= pkg.version %>/assets/js/*.js', '!docs/<%= pkg.version %>/assets/js/*.min.js']
            }
        },

        concat: {
            options: {
                banner: '<%= banner %>\n<%= jqueryCheck %>\n<%= jqueryVersionCheck %>',
                stripBanners: false
            },
            core: {
                src: '<%= jsCore %>',
                dest: 'dist/js/<%= pkg.name %>.js'
            }
        },

        uglify: {
            options: {
                mangle: true,
                output: {
                    comments: /^!|@preserve|@license|@cc_on/i
                }
            },
            core: {
                src: '<%= concat.core.dest %>',
                dest: 'dist/js/<%= pkg.name %>.min.js'
            },
            docs: {
                src: '<%= jsDocs %>',
                dest: 'docs/<%= pkg.version %>/assets/js/docs.min.js'
            }
        },

        copy: {
            docs: {
                expand: true,
                cwd: 'dist/',
                src: ['**/*'],
                dest: 'docs//<%= pkg.version %>/dist/'
            }
        },

        stylelint: {
            options: {
                configFile: '.stylelintrc'
            },
            core: {
                src: ['scss/**/*.scss']
            },
            docs: {
                src: ['docs/<%= pkg.version %>/assets/scss/*.scss']
            }
        },

        sass: {
            options: {
                implementation: sass,
                includePaths: ['scss'],
                precision: 6,
                sourceComments: false,
                sourceMap: true,
                outputStyle: 'expanded'
            },
            core: {
                files: {
                    'dist/css/<%= pkg.name %>.css': 'scss/<%= pkg.name %>.scss'
                }
            },
            docs: {
                files: {
                    'docs/<%= pkg.version %>/assets/css/docs.css': 'docs/<%= pkg.version %>/assets/scss/docs.scss'
                }
            }
        },

        postcss: {
            core: {
                options: {
                    map: true,
                    processors: [flexbugs, calc, autoprefixer]
                },
                src: ['dist/css/*.css', '!dist/css/*.min.css']
            },
            docs: {
                options: {
                    processors: [flexbugs, calc, autoprefixer]
                },
                src: ['docs/<%= pkg.version %>/assets/css/*.css', '!docs/<%= pkg.version %>/assets/css/*.min.css']
            }
        },

        rtlcss: {
            core: {
                options: {
                    map: true
                },
                opts: {
                    clean: false
                },
                expand: true,
                cwd: 'dist/css',
                src: ['*.css', '!*.min.css', '!*-rtl.css'],
                dest: 'dist/css',
                ext: '-rtl.css'
            },
            docs: {
                options: {
                    map: true
                },
                opts: {
                    clean: false
                },
                expand: true,
                cwd: 'docs/<%= pkg.version %>/assets/css',
                src: ['*.css', '!*.min.css', '!*-rtl.css'],
                dest: 'docs/<%= pkg.version %>/assets/css',
                ext: '-rtl.css'
            }
        },

        cssmin: {
            options: {
                report: 'gzip',
                specialComments: '*',
                sourceMap: true,
                advanced: false
            },
            core: {
                files: [
                    {
                        expand: true,
                        cwd: 'dist/css',
                        src: ['*.css', '!*.min.css'],
                        dest: 'dist/css',
                        ext: '.min.css'
                    }
                ]
            },
            docs: {
                files: [
                    {
                        expand: true,
                        cwd: 'docs/<%= pkg.version %>/assets/css',
                        src: ['*.css', '!*.min.css'],
                        dest: 'docs/<%= pkg.version %>/assets/css',
                        ext: '.min.css'
                    }
                ]
            }
        },

        jekyll: {
            options: {
                bundleExec: true,
                config: '_config.yml',
                incremental: false
            },
            docs: {},
            github: {
                options: {
                    raw: 'github: true'
                }
            }
        },

        htmllint: {
            options: {
                ignore: [
                    'Attribute "autocomplete" is only allowed when the input type is "color", "date", "datetime", "datetime-local", "email", "hidden", "month", "number", "password", "range", "search", "tel", "text", "time", "url", or "week".',
                    'Attribute "autocomplete" not allowed on element "button" at this point.',
                    'Attribute "focusable" not allowed on element "svg" at this point.',
                    'Consider using the "h1" element as a top-level heading only (all "h1" elements are treated as top-level headings by many screen readers and other tools).',
                    'Element "div" not allowed as child of element "progress" in this context. (Suppressing further errors from this subtree.)',
                    'Element "legend" not allowed as child of element "div" in this context. (Suppressing further errors from this subtree.)',
                    'Element "img" is missing required attribute "src".',
                    'The "color" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "date" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "datetime" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "datetime-local" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "month" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "time" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "week" input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.',
                    'The "main" role is unnecessary for element "main".'
                ]
            },
            docs: {
                src: ['_gh_pages/**/*.html']
            },
            test: {
                src: ['test/visual/*.html']
            }
        },

        watch: {
            src: {
                files: '<%= eslint.core.src %>',
                tasks: ['eslint:core', 'concat']
            },
            sass: {
                files: 'scss/**/*.scss',
                tasks: ['dist-css', 'docs']
            },
            docs: {
                files: 'docs/<%= pkg.version %>/assets/scss/**/*.scss',
                tasks: ['dist-css', 'docs']
            }
        },

        run: {
            npmJsTestKarma: {
                exec: 'npm run js-test-karma'
            },
            npmJsTestCloud: {
                exec: 'npm run js-test-cloud'
            }
        }
    });

    // Tasks
    // ==========
    // Load required plugins for tasks
    require('load-grunt-tasks')(grunt, {
        scope: 'devDependencies'
    });
    require('time-grunt')(grunt);

    // Default
    grunt.registerTask('default', ['clean:dist', 'test', 'docs']);

    // Test
    grunt.registerTask('test', ['dist-css', 'dist-js', 'test-css', 'test-js']);
    grunt.registerTask('test-css', ['stylelint:core']);
    grunt.registerTask('test-html', ['htmllint:test']);

    // Test - JS subtasks
    var jsTestTasks = ['eslint:core', 'eslint:test', 'eslint:grunt', 'eslint:build'];
    if (saucekey !== null && process.env.TEST_SAUCE === 'true') {
        jsTestTasks.push('run:npmJsTestCloud');
    } else {
        jsTestTasks.push('run:npmJsTestKarma');
    }
    grunt.registerTask('test-js', jsTestTasks);

    // CSS distribution
    grunt.registerTask('dist-css', ['sass:core', 'postcss:core', 'rtlcss:core', 'cssmin:core']);

    // JS distribution
    grunt.registerTask('dist-js', ['concat', 'uglify:core']);

    // Full distribution
    grunt.registerTask('dist', ['clean:dist', 'dist-css', 'dist-js']);

    // Docs tasks
    grunt.registerTask('docs-test-html', ['jekyll:docs', 'htmllint:docs']);
    grunt.registerTask('docs-test-css', ['stylelint:docs']);
    grunt.registerTask('docs-dist-css', ['sass:docs', 'postcss:docs', 'rtlcss:docs', 'cssmin:docs']);
    grunt.registerTask('docs-test-js', ['eslint:docs']);
    grunt.registerTask('docs-dist-js', ['uglify:docs']);
    grunt.registerTask('docs', ['docs-test-css', 'clean:docscss', 'docs-dist-css', 'docs-test-js', 'docs-dist-js', 'clean:docs', 'copy:docs']);
    grunt.registerTask('docs-github', ['jekyll:github']);
};
<|endoftext|>


<commit_before>import { updateChildNodes } from "../core/dom.js";
import {
  defaultState,
  getItemText,
  itemAvailableInState,
  raiseChangeEvents,
  render,
  setState,
  state,
} from "./internal.js";
import ListBox from "./ListBox.js";

/**
 * List that only shows items containing a given text string
 *
 * @inherits ListBox
 */
class FilterListBox extends ListBox {
  get [defaultState]() {
    return Object.assign(super[defaultState], {
      filter: null,
    });
  }

  /**
   * A text filter applied to the list's items. Only content elements whose
   * text contains the indicated filter text will be included in the list's
   * `items` property. The text search is case insensitive.
   *
   * By default, the filter is empty, so all substantive content elements
   * are included in `items`.
   *
   * @type {string}
   */
  get filter() {
    return this[state].filter;
  }
  set filter(filter) {
    // If external code sets the filter, it's impossible for that code to
    // predict the effects on the items and selection, so we'll need to raise
    // change events.
    const saveRaiseChangesEvents = this[raiseChangeEvents];
    this[raiseChangeEvents] = true;
    this[setState]({ filter });
    this[raiseChangeEvents] = saveRaiseChangesEvents;
  }

  /**
   * @private
   * @param {string} textToHighlight
   * @param {ListItemElement} item
   */
  highlightTextInItem(textToHighlight, item) {
    const text = item.textContent || "";
    const start = textToHighlight
      ? text.toLowerCase().indexOf(textToHighlight.toLowerCase())
      : -1;
    if (start >= 0) {
      const end = start + textToHighlight.length;
      const part1 = text.substr(0, start);
      const part2 = text.substring(start, end);
      const part3 = text.substr(end);
      const fragment = document.createDocumentFragment();
      const strong = document.createElement("strong");
      strong.textContent = part2;
      fragment.append(new Text(part1), strong, new Text(part3));
      return fragment.childNodes;
    } else {
      return [new Text(text)];
    }
  }

  /**
   * Returns true if the given item is available (matches the filter) in the
   * given state.
   *
   * @param {ListItemElement} item
   * @param {PlainObject} state
   */
  [itemAvailableInState](item, state) {
    const base = super[itemAvailableInState]
      ? super[itemAvailableInState](item, state)
      : true;
    if (!base) {
      return false;
    }
    const text = this[getItemText](item).toLowerCase();
    const filter = state.filter && state.filter.toLowerCase();
    return !filter ? true : !text ? false : text.includes(filter);
  }

  [render](/** @type {ChangedFlags} */ changed) {
    super[render](changed);

    // Hide items that don't match state.
    // For matching items, highlight the matching text.
    if (changed.filter || changed.items) {
      const { filter, items } = this[state];
      if (items) {
        items.forEach((item) => {
          const matches = this[itemAvailableInState](item, this[state]);
          item.style.display = matches ? "" : "none";
          if (matches) {
            const childNodes = this.highlightTextInItem(filter, item);
            updateChildNodes(item, childNodes);
          }
        });
      }
    }
  }
}

export default FilterListBox;
<commit_msg>Ensure cursor stays in view after filter change.<commit_after>import { updateChildNodes } from "../core/dom.js";
import {
  defaultState,
  getItemText,
  itemAvailableInState,
  raiseChangeEvents,
  render,
  rendered,
  setState,
  state,
} from "./internal.js";
import ListBox from "./ListBox.js";

/**
 * List that only shows items containing a given text string
 *
 * @inherits ListBox
 */
class FilterListBox extends ListBox {
  get [defaultState]() {
    return Object.assign(super[defaultState], {
      filter: null,
    });
  }

  /**
   * A text filter applied to the list's items. Only content elements whose
   * text contains the indicated filter text will be included in the list's
   * `items` property. The text search is case insensitive.
   *
   * By default, the filter is empty, so all substantive content elements
   * are included in `items`.
   *
   * @type {string}
   */
  get filter() {
    return this[state].filter;
  }
  set filter(filter) {
    // If external code sets the filter, it's impossible for that code to
    // predict the effects on the items and selection, so we'll need to raise
    // change events.
    const saveRaiseChangesEvents = this[raiseChangeEvents];
    this[raiseChangeEvents] = true;
    this[setState]({ filter });
    this[raiseChangeEvents] = saveRaiseChangesEvents;
  }

  /**
   * @private
   * @param {string} textToHighlight
   * @param {ListItemElement} item
   */
  highlightTextInItem(textToHighlight, item) {
    const text = item.textContent || "";
    const start = textToHighlight
      ? text.toLowerCase().indexOf(textToHighlight.toLowerCase())
      : -1;
    if (start >= 0) {
      const end = start + textToHighlight.length;
      const part1 = text.substr(0, start);
      const part2 = text.substring(start, end);
      const part3 = text.substr(end);
      const fragment = document.createDocumentFragment();
      const strong = document.createElement("strong");
      strong.textContent = part2;
      fragment.append(new Text(part1), strong, new Text(part3));
      return fragment.childNodes;
    } else {
      return [new Text(text)];
    }
  }

  /**
   * Returns true if the given item is available (matches the filter) in the
   * given state.
   *
   * @param {ListItemElement} item
   * @param {PlainObject} state
   */
  [itemAvailableInState](item, state) {
    const base = super[itemAvailableInState]
      ? super[itemAvailableInState](item, state)
      : true;
    if (!base) {
      return false;
    }
    const text = this[getItemText](item).toLowerCase();
    const filter = state.filter && state.filter.toLowerCase();
    return !filter ? true : !text ? false : text.includes(filter);
  }

  [render](/** @type {ChangedFlags} */ changed) {
    super[render](changed);

    // Hide items that don't match state.
    // For matching items, highlight the matching text.
    if (changed.filter || changed.items) {
      const { filter, items } = this[state];
      if (items) {
        items.forEach((item) => {
          const matches = this[itemAvailableInState](item, this[state]);
          item.style.display = matches ? "" : "none";
          if (matches) {
            const childNodes = this.highlightTextInItem(filter, item);
            updateChildNodes(item, childNodes);
          }
        });
      }
    }
  }

  [rendered](changed) {
    super[rendered](changed);

    // If filter changed, we may need to scroll cursor back into view.
    if (changed.filter) {
      this.scrollCurrentItemIntoView();
    }
  }
}

export default FilterListBox;
<|endoftext|>


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

// Generated on 2015-10-19 using generator-angular 0.12.1
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

var modRewrite = require('connect-modrewrite');
var execSync = require("child_process").execSync;
var serveStatic = require('serve-static');
var gitRev = execSync("git rev-parse --verify --short HEAD  | xargs echo -n");
var gitTag = execSync("git describe --tags");

module.exports = function (grunt) {

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Automatically load required Grunt tasks
  require('jit-grunt')(grunt, {
    useminPrepare: 'grunt-usemin',
    ngtemplates: 'grunt-angular-templates',
    insert: 'grunt-insert',
    cdnify: 'grunt-google-cdn'
  });

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({
    ngdocs: {
      all: ['app/**/*.js']
    },
    branch: execSync("git rev-parse --abbrev-ref HEAD  | xargs echo -n"),
    banner: "/*!\n <%= grunt.template.today('dddd, mmmm dS, yyyy, h:MM:ss TT') %>\n branch: <%= branch %>\n revision: "+gitRev+"\n */\n",

    // Project settings
    yeoman: appConfig,

    cache_control: {
      dist: {
        source: "<%= yeoman.dist %>/index.html",
        options: {
          version: gitRev,
          links: true,
          scripts: true
        }
      }
    },

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        tasks: ['newer:jshint:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer:server']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.app %>/docs/{,*/}*.md'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: '0.0.0.0',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              modRewrite([
                '^[^\\.]*$ /index.html [L]',
                '^/containers/(.*)$ /index.html [L]',
                '^/auth/(.*)$ /index.html [L]'
              ]),
              serveStatic('.tmp'),
              connect().use(
                '/bower_components',
                serveStatic('./bower_components')
              ),
              connect().use(
                '/app/styles',
                serveStatic('./app/styles')
              ),
              serveStatic(appConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              serveStatic('.tmp'),
              serveStatic('test'),
              connect().use(
                '/bower_components',
                serveStatic('./bower_components')
              ),
              serveStatic(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>',
          middleware: function (connect) {
            return [
              modRewrite([
                '^[^\\.]*$ /index.html [L]',
                '^/containers/(.*)$ /index.html [L]',
                '^/auth/(.*)$ /index.html [L]'
              ]),
              serveStatic('.tmp'),
              connect().use(
                '/bower_components',
                serveStatic('./bower_components')
              ),
              connect().use(
                '/dist/styles',
                serveStatic('./dist/styles')
              ),
              serveStatic(appConfig.dist)
            ];
          }
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git{,*/}*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      server: {
        options: {
          map: true
        },
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath:  /\.\.\//
      },
      sass: {
        src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        ignorePath: /(\.\.\/){1,2}bower_components\//
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: './bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false,
        assetCacheBuster: false,
        raw: 'Sass::Script::Number.precision = 10\n'
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          sourcemap: true
        }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {
              js: [{
                name: 'uglify',
                createConfig: function (context, block) {
                  var generated = context.options.generated;
                  generated.options = {
                    banner: "<%= banner %>"
                  };
                }
              }]
            }
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'],
      options: {
        assetsDirs: [
          '<%= yeoman.dist %>',
          '<%= yeoman.dist %>/images',
          '<%= yeoman.dist %>/styles'
        ],
        patterns: {
          js: [[/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images']]
        }
      }
    },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: false,
          //conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    //minify, combine and automatically cache html template
    ngtemplates: {
      dist: {
        options: {
          module: 'dockstore.ui',
          htmlmin: '<%= htmlmin.dist.options %>',
          usemin: 'scripts/scripts.js'
        },
        cwd: '<%= yeoman.app %>',
        src: [
          'views/{,*/}*.html',
          'templates/**/{,*/}*.html'
        ],
        dest: '.tmp/templateCache.js'
      }
    },

    // ng-annotate tries to make the code safe for minification automatically
    // by using the Angular long form for dependency injection.
    ngAnnotate: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            '*.html',
            'images/{,*/}*.{webp}',
            'styles/images/{,*/}*.*',
            'styles/fonts/{,*/}*.*',
            'static/**/*',
            'scripts/libs/**/*',
            'docs/**/*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }, {
          expand: true,
          cwd: '.',
          src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
          dest: '<%= yeoman.dist %>'
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'compass:server'
      ],
      test: [
        'compass'
      ],
      dist: [
        'compass:dist',
        'imagemin',
        'svgmin'
      ]
    },

    //insert into index.html
    insert: {
      options:{},
       main: {
          src: "<%= yeoman.app %>/gitVersion.htm",
          dest: "<%= yeoman.dist %>/index.html",
          match: ""
      }
    },
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer:server',
      'copy:styles',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'wiredep',
    'concurrent:test',
    'autoprefixer',
    'connect:test'
  ]);

  grunt.loadNpmTasks('grunt-ngdocs');

  grunt.registerTask('build', function(target){

    grunt.log.write("revision: "+gitRev);
    grunt.file.write('./app/gitVersion.htm',"
DockstoreUI&nbsp-&nbsp"+
      ""+
      gitTag+"");

    grunt.task.run([
      'clean:dist',
      'wiredep',
      'useminPrepare',
      'concurrent:dist',
      'autoprefixer',
      'ngtemplates',
      'concat',
      'ngAnnotate',
      'copy:dist',
      'copy:styles',
      'insert',
      'cdnify',
      'cssmin',
      'uglify',
      'filerev',
      'usemin',
      'htmlmin',
      'cache_control'
    ]);
  });
  grunt.registerTask('createDocs', ['ngdocs']);
  grunt.registerTask('default', [
    'newer:jshint',
    'build'
  ]);
};

<commit_msg>Looks like images in /images get rewritten but markdown has issues with this<commit_after>/*
 *    Copyright 2016 OICR
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

// Generated on 2015-10-19 using generator-angular 0.12.1
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

var modRewrite = require('connect-modrewrite');
var execSync = require("child_process").execSync;
var serveStatic = require('serve-static');
var gitRev = execSync("git rev-parse --verify --short HEAD  | xargs echo -n");
var gitTag = execSync("git describe --tags");

module.exports = function (grunt) {

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Automatically load required Grunt tasks
  require('jit-grunt')(grunt, {
    useminPrepare: 'grunt-usemin',
    ngtemplates: 'grunt-angular-templates',
    insert: 'grunt-insert',
    cdnify: 'grunt-google-cdn'
  });

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({
    ngdocs: {
      all: ['app/**/*.js']
    },
    branch: execSync("git rev-parse --abbrev-ref HEAD  | xargs echo -n"),
    banner: "/*!\n <%= grunt.template.today('dddd, mmmm dS, yyyy, h:MM:ss TT') %>\n branch: <%= branch %>\n revision: "+gitRev+"\n */\n",

    // Project settings
    yeoman: appConfig,

    cache_control: {
      dist: {
        source: "<%= yeoman.dist %>/index.html",
        options: {
          version: gitRev,
          links: true,
          scripts: true
        }
      }
    },

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        tasks: ['newer:jshint:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer:server']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.app %>/docs/{,*/}*.md'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: '0.0.0.0',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              modRewrite([
                '^[^\\.]*$ /index.html [L]',
                '^/containers/(.*)$ /index.html [L]',
                '^/auth/(.*)$ /index.html [L]'
              ]),
              serveStatic('.tmp'),
              connect().use(
                '/bower_components',
                serveStatic('./bower_components')
              ),
              connect().use(
                '/app/styles',
                serveStatic('./app/styles')
              ),
              serveStatic(appConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              serveStatic('.tmp'),
              serveStatic('test'),
              connect().use(
                '/bower_components',
                serveStatic('./bower_components')
              ),
              serveStatic(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>',
          middleware: function (connect) {
            return [
              modRewrite([
                '^[^\\.]*$ /index.html [L]',
                '^/containers/(.*)$ /index.html [L]',
                '^/auth/(.*)$ /index.html [L]'
              ]),
              serveStatic('.tmp'),
              connect().use(
                '/bower_components',
                serveStatic('./bower_components')
              ),
              connect().use(
                '/dist/styles',
                serveStatic('./dist/styles')
              ),
              serveStatic(appConfig.dist)
            ];
          }
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git{,*/}*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      server: {
        options: {
          map: true
        },
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath:  /\.\.\//
      },
      sass: {
        src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        ignorePath: /(\.\.\/){1,2}bower_components\//
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: './bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false,
        assetCacheBuster: false,
        raw: 'Sass::Script::Number.precision = 10\n'
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          sourcemap: true
        }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {
              js: [{
                name: 'uglify',
                createConfig: function (context, block) {
                  var generated = context.options.generated;
                  generated.options = {
                    banner: "<%= banner %>"
                  };
                }
              }]
            }
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'],
      options: {
        assetsDirs: [
          '<%= yeoman.dist %>',
          '<%= yeoman.dist %>/images',
          '<%= yeoman.dist %>/styles'
        ],
        patterns: {
          js: [[/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images']]
        }
      }
    },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: false,
          //conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    //minify, combine and automatically cache html template
    ngtemplates: {
      dist: {
        options: {
          module: 'dockstore.ui',
          htmlmin: '<%= htmlmin.dist.options %>',
          usemin: 'scripts/scripts.js'
        },
        cwd: '<%= yeoman.app %>',
        src: [
          'views/{,*/}*.html',
          'templates/**/{,*/}*.html'
        ],
        dest: '.tmp/templateCache.js'
      }
    },

    // ng-annotate tries to make the code safe for minification automatically
    // by using the Angular long form for dependency injection.
    ngAnnotate: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            '*.html',
            'images/{,*/}*.{webp}',
            'styles/images/{,*/}*.*',
            'styles/fonts/{,*/}*.*',
            'static/**/*',
            'scripts/libs/**/*',
            'docs/**/*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }, {
          expand: true,
          cwd: '.',
          src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
          dest: '<%= yeoman.dist %>'
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'compass:server'
      ],
      test: [
        'compass'
      ],
      dist: [
        'compass:dist',
        'imagemin',
        'svgmin'
      ]
    },

    //insert into index.html
    insert: {
      options:{},
       main: {
          src: "<%= yeoman.app %>/gitVersion.htm",
          dest: "<%= yeoman.dist %>/index.html",
          match: ""
      }
    },
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer:server',
      'copy:styles',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'wiredep',
    'concurrent:test',
    'autoprefixer',
    'connect:test'
  ]);

  grunt.loadNpmTasks('grunt-ngdocs');

  grunt.registerTask('build', function(target){

    grunt.log.write("revision: "+gitRev);
    grunt.file.write('./app/gitVersion.htm',"
DockstoreUI&nbsp-&nbsp"+
      ""+
      gitTag+"");

    grunt.task.run([
      'clean:dist',
      'wiredep',
      'useminPrepare',
      'concurrent:dist',
      'autoprefixer',
      'ngtemplates',
      'concat',
      'ngAnnotate',
      'copy:dist',
      'copy:styles',
      'insert',
      'cdnify',
      'cssmin',
      'uglify',
      'filerev',
      'usemin',
      'htmlmin',
      'cache_control'
    ]);
  });
  grunt.registerTask('createDocs', ['ngdocs']);
  grunt.registerTask('default', [
    'newer:jshint',
    'build'
  ]);
};

<|endoftext|>


<commit_before>const BASE_BLOCK_WIDTH = 30
const WIDTH = 10
const HEIGHT = 20
const VELOCITY = 0.05

const BLOCK_COLORS = {
  'o': '#FBC02D',
  'i': '#0097A7',
  's': '#4CAF50',
  'z': '#F44336',
  'l': '#FF9800',
  'j': '#2196F3',
  't': '#9C27B0'
}
const SHAPES = {
  'o': [
    [[1, 1], [1, 1]]
  ],
  'i': [
    [[1, 1, 1, 1]],
    [[1], [1], [1], [1]]
  ],
  's': [
    [[0, 1], [1, 1], [1]],
    [[1, 1], [0, 1, 1]]
  ],
  'z': [
    [[1], [1, 1], [0, 1]],
    [[0, 1, 1], [1, 1]]
  ],
  'l': [
    [[1, 1, 1], [0, 0, 1]],
    [[1, 1], [1], [1]],
    [[1], [1, 1, 1]],
    [[0, 1], [0, 1], [1, 1]]
  ],
  'j': [
    [[0, 0, 1], [1, 1, 1]],
    [[1, 1], [0, 1], [0, 1]],
    [[1, 1, 1], [1]],
    [[1], [1], [1, 1]]
  ],
  't': [
    [[1, 0], [1, 1], [1]],
    [[0, 1], [1, 1, 1]],
    [[0, 1], [1, 1], [0, 1]],
    [[1, 1, 1], [0, 1]]
  ]
}

class Board {
  constructor (x, y) {
    this.score = 0
    this.gameOver = false
    this.tetris = false
    this.x = x
    this.y = y
    this.width = BASE_BLOCK_WIDTH * WIDTH
    this.height = BASE_BLOCK_WIDTH * HEIGHT
    this.color = '#333'
    this.board = []
    // initialize starting board position based on the width and height
    for (var i = 0; i < HEIGHT; i ++) {
      this.board.push([])
      for (var j = 0; j < WIDTH; j ++) {
        this.board[i].push(new BaseBlock(this, j, i, 'empty'))
      }
    }
    this.spawnNewBlock()
  }

  draw (ctx) {
    ctx.strokeStyle = this.color
    ctx.strokeRect(this.x, this.y, this.width, this.height)
    ctx.fillStyle = '#333'
    ctx.font = '24px monospace'
    ctx.fillText('Score: ', this.x * 2 + this.width, this.y * 3)
    ctx.fillText(this.score, this.x * 2 + this.width, this.y * 5)
    ctx.fillText('Next: ', this.x * 2 + this.width, this.y * 7)

    this.board.forEach(row => {
      row.forEach(b => {
        b.draw(ctx)
      })
    })
    this.nextBlock.draw(ctx)
    this.activeBlock.draw(ctx)
  }

  handleAction (keyPressed) {
    if (this.gameOver) {
      return
    }
    // validate input (boundary)
    Object.keys(keyPressed).forEach(key => {
      if (keyPressed[key]) {
        let newBlock = this.activeBlock.move(key)
        if (newBlock.blocks.every(b => this.isValid(b))) {
          this.activeBlock = newBlock
        }
        keyPressed[key] = false
      }
    })
    // check if the current active block is colliding if so, spawn a new block
    if (this.activeBlock.blocks.some(b => this.collide(b))) {
      // collide
      this.activeBlock.collideCounter ++
      if (this.activeBlock.collideCounter >= 30) {
        this.activeBlock.blocks.forEach(b => {
          let y = Math.round(b.y)
            b.y = y
            this.board[b.y][b.x] = b
        })
        this.spawnNewBlock()
      }
    } else {
      this.activeBlock.collideCounter = 0
      let newBlock = this.activeBlock.move()
      if (newBlock.blocks.every(b => this.isValid(b))) {
        this.activeBlock = newBlock
      }
    }
    this.clearLine()
  }

  clearLine () {
    let lineCleared = 0
    this.board.forEach((row, index) => {
      if (row.every(b => b.type !== 'empty')) {
        lineCleared ++
        let emptyRow = []
        for (var j = 0; j < WIDTH; j ++) {
          emptyRow.push(new BaseBlock(this, j, 0, 'empty'))
        }
        for (var i = index; i > 0; i --) {
          let rowAbove = this.board[i - 1]
          rowAbove.forEach(b => {
            b.y = b.y+1
          })
          this.board[i] = rowAbove
        }
        this.board[0] = emptyRow
      }
    })
    if (lineCleared > 0) {
      if (this.tetris && lineCleared === 4) {
        // back to back tetris!
        this.score += 400
      }
      this.tetris = lineCleared === 4
      this.score += 100 * Math.pow(2, lineCleared - 1)
    }
  }

  isValid (block) {
    let y = Math.floor(block.y)
    let x = block.x
    return x >= 0 &&
      x <= WIDTH - 1 &&
      this.board[y][x].type === 'empty' &&
      y < HEIGHT
  }

  collide (block) {
    let y = Math.floor(block.y)
    let x = block.x
    return y >= (HEIGHT - 1) ||
      (
        this.board[y+1][x] &&
        this.board[y+1][x].type !== 'empty'
      )
  }

  spawnNewBlock () {
    let shapes = Object.keys(SHAPES)
    let randomShape = shapes[Math.floor(Math.random() * shapes.length)]

    if (!this.activeBlock) {
      this.activeBlock = new ComplexBlock(this, randomShape)
      randomShape = shapes[Math.floor(Math.random() * shapes.length)]
    } else {
      this.activeBlock = new ComplexBlock(this, this.nextBlock.shape)
    }
    let x = this.x * 2 + this.width
    let y = this.y * 8
    this.nextBlock = new ComplexBlock({x, y}, randomShape, 0, 1, 0)

    if (this.activeBlock.blocks.some(b => this.collide(b))) {
      this.gameOver = true
    }
  }
}

class ComplexBlock {
  constructor(board, shape, shapeIndex = 0, x = 4, y = 0) {
    this.board = board
    this.shape = shape
    this.blocks = []
    this.shapeIndex = shapeIndex
    this.collideCounter = 0
    let shapeMatrix = SHAPES[this.shape][shapeIndex]
    shapeMatrix.forEach((row, i) => {
      row.forEach((col, j)=> {
        if (shapeMatrix[i][j] === 1) {
          this.blocks.push(new BaseBlock(board, x+i, y+j, this.shape))
        }
      })
    })
  }

  move (action) {
    if (action === 'rotate') {
      let x = this.blocks[0].x
      let y = this.blocks[0].y
      let shapeIndex = (this.shapeIndex === SHAPES[this.shape].length - 1) ?
        0 : this.shapeIndex + 1
      let newBlock = new ComplexBlock(this.board, this.shape, shapeIndex, x, y)
      return newBlock
    } else if (action === 'drop') {
      let newBlock = this.move()
      // TODO: probably find a better way to loop though this better later
      while (!newBlock.blocks.some(b => this.board.collide(b))) {
        newBlock = newBlock.move()
      }
      newBlock.collideCounter = 30
      return newBlock
    }

    let newBlock = Object.assign(Object.create(this), this)
    newBlock.blocks = newBlock.blocks.map(b => b.move(action))
    return newBlock
  }

  draw (ctx) {
    this.blocks.forEach(b => b.draw(ctx))
  }
}

class BaseBlock {
  constructor(board, x = 4, y = 0, type = 'block') {
    this.board = board
    this.x = x
    this.y = y
    this.width = BASE_BLOCK_WIDTH
    this.height = BASE_BLOCK_WIDTH
    this.type = type
    this.color = this.type === 'empty' ? '#ccc' : BLOCK_COLORS[type]
    this.velocity = VELOCITY
  }

  move (action) {
    let newBlock = Object.assign(Object.create(this), this)
    switch (action) {
      case 'left':
        newBlock.x--
        break
      case 'right':
        newBlock.x++
        break
      case 'down':
        newBlock.y+=this.velocity*5
        break
      default:
        newBlock.y+=this.velocity
    }
    return newBlock
  }

  draw (ctx) {
    let x = this.board.x + this.x * BASE_BLOCK_WIDTH
    let y = this.board.y + this.y * BASE_BLOCK_WIDTH
    ctx.fillStyle = this.color
    ctx.fillRect(x, y, this.width, this.height)
    ctx.strokeStyle = '#eee'
    ctx.strokeRect(x, y, this.width, this.height)
  }
}

export {
  Board,
  BaseBlock
}
<commit_msg>feat: add line cleared on UI<commit_after>const BASE_BLOCK_WIDTH = 30
const WIDTH = 10
const HEIGHT = 20
const VELOCITY = 0.05

const BLOCK_COLORS = {
  'o': '#FBC02D',
  'i': '#0097A7',
  's': '#4CAF50',
  'z': '#F44336',
  'l': '#FF9800',
  'j': '#2196F3',
  't': '#9C27B0'
}
const SHAPES = {
  'o': [
    [[1, 1], [1, 1]]
  ],
  'i': [
    [[1, 1, 1, 1]],
    [[1], [1], [1], [1]]
  ],
  's': [
    [[0, 1], [1, 1], [1]],
    [[1, 1], [0, 1, 1]]
  ],
  'z': [
    [[1], [1, 1], [0, 1]],
    [[0, 1, 1], [1, 1]]
  ],
  'l': [
    [[1, 1, 1], [0, 0, 1]],
    [[1, 1], [1], [1]],
    [[1], [1, 1, 1]],
    [[0, 1], [0, 1], [1, 1]]
  ],
  'j': [
    [[0, 0, 1], [1, 1, 1]],
    [[1, 1], [0, 1], [0, 1]],
    [[1, 1, 1], [1]],
    [[1], [1], [1, 1]]
  ],
  't': [
    [[1, 0], [1, 1], [1]],
    [[0, 1], [1, 1, 1]],
    [[0, 1], [1, 1], [0, 1]],
    [[1, 1, 1], [0, 1]]
  ]
}

class Board {
  constructor (x, y) {
    this.score = 0
    this.lineCleared = 0
    this.gameOver = false
    this.tetris = false
    this.x = x
    this.y = y
    this.width = BASE_BLOCK_WIDTH * WIDTH
    this.height = BASE_BLOCK_WIDTH * HEIGHT
    this.color = '#333'
    this.board = []
    // initialize starting board position based on the width and height
    for (var i = 0; i < HEIGHT; i ++) {
      this.board.push([])
      for (var j = 0; j < WIDTH; j ++) {
        this.board[i].push(new BaseBlock(this, j, i, 'empty'))
      }
    }
    this.spawnNewBlock()
  }

  draw (ctx) {
    ctx.strokeStyle = this.color
    ctx.strokeRect(this.x, this.y, this.width, this.height)
    ctx.fillStyle = '#333'
    ctx.font = '24px monospace'
    let rightX = this.x * 2 + this.width
    ctx.fillText('Score: ', rightX, this.y * 3)
    ctx.fillText(this.score, rightX, this.y * 5)
    ctx.fillText('Lines:', rightX, this.y * 7)
    ctx.fillText(this.lineCleared, rightX, this.y * 9)
    ctx.fillText('Next: ', rightX, this.y * 11)

    this.board.forEach(row => {
      row.forEach(b => {
        b.draw(ctx)
      })
    })
    this.nextBlock.draw(ctx)
    this.activeBlock.draw(ctx)
  }

  handleAction (keyPressed) {
    if (this.gameOver) {
      return
    }
    // validate input (boundary)
    Object.keys(keyPressed).forEach(key => {
      if (keyPressed[key]) {
        let newBlock = this.activeBlock.move(key)
        if (newBlock.blocks.every(b => this.isValid(b))) {
          this.activeBlock = newBlock
        }
        keyPressed[key] = false
      }
    })
    // check if the current active block is colliding if so, spawn a new block
    if (this.activeBlock.blocks.some(b => this.collide(b))) {
      // collide
      this.activeBlock.collideCounter ++
      if (this.activeBlock.collideCounter >= 30) {
        this.activeBlock.blocks.forEach(b => {
          let y = Math.round(b.y)
            b.y = y
            this.board[b.y][b.x] = b
        })
        this.spawnNewBlock()
      }
    } else {
      this.activeBlock.collideCounter = 0
      let newBlock = this.activeBlock.move()
      if (newBlock.blocks.every(b => this.isValid(b))) {
        this.activeBlock = newBlock
      }
    }
    this.clearLine()
  }

  clearLine () {
    let lineCleared = 0
    this.board.forEach((row, index) => {
      if (row.every(b => b.type !== 'empty')) {
        lineCleared ++
        let emptyRow = []
        for (var j = 0; j < WIDTH; j ++) {
          emptyRow.push(new BaseBlock(this, j, 0, 'empty'))
        }
        for (var i = index; i > 0; i --) {
          let rowAbove = this.board[i - 1]
          rowAbove.forEach(b => {
            b.y = b.y+1
          })
          this.board[i] = rowAbove
        }
        this.board[0] = emptyRow
      }
    })
    if (lineCleared > 0) {
      if (this.tetris && lineCleared === 4) {
        // back to back tetris!
        this.score += 400
      }
      this.tetris = lineCleared === 4
      this.score += 100 * Math.pow(2, lineCleared - 1)
    }
    this.lineCleared += lineCleared
  }

  isValid (block) {
    let y = Math.floor(block.y)
    let x = block.x
    return x >= 0 &&
      x <= WIDTH - 1 &&
      this.board[y][x].type === 'empty' &&
      y < HEIGHT
  }

  collide (block) {
    let y = Math.floor(block.y)
    let x = block.x
    return y >= (HEIGHT - 1) ||
      (
        this.board[y+1][x] &&
        this.board[y+1][x].type !== 'empty'
      )
  }

  spawnNewBlock () {
    let shapes = Object.keys(SHAPES)
    let randomShape = shapes[Math.floor(Math.random() * shapes.length)]

    if (!this.activeBlock) {
      this.activeBlock = new ComplexBlock(this, randomShape)
      randomShape = shapes[Math.floor(Math.random() * shapes.length)]
    } else {
      this.activeBlock = new ComplexBlock(this, this.nextBlock.shape)
    }
    let x = this.x * 2 + this.width
    let y = this.y * 12
    this.nextBlock = new ComplexBlock({x, y}, randomShape, 0, 1, 0)

    if (this.activeBlock.blocks.some(b => this.collide(b))) {
      this.gameOver = true
    }
  }
}

class ComplexBlock {
  constructor(board, shape, shapeIndex = 0, x = 4, y = 0) {
    this.board = board
    this.shape = shape
    this.blocks = []
    this.shapeIndex = shapeIndex
    this.collideCounter = 0
    let shapeMatrix = SHAPES[this.shape][shapeIndex]
    shapeMatrix.forEach((row, i) => {
      row.forEach((col, j)=> {
        if (shapeMatrix[i][j] === 1) {
          this.blocks.push(new BaseBlock(board, x+i, y+j, this.shape))
        }
      })
    })
  }

  move (action) {
    if (action === 'rotate') {
      let x = this.blocks[0].x
      let y = this.blocks[0].y
      let shapeIndex = (this.shapeIndex === SHAPES[this.shape].length - 1) ?
        0 : this.shapeIndex + 1
      let newBlock = new ComplexBlock(this.board, this.shape, shapeIndex, x, y)
      return newBlock
    } else if (action === 'drop') {
      let newBlock = this.move()
      // TODO: probably find a better way to loop though this better later
      while (!newBlock.blocks.some(b => this.board.collide(b))) {
        newBlock = newBlock.move()
      }
      newBlock.collideCounter = 30
      return newBlock
    }

    let newBlock = Object.assign(Object.create(this), this)
    newBlock.blocks = newBlock.blocks.map(b => b.move(action))
    return newBlock
  }

  draw (ctx) {
    this.blocks.forEach(b => b.draw(ctx))
  }
}

class BaseBlock {
  constructor(board, x = 4, y = 0, type = 'block') {
    this.board = board
    this.x = x
    this.y = y
    this.width = BASE_BLOCK_WIDTH
    this.height = BASE_BLOCK_WIDTH
    this.type = type
    this.color = this.type === 'empty' ? '#ccc' : BLOCK_COLORS[type]
    this.velocity = VELOCITY
  }

  move (action) {
    let newBlock = Object.assign(Object.create(this), this)
    switch (action) {
      case 'left':
        newBlock.x--
        break
      case 'right':
        newBlock.x++
        break
      case 'down':
        newBlock.y+=this.velocity*5
        break
      default:
        newBlock.y+=this.velocity
    }
    return newBlock
  }

  draw (ctx) {
    let x = this.board.x + this.x * BASE_BLOCK_WIDTH
    let y = this.board.y + this.y * BASE_BLOCK_WIDTH
    ctx.fillStyle = this.color
    ctx.fillRect(x, y, this.width, this.height)
    ctx.strokeStyle = '#eee'
    ctx.strokeRect(x, y, this.width, this.height)
  }
}

export {
  Board,
  BaseBlock
}
<|endoftext|>


<commit_before>module.exports = function(grunt) {

  var pkg = grunt.file.readJSON('package.json');

  grunt.initConfig({

    gitinfo: {},

    pkg: pkg,

    // Ignore patch versions. Those should only have bugfixes so we just want to
    // udpate the docs in that case. Minor version changes should add features
    // meaning we want new docs.
    docVersion: pkg.version.split('.').slice(0,-1).join('.'),

    src: ['src/**/*.js'],

    clean: {
      dist: ['dist/<%= docVersion %>']
    },

    ngdocs: {
      options: {
        dest: 'dist/<%= docVersion %>',
        html5Mode: false,
        title: 'FamilySearch Javascript SDK',
        bestMatch: false,
        navTemplate: 'dist/nav.tmpl'
      },
      all: ['<%= src %>']
    },

    jshint: {
      src: {
        options: {
          jshintrc: '.jshintrc'
        },
        src: ['<%= src %>']
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/unit/*.js']
      }
    },

    'gh-pages': {
      options: {
        base: 'dist',
        message: 'Update docs and files to distribute',
        add: true
      },
      travis: {
        options: {
          repo: 'https://' + process.env.GH_TOKEN + '@github.com/FamilySearch/familysearch-javascript-sdk.git',
          user: {
            name: 'Travis',
            email: 'travis@travis-ci.org'
          },
          silent: true
        },
        src: ['**/*']
      }
    },

    copy: {
      dist: {
        files: [{
          cwd: '.',
          src: 'bower.json',
          dest: 'dist/<%= docVersion %>',
          expand: true
        }]
      }
    },

    run: {
      jasmine: {
        exec: 'npm run jasmine'
      },
      coveralls: {
        exec: 'npm run coveralls'
      },
      browserify: {
        exec: 'node_modules/.bin/browserify src/FamilySearch.js --standalone FamilySearch > dist/<%= docVersion %>/familysearch-javascript-sdk.js'
      }
    },

    uglify: {
      build: {
        files: {
          'dist/<%= docVersion %>/familysearch-javascript-sdk.min.js': 'dist/<%= docVersion %>/familysearch-javascript-sdk.js'
        }
      }
    },

    gitcheckout: {
      'gh-pages': {
        options: {
          branch: 'gh-pages'
        }
      },
      'reset': {
        options: {
          branch: '<%= gitinfo.local.branch.current.name %>'
        }
      }
    },

    gitstash: {
      stash: {
        options: {
          command: 'save'
        }
      },
      apply: {
        options: {
          command: 'apply'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-gh-pages');
  grunt.loadNpmTasks('grunt-ngdocs');
  grunt.loadNpmTasks('grunt-run');
  grunt.loadNpmTasks('grunt-gitinfo');
  grunt.loadNpmTasks('grunt-git');

  grunt.registerTask('test', [
    'jshint',
    'run:jasmine'
  ]);

  // Get a list of previous doc versions by checkout out gh-pages branch
  // and traversing the list of directories
  grunt.registerTask('versions', function(){

    // We filter out any lingering directories that might not match what
    // we're looking for, such as node_modules
    var versionMatcher = /^\d+\.\d+$/,
        versions = grunt.file.expand({
          filter: "isDirectory"
        }, ["*"]).filter(function(name){
          return versionMatcher.test(name);
        });

    var currentVersion = grunt.config.get('docVersion');
    if(versions.indexOf(currentVersion) === -1){
      versions.push(currentVersion);
    }

    // We want them in reverse order so that most recent is first
    versions.sort().reverse();

    // Save in grunt config so that the docs generator can see it
    grunt.config.set('versions', versions);
  });

  grunt.registerTask('docsindex', function(){
    grunt.file.write('dist/index.html', grunt.template.process(grunt.file.read('dist/index.tmpl')));
  });

  grunt.registerTask('build', [
    'clean:dist',
    'test',
    'gitinfo',
    'gitstash:stash',
    'gitcheckout:gh-pages',
    'versions',
    'gitcheckout:reset',
    'gitstash:apply',
    'ngdocs',
    'docsindex',
    'run:browserify',
    'uglify',
    'copy:dist'
  ]);

  grunt.registerTask('travis', [
    'build',
    'run:coveralls',
    'gh-pages:travis'
  ]);

  // Default task(s)
  grunt.registerTask('default', ['build']);
};
<commit_msg>don't build with uncommitted changes<commit_after>module.exports = function(grunt) {

  var pkg = grunt.file.readJSON('package.json');

  grunt.initConfig({

    gitinfo: {},

    pkg: pkg,

    // Ignore patch versions. Those should only have bugfixes so we just want to
    // udpate the docs in that case. Minor version changes should add features
    // meaning we want new docs.
    docVersion: pkg.version.split('.').slice(0,-1).join('.'),

    src: ['src/**/*.js'],

    clean: {
      dist: ['dist/<%= docVersion %>']
    },

    ngdocs: {
      options: {
        dest: 'dist/<%= docVersion %>',
        html5Mode: false,
        title: 'FamilySearch Javascript SDK',
        bestMatch: false,
        navTemplate: 'dist/nav.tmpl'
      },
      all: ['<%= src %>']
    },

    jshint: {
      src: {
        options: {
          jshintrc: '.jshintrc'
        },
        src: ['<%= src %>']
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/unit/*.js']
      }
    },

    'gh-pages': {
      options: {
        base: 'dist',
        message: 'Update docs and files to distribute',
        add: true
      },
      travis: {
        options: {
          repo: 'https://' + process.env.GH_TOKEN + '@github.com/FamilySearch/familysearch-javascript-sdk.git',
          user: {
            name: 'Travis',
            email: 'travis@travis-ci.org'
          },
          silent: true
        },
        src: ['**/*']
      }
    },

    copy: {
      dist: {
        files: [{
          cwd: '.',
          src: 'bower.json',
          dest: 'dist/<%= docVersion %>',
          expand: true
        }]
      }
    },

    run: {
      jasmine: {
        exec: 'npm run jasmine'
      },
      coveralls: {
        exec: 'npm run coveralls'
      },
      browserify: {
        exec: 'node_modules/.bin/browserify src/FamilySearch.js --standalone FamilySearch > dist/<%= docVersion %>/familysearch-javascript-sdk.js'
      }
    },

    uglify: {
      build: {
        files: {
          'dist/<%= docVersion %>/familysearch-javascript-sdk.min.js': 'dist/<%= docVersion %>/familysearch-javascript-sdk.js'
        }
      }
    },

    gitcheckout: {
      'gh-pages': {
        options: {
          branch: 'gh-pages'
        }
      },
      'reset': {
        options: {
          branch: '<%= gitinfo.local.branch.current.name %>'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-gh-pages');
  grunt.loadNpmTasks('grunt-ngdocs');
  grunt.loadNpmTasks('grunt-run');
  grunt.loadNpmTasks('grunt-gitinfo');
  grunt.loadNpmTasks('grunt-git');

  grunt.registerTask('test', [
    'jshint',
    'run:jasmine'
  ]);

  // Get a list of previous doc versions by checkout out gh-pages branch
  // and traversing the list of directories
  grunt.registerTask('versions', function(){

    // We filter out any lingering directories that might not match what
    // we're looking for, such as node_modules
    var versionMatcher = /^\d+\.\d+$/,
        versions = grunt.file.expand({
          filter: "isDirectory"
        }, ["*"]).filter(function(name){
          return versionMatcher.test(name);
        });

    var currentVersion = grunt.config.get('docVersion');
    if(versions.indexOf(currentVersion) === -1){
      versions.push(currentVersion);
    }

    // We want them in reverse order so that most recent is first
    versions.sort().reverse();

    // Save in grunt config so that the docs generator can see it
    grunt.config.set('versions', versions);
  });

  grunt.registerTask('docsindex', function(){
    grunt.file.write('dist/index.html', grunt.template.process(grunt.file.read('dist/index.tmpl')));
  });

  grunt.registerTask('build', [
    'clean:dist',
    'test',
    'gitinfo',
    'gitcheckout:gh-pages',
    'versions',
    'gitcheckout:reset',
    'ngdocs',
    'docsindex',
    'run:browserify',
    'uglify',
    'copy:dist'
  ]);

  grunt.registerTask('travis', [
    'build',
    'run:coveralls',
    'gh-pages:travis'
  ]);

  // Default task(s)
  grunt.registerTask('default', ['build']);
};
<|endoftext|>


<commit_before>module.exports = function (grunt) {
  'use strict';

  // Project configuration.
  grunt.initConfig({
    // secret.json contains the host, username and password for a server to
    // run the tests on.
    secret: grunt.file.readJSON('secret.json'),
    test: {
      files: ['test/**/*.js']
    },
    nodeunit: {
      files: ['test/**/*.js']
    },
    beautify: {
      files: '<%= jshint.files %>'
    },
    watch: {
      files: '<%= jshint.files %>',
      tasks: 'default'
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        boss: true,
        eqnull: true,
        node: true,
        globals: {
          exports: true
        }
      },
      files: ['grunt.js', 'tasks/**/*.js', 'test/**/*.js']
    },
    sftp: {
      test: {
        files: {
          "./": "tasks/**/*.js"
        },
        options: {
          host: '<%= secret.host %>',
          username: '<%= secret.username %>',
          // password auth
          password: '<%= secret.password %>'
          // private key auth
          /*
          privateKey: grunt.file.read('id_rsa'),
          passphrase: '<%= secret.passphrase %>',
          */
          // create directories
          /*
          path: "/tmp/does/not/exist/",
          createDirectories: true
          */
        }
      }
    },
    sshexec: {
      test: {
        // single command
        // command: 'uptime',
        // multiple commands
        command: ['uptime', 'ls', 'uptime'],
        options: {
          host: '<%= secret.host %>',
          username: '<%= secret.username %>',
          // password auth
          password: '<%= secret.password %>'
          // private key auth
          /*
          privateKey: grunt.file.read('id_rsa'),
          passphrase: '<%= secret.passphrase %>',
          */
        }
      }
    }
  });

  // Actually load this plugin's tasks
  grunt.loadTasks('tasks');

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-beautify');
  grunt.loadNpmTasks('grunt-bump');
  grunt.loadNpmTasks('grunt-contrib-nodeunit');

  // Default task.
  grunt.registerTask('default', ['jshint','nodeunit']);

  grunt.registerTask('tidy', ['beautify']);

};
<commit_msg>show sshexec:test using private key. tidy<commit_after>module.exports = function (grunt) {
  'use strict';

  var secret = grunt.file.readJSON('secret.json');

  // Project configuration.
  grunt.initConfig({
    // secret.json contains the host, username and password for a server to
    // run the tests on.
    secret: secret,
    test: {
      files: ['test/**/*.js']
    },
    nodeunit: {
      files: ['test/**/*.js']
    },
    beautify: {
      files: '<%= jshint.files %>'
    },
    watch: {
      files: '<%= jshint.files %>',
      tasks: 'default'
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        boss: true,
        eqnull: true,
        node: true,
        globals: {
          exports: true
        }
      },
      files: ['Gruntfile.js', 'tasks/**/*.js', 'test/**/*.js']
    },
    sftp: {
      test: {
        files: {
          "./": "tasks/**/*.js"
        },
        options: {
          host: '<%= secret.host %>',
          username: '<%= secret.username %>',
          // password auth
          password: '<%= secret.password %>'
          // private key auth
/*
          privateKey: grunt.file.read('id_rsa'),
          passphrase: '<%= secret.passphrase %>',
          */
          // create directories
/*
          path: "/tmp/does/not/exist/",
          createDirectories: true
          */
        }
      }
    },
    sshexec: {
      test: {
        // single command
        // command: 'uptime',
        // multiple commands
        command: ['uptime', 'ls', 'uptime'],
        options: {
          host: secret.host,
          username: secret.username,
          // private key auth
          privateKey: grunt.file.read(secret.privateKeyPath),
          passphrase: secret.passphrase
        }
      }
    }
  });

  // Actually load this plugin's tasks
  grunt.loadTasks('tasks');

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-beautify');
  grunt.loadNpmTasks('grunt-bump');
  grunt.loadNpmTasks('grunt-contrib-nodeunit');

  // Default task.
  grunt.registerTask('default', ['jshint', 'nodeunit']);

  grunt.registerTask('tidy', ['beautify']);

};<|endoftext|>


<commit_before>/*global module:false*/
module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
      '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
      '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
      '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
      ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',
    concat_sourcemap: {
      options: {
        sourcesContent: true
      },
      target: {
        files: {
          'dist/FooFighter.js': [
              'src/namespace.js',
              'src/modules/Util.js',
              'bower_components/shout/shout.js',
              'src/modules/GameState.js',
              'src/modules/GameEngine.js',
              'src/modules/StartScreen.js',
              'src/entities/Score.js',
              'src/entities/Timer.js',
              'src/entities/StarField.js',
              'src/entities/Player.js',
              'src/entities/EnemyShip.js',
              'src/entities/Asteroid.js',
              'src/app.js'
          ]
        }
      }
    },
    uglify: {
      options: {
        banner: '<%= banner %>',
        sourceMap: 'dist/FooFighter.min.map',
        sourceMappingURL: 'dist/FooFighter.min.map',
        sourceMapPrefix: 1
      },
      dist: {
        src: 'dist/FooFighter.js',
        dest: 'dist/FooFighter.min.js'
      }
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        unused: true,
        boss: true,
        eqnull: true,
        browser: true,
        globals: {}
      },
      gruntfile: {
        src: 'Gruntfile.js'
      }
    },
    watch: {
      gruntfile: {
        files: ['src/**/*.js','src/*.js'],
        tasks: ['jshint:gruntfile', 'default']
      }
    },
    connect: {
      server: {
        options: {
          port: 8000,
          base: './',
          keepalive: true
        }
      }
  }
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-concat-sourcemap');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-connect');

  // Default task.
  grunt.registerTask('default', ['jshint', 'concat_sourcemap', 'uglify']);

};
<commit_msg>take out jshint for now, abstract src files array<commit_after>/*global module:false*/
module.exports = function(grunt) {

  var srcFiles = [
    'src/namespace.js',
    'src/modules/Util.js',
    'bower_components/shout/shout.js',
    'src/modules/GameState.js',
    'src/modules/GameEngine.js',
    'src/modules/StartScreen.js',
    'src/entities/Score.js',
    'src/entities/Timer.js',
    'src/entities/StarField.js',
    'src/entities/Player.js',
    'src/entities/EnemyShip.js',
    'src/entities/Asteroid.js',
    'src/app.js'
  ];

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
      '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
      '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
      '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
      ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',
    concat_sourcemap: {
      options: {
        sourcesContent: true
      },
      target: {
        files: {
          'dist/FooFighter.js': srcFiles
        }
      }
    },
    uglify: {
      options: {
        banner: '<%= banner %>',
        sourceMap: 'dist/FooFighter.min.map',
        sourceMappingURL: 'dist/FooFighter.min.map',
        sourceMapPrefix: 1
      },
      dist: {
        src: 'dist/FooFighter.js',
        dest: 'dist/FooFighter.min.js'
      }
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        unused: true,
        boss: true,
        eqnull: true,
        browser: true,
        globals: {}
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      game: {
        src: srcFiles
      }
    },
    watch: {
      gruntfile: {
        files: ['src/**/*.js','src/*.js'],
        tasks: ['jshint:gruntfile']
      }
    },
    connect: {
      server: {
        options: {
          port: 8000,
          base: './',
          keepalive: true
        }
      }
  }
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-concat-sourcemap');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-connect');

  // Default task.
  grunt.registerTask('default', ['concat_sourcemap', 'uglify']);

};
<|endoftext|>