<commit_before>'use strict';

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

module.exports = function(grunt) {

    // Load grunt tasks automatically
    require('load-grunt-tasks')(grunt);

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

    // Define the configuration for all the tasks
    grunt.initConfig({

        // Project settings
        yeoman: {
            // Configurable paths
            app: 'app',
            dist: 'dist'
        },

        // The actual grunt server settings
        connect: {
            options: {
                port: 9000,
                livereload: 35729,
                // Change this to '0.0.0.0' to access the server from outside
                hostname: 'localhost'
            },
            livereload: {
                options: {
                    open: true,
                    base: ['.tmp', '<%%= yeoman.app %>']
                }
            },
            test: {
                options: {
                    hostname: 'localhost',
                    port: 9001,
                    base: ['.tmp', 'test', '<%%= yeoman.app %>']
                }
            },
            dist: {
                options: {
                    open: true,
                    base: '<%%= yeoman.dist %>',
                    livereload: false
                }
            }
        },

        // Watches files for changes and runs tasks based on the changed files
        watch: {
            js: {
                files: ['<%%= yeoman.app %>/src/{,*/}*.js'],
                tasks: ['jshint'],
                options: { livereload: true }
            },
            requirejsConfig: {
                files: ['<%%= yeoman.app %>/src/config.js'],
                tasks: ['requirejs-config-copy'],
            },
            jstest: {
                files: ['test/spec/{,*/}*.js'],
                tasks: ['test:watch']
            },
            //uncomment this task, if you want to run karma testing everytime the scripts are changing
            //karma: {
                //files: ['app/src/**/*.js'],
                //tasks: ['karma:unit']
            //},
            gruntfile: { files: ['Gruntfile.js'] },
            compass: {
                files: ['<%%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
                tasks: ['compass:server', 'autoprefixer', 'concat']
            },
            styles: {
                files: ['<%%= yeoman.app %>/styles/{,*/}*.css'],
                tasks: ['newer:copy:styles', 'autoprefixer']
            },
            livereload: {
                options: { livereload: '<%%= connect.options.livereload %>' },
                files: [
                    '<%%= yeoman.app %>/{,*/}*.html', // index.html, 404.html, etc
                    '<%%= yeoman.app %>/src/{,*/}*.html', // arrange code by modules
                    '.tmp/styles/{,*/}*.css',
                    '<%%= yeoman.app %>/images/{,*/}*.{gif,jpeg,jpg,png,svg,webp}'
                ]
            }
        },

        // grunt task for running shell command
        shell: {
            protractor: {
                options: { stdout: true },
                command: 'protractor config/e2e.conf.js'
            }
        },

        // Empties folders to start fresh
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: ['.tmp', '<%%= yeoman.dist %>/*', '!<%%= yeoman.dist %>/.git*']
                }]
            },
            afterBuild: {
                files: [{
                    dot: true,
                    src: [
                        '<%%= yeoman.dist %>/src/*.js', // remove config.js
                        '<%%= yeoman.dist %>/src/**/*.spec.js', // remove all test files
                        '!<%%= yeoman.dist %>/src/main.js' // keep main.js file
                    ]
                }]
            },
            server: '.tmp'
        },

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

        // requirejs task
        requirejs: {
            dist: {
                options: {
                    dir: "<%%= yeoman.dist %>/src/",
                    baseUrl: '<%%= yeoman.app %>/src', // Directory to look for the require configuration file
                    mainConfigFile: '<%%= yeoman.app %>/src/config.js', // This is relative to the grunt file
                    modules: [{ name: 'main' }], // create a global bundle
                    preserveLicenseComments: false, // remove all comments
                    removeCombined: true, // remove files which aren't in bundles
                    optimize: 'uglify', // minify bundles with uglify 2
                    useStrict: true
                }
            }
        },

        // karma testing
        karma: {
            unit: {
                configFile: 'config/karma.conf.js'
            }
        },<% if (testFramework === 'jasmine') { %>

        // Jasmine testing framework configuration options
        jasmine: {
            pivotal: {
                src: '<%%= yeoman.app %>/src/**/*.js',
                options: {
                    specs: 'test/specs/*.spec.js',
                    helpers: 'test/specs/*.helper.js'
                }
            }
        },<% } else { %>

        // Mocha tesing framework configuration options
        mocha: {
            all: {
                options: {
                    run: true,
                    urls: ['http://<%%= connect.test.options.hostname %>:<%%= connect.test.options.port %>/index.html']
                }
            }
        },<% } %>

        // 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 %>/src',
                fontsDir: '<%%= yeoman.app %>/styles/fonts',
                importPath: '<%%= yeoman.app %>/bower_components',
                httpImagesPath: '/images',
                httpGeneratedImagesPath: '/images/generated',
                httpFontsPath: '/styles/fonts',
                relativeAssets: false,
                assetCacheBuster: false
            },
            dist: { options: { generatedImagesDir: '<%%= yeoman.dist %>/images/generated' } },
            server: { options: { debugInfo: true } }
        },

        // Add vendor prefixed styles
        autoprefixer: {
            options: { browsers: ['last 1 version'] },
            dist: {
                files: [{
                    expand: true,
                    cwd: '.tmp/styles/',
                    src: '{,*/}*.',
                    app: {
                        html: '<%%= yeoman.app %>/index.html',
                        ignorePath: '<%= yeoman.app %>/'
                    },
                    dest: '.tmp/styles/'
                }]
            }
        },

        // Automatically inject Bower components into the HTML file
        'bower-install': {
            app: {
                html: '<%%= yeoman.app %>/index.html',
                ignorePath: '<%%= yeoman.app %>/'
            }
        },

        // bundle script for bower_components into app/src/main.js
        bower: { target: { rjsConfig: '<%%= yeoman.app %>/src/config.js' } },

        // Renames files for browser caching purposes
        rev: {
            dist: {
                files: {
                    src: [
                        '<%%= yeoman.dist %>/styles/{,*/}*.css',
                        '<%%= yeoman.dist %>/images/{,*/}*.{gif,jpeg,jpg,png,webp}',
                        '<%%= 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: {
            options: { dest: '<%%= yeoman.dist %>' },
            html: '<%%= yeoman.app %>/index.html'
        },

        // Performs rewrites based on rev and the useminPrepare configuration
        usemin: {
            options: { assetsDirs: ['<%%= yeoman.dist %>'] },
            html: ['<%%= yeoman.dist %>/{,*/}*.html'],
            css: ['<%%= yeoman.dist %>/styles/{,*/}*.css']
        },

        // The following *-min tasks produce minified files in the dist folder
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/images',
                    src: '{,*/}*.{gif,jpeg,jpg,png}',
                    dest: '<%%= yeoman.dist %>/images'
                }],
                options: { cache: false }
            }
        },

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

        // html minify
        htmlmin: {
            dist: {
                options: {
                    collapseBooleanAttributes: true,
                    collapseWhitespace: true,
                    removeAttributeQuotes: true,
                    removeCommentsFromCDATA: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true
                },
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.dist %>',
                    src: '**/*.html',
                    dest: '<%%= yeoman.dist %>'
                }]
            }
        },

        // By default, your `index.html`'s <!-- Usemin block --> will take care of
        // minification. These next options are pre-configured if you do not wish
        // to use the Usemin blocks.
        cssmin: {
            dist: {
                files: { '<%%= yeoman.dist %>/styles/style.css': [ '.tmp/styles/{,*/}*.css' ] }
            }
        },

        uglify: {
            dist: {
                files: [{
                    src: '<%%= yeoman.app %>/src/**/*.js', // source files mask
                    dest: '<%%= yeoman.dist %>/src/', // destination folder
                    expand: true, // allow dynamic building
                    flatten: true // remove all unnecessary nesting
                }]
            }
        },

        concat: {
            dist: {
                src: ['.tmp/styles/{,*/}*.css'],
                dest: '.tmp/styles/style.css'
            }
        },

        // 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',
                        'images/{,*/}*.webp',
                        '{,*/}*.html',
                        'styles/fonts/{,*/}*.*',
                        'bower_components/sass-bootstrap/fonts/*.*',
                        'bower_components/font-awesome/fonts/*.*'
                    ]
                }]
            },
            styles: {
                expand: true,
                dot: true,
                cwd: '<%%= yeoman.app %>/styles',
                dest: '.tmp/styles/',
                src: '{,*/}*.css'
            }
        },

        // Generates a custom Modernizr build that includes only the tests you
        // reference in your app
        modernizr: {
            dist: {
                devFile: '<%%= yeoman.app %>/bower_components/modernizr/modernizr.js',
                outputFile: '<%%= yeoman.dist %>/src/vendor/modernizr.js',
                files: {
                    src: [
                        '<%%= yeoman.dist %>/scr/{,*/}*.js',
                        '<%%= yeoman.dist %>/styles/{,*/}*.css',
                        '!<%%= yeoman.dist %>/src/vendor/*'
                    ]
                },
                uglify: true
            }
        },

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

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

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

    grunt.registerTask('server', function() {
        grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
        grunt.task.run(['serve']);
    });

    grunt.registerTask('test', function(target) {
        if (target !== 'watch') {
            grunt.task.run([
                'clean:server',
                'concurrent:test',
                'autoprefixer',
            ]);
        }

        grunt.task.run([
            'connect:test',<% if (testFramework === 'mocha') { %>
            'mocha'<% } else if (testFramework === 'jasmine') { %>
            'jasmine'<% } %>
        ]);
    });

    //function for process requirejs file
    function replaceBetween(string, start, end, what) {
        return string.substring(0, start) + what + string.substring(end);
    };

    grunt.registerTask('requirejs-bundle', function() {
        var indexHTML = grunt.file.read('dist/index.html');
        //remove config file
        indexHTML = replaceBetween(indexHTML,
            indexHTML.indexOf('<!--build tag-->'),
            indexHTML.indexOf('<!--end build tag-->') + '<!--end build tag-->'.length, '');
        grunt.file.write('dist/index.html', indexHTML);
    });

    grunt.registerTask('requirejs-config-copy', function() {
        var mainRequireJs = grunt.file.read('app/src/config.js');
        var configurations = mainRequireJs.substring(
            mainRequireJs.indexOf('paths'),
            mainRequireJs.indexOf('/*--requirejs config copy tag--do not remove*/')
        ) + ',';
        var testMainRequireJs = grunt.file.read('test/test-main.js.template');
        var tmpIndex = testMainRequireJs.indexOf('// configurations');
        testMainRequireJs = replaceBetween(testMainRequireJs, tmpIndex, tmpIndex + '// configurations'.length, configurations);
        grunt.file.write('test/test-main.js', testMainRequireJs);
    });

    grunt.registerTask('build', [
        'clean:dist',
        'useminPrepare',
        'concurrent:dist',
        'autoprefixer',
        'cssmin',
        'requirejs',
        'clean:afterBuild',
        // 'uglify', // this task is not required for the reason that grunt-requirejs already minified the files
        'copy:dist',
        'requirejs-bundle',
        //'modernizr', // this will be done in the future
        // 'rev', // turns on this task if you want to use revision
        'usemin',
        'htmlmin'
    ]);

    grunt.registerTask('default', [
        'newer:jshint',
        'test',
        'build'
    ]);
};
<commit_msg>edit grunt to reload when src/**/*.html changed<commit_after>'use strict';

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

module.exports = function(grunt) {

    // Load grunt tasks automatically
    require('load-grunt-tasks')(grunt);

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

    // Define the configuration for all the tasks
    grunt.initConfig({

        // Project settings
        yeoman: {
            // Configurable paths
            app: 'app',
            dist: 'dist'
        },

        // The actual grunt server settings
        connect: {
            options: {
                port: 9000,
                livereload: 35729,
                // Change this to '0.0.0.0' to access the server from outside
                hostname: 'localhost'
            },
            livereload: {
                options: {
                    open: true,
                    base: ['.tmp', '<%%= yeoman.app %>']
                }
            },
            test: {
                options: {
                    hostname: 'localhost',
                    port: 9001,
                    base: ['.tmp', 'test', '<%%= yeoman.app %>']
                }
            },
            dist: {
                options: {
                    open: true,
                    base: '<%%= yeoman.dist %>',
                    livereload: false
                }
            }
        },

        // Watches files for changes and runs tasks based on the changed files
        watch: {
            js: {
                files: ['<%%= yeoman.app %>/src/{,*/}*.js'],
                tasks: ['jshint'],
                options: { livereload: true }
            },
            requirejsConfig: {
                files: ['<%%= yeoman.app %>/src/config.js'],
                tasks: ['requirejs-config-copy'],
            },
            jstest: {
                files: ['test/spec/{,*/}*.js'],
                tasks: ['test:watch']
            },
            //uncomment this task, if you want to run karma testing everytime the scripts are changing
            //karma: {
                //files: ['app/src/**/*.js'],
                //tasks: ['karma:unit']
            //},
            gruntfile: { files: ['Gruntfile.js'] },
            compass: {
                files: ['<%%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
                tasks: ['compass:server', 'autoprefixer', 'concat']
            },
            styles: {
                files: ['<%%= yeoman.app %>/styles/{,*/}*.css'],
                tasks: ['newer:copy:styles', 'autoprefixer']
            },
            livereload: {
                options: { livereload: '<%%= connect.options.livereload %>' },
                files: [
                    '<%%= yeoman.app %>/{,*/}*.html', // index.html, 404.html, etc
                    '<%%= yeoman.app %>/src/**/*.html', // arrange code by modules
                    '.tmp/styles/{,*/}*.css',
                    '<%%= yeoman.app %>/images/{,*/}*.{gif,jpeg,jpg,png,svg,webp}'
                ]
            }
        },

        // grunt task for running shell command
        shell: {
            protractor: {
                options: { stdout: true },
                command: 'protractor config/e2e.conf.js'
            }
        },

        // Empties folders to start fresh
        clean: {
            dist: {
                files: [{
                    dot: true,
                    src: ['.tmp', '<%%= yeoman.dist %>/*', '!<%%= yeoman.dist %>/.git*']
                }]
            },
            afterBuild: {
                files: [{
                    dot: true,
                    src: [
                        '<%%= yeoman.dist %>/src/*.js', // remove config.js
                        '<%%= yeoman.dist %>/src/**/*.spec.js', // remove all test files
                        '!<%%= yeoman.dist %>/src/main.js' // keep main.js file
                    ]
                }]
            },
            server: '.tmp'
        },

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

        // requirejs task
        requirejs: {
            dist: {
                options: {
                    dir: "<%%= yeoman.dist %>/src/",
                    baseUrl: '<%%= yeoman.app %>/src', // Directory to look for the require configuration file
                    mainConfigFile: '<%%= yeoman.app %>/src/config.js', // This is relative to the grunt file
                    modules: [{ name: 'main' }], // create a global bundle
                    preserveLicenseComments: false, // remove all comments
                    removeCombined: true, // remove files which aren't in bundles
                    optimize: 'uglify', // minify bundles with uglify 2
                    useStrict: true
                }
            }
        },

        // karma testing
        karma: {
            unit: {
                configFile: 'config/karma.conf.js'
            }
        },<% if (testFramework === 'jasmine') { %>

        // Jasmine testing framework configuration options
        jasmine: {
            pivotal: {
                src: '<%%= yeoman.app %>/src/**/*.js',
                options: {
                    specs: 'test/specs/*.spec.js',
                    helpers: 'test/specs/*.helper.js'
                }
            }
        },<% } else { %>

        // Mocha tesing framework configuration options
        mocha: {
            all: {
                options: {
                    run: true,
                    urls: ['http://<%%= connect.test.options.hostname %>:<%%= connect.test.options.port %>/index.html']
                }
            }
        },<% } %>

        // 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 %>/src',
                fontsDir: '<%%= yeoman.app %>/styles/fonts',
                importPath: '<%%= yeoman.app %>/bower_components',
                httpImagesPath: '/images',
                httpGeneratedImagesPath: '/images/generated',
                httpFontsPath: '/styles/fonts',
                relativeAssets: false,
                assetCacheBuster: false
            },
            dist: { options: { generatedImagesDir: '<%%= yeoman.dist %>/images/generated' } },
            server: { options: { debugInfo: true } }
        },

        // Add vendor prefixed styles
        autoprefixer: {
            options: { browsers: ['last 1 version'] },
            dist: {
                files: [{
                    expand: true,
                    cwd: '.tmp/styles/',
                    src: '{,*/}*.',
                    app: {
                        html: '<%%= yeoman.app %>/index.html',
                        ignorePath: '<%= yeoman.app %>/'
                    },
                    dest: '.tmp/styles/'
                }]
            }
        },

        // Automatically inject Bower components into the HTML file
        'bower-install': {
            app: {
                html: '<%%= yeoman.app %>/index.html',
                ignorePath: '<%%= yeoman.app %>/'
            }
        },

        // bundle script for bower_components into app/src/main.js
        bower: { target: { rjsConfig: '<%%= yeoman.app %>/src/config.js' } },

        // Renames files for browser caching purposes
        rev: {
            dist: {
                files: {
                    src: [
                        '<%%= yeoman.dist %>/styles/{,*/}*.css',
                        '<%%= yeoman.dist %>/images/{,*/}*.{gif,jpeg,jpg,png,webp}',
                        '<%%= 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: {
            options: { dest: '<%%= yeoman.dist %>' },
            html: '<%%= yeoman.app %>/index.html'
        },

        // Performs rewrites based on rev and the useminPrepare configuration
        usemin: {
            options: { assetsDirs: ['<%%= yeoman.dist %>'] },
            html: ['<%%= yeoman.dist %>/{,*/}*.html'],
            css: ['<%%= yeoman.dist %>/styles/{,*/}*.css']
        },

        // The following *-min tasks produce minified files in the dist folder
        imagemin: {
            dist: {
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.app %>/images',
                    src: '{,*/}*.{gif,jpeg,jpg,png}',
                    dest: '<%%= yeoman.dist %>/images'
                }],
                options: { cache: false }
            }
        },

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

        // html minify
        htmlmin: {
            dist: {
                options: {
                    collapseBooleanAttributes: true,
                    collapseWhitespace: true,
                    removeAttributeQuotes: true,
                    removeCommentsFromCDATA: true,
                    removeEmptyAttributes: true,
                    removeOptionalTags: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true
                },
                files: [{
                    expand: true,
                    cwd: '<%%= yeoman.dist %>',
                    src: '**/*.html',
                    dest: '<%%= yeoman.dist %>'
                }]
            }
        },

        // By default, your `index.html`'s <!-- Usemin block --> will take care of
        // minification. These next options are pre-configured if you do not wish
        // to use the Usemin blocks.
        cssmin: {
            dist: {
                files: { '<%%= yeoman.dist %>/styles/style.css': [ '.tmp/styles/{,*/}*.css' ] }
            }
        },

        uglify: {
            dist: {
                files: [{
                    src: '<%%= yeoman.app %>/src/**/*.js', // source files mask
                    dest: '<%%= yeoman.dist %>/src/', // destination folder
                    expand: true, // allow dynamic building
                    flatten: true // remove all unnecessary nesting
                }]
            }
        },

        concat: {
            dist: {
                src: ['.tmp/styles/{,*/}*.css'],
                dest: '.tmp/styles/style.css'
            }
        },

        // 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',
                        'images/{,*/}*.webp',
                        '{,*/}*.html',
                        'styles/fonts/{,*/}*.*',
                        'bower_components/sass-bootstrap/fonts/*.*',
                        'bower_components/font-awesome/fonts/*.*'
                    ]
                }]
            },
            styles: {
                expand: true,
                dot: true,
                cwd: '<%%= yeoman.app %>/styles',
                dest: '.tmp/styles/',
                src: '{,*/}*.css'
            }
        },

        // Generates a custom Modernizr build that includes only the tests you
        // reference in your app
        modernizr: {
            dist: {
                devFile: '<%%= yeoman.app %>/bower_components/modernizr/modernizr.js',
                outputFile: '<%%= yeoman.dist %>/src/vendor/modernizr.js',
                files: {
                    src: [
                        '<%%= yeoman.dist %>/scr/{,*/}*.js',
                        '<%%= yeoman.dist %>/styles/{,*/}*.css',
                        '!<%%= yeoman.dist %>/src/vendor/*'
                    ]
                },
                uglify: true
            }
        },

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

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

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

    grunt.registerTask('server', function() {
        grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
        grunt.task.run(['serve']);
    });

    grunt.registerTask('test', function(target) {
        if (target !== 'watch') {
            grunt.task.run([
                'clean:server',
                'concurrent:test',
                'autoprefixer',
            ]);
        }

        grunt.task.run([
            'connect:test',<% if (testFramework === 'mocha') { %>
            'mocha'<% } else if (testFramework === 'jasmine') { %>
            'jasmine'<% } %>
        ]);
    });

    //function for process requirejs file
    function replaceBetween(string, start, end, what) {
        return string.substring(0, start) + what + string.substring(end);
    };

    grunt.registerTask('requirejs-bundle', function() {
        var indexHTML = grunt.file.read('dist/index.html');
        //remove config file
        indexHTML = replaceBetween(indexHTML,
            indexHTML.indexOf('<!--build tag-->'),
            indexHTML.indexOf('<!--end build tag-->') + '<!--end build tag-->'.length, '');
        grunt.file.write('dist/index.html', indexHTML);
    });

    grunt.registerTask('requirejs-config-copy', function() {
        var mainRequireJs = grunt.file.read('app/src/config.js');
        var configurations = mainRequireJs.substring(
            mainRequireJs.indexOf('paths'),
            mainRequireJs.indexOf('/*--requirejs config copy tag--do not remove*/')
        ) + ',';
        var testMainRequireJs = grunt.file.read('test/test-main.js.template');
        var tmpIndex = testMainRequireJs.indexOf('// configurations');
        testMainRequireJs = replaceBetween(testMainRequireJs, tmpIndex, tmpIndex + '// configurations'.length, configurations);
        grunt.file.write('test/test-main.js', testMainRequireJs);
    });

    grunt.registerTask('build', [
        'clean:dist',
        'useminPrepare',
        'concurrent:dist',
        'autoprefixer',
        'cssmin',
        'requirejs',
        'clean:afterBuild',
        // 'uglify', // this task is not required for the reason that grunt-requirejs already minified the files
        'copy:dist',
        'requirejs-bundle',
        //'modernizr', // this will be done in the future
        // 'rev', // turns on this task if you want to use revision
        'usemin',
        'htmlmin'
    ]);

    grunt.registerTask('default', [
        'newer:jshint',
        'test',
        'build'
    ]);
};
<|endoftext|>


<commit_before>var Q = require("q");

var LogTypeMap = {};
LogTypeMap[100] = {type: "Added Inventory", callFunction:toStringAddInventory};
LogTypeMap[800] = {type: "Created User", callFunction:toStringCreatedUser};
LogTypeMap[900] = {type: "temp", callFunction:toStringDefault};

var stringLogs = [];

function toStringDefault (LogType, logUsername,  time,  actionData) {
    return time + " - " + LogTypeMap[LogType].type;
}

function toStringCreatedUser (LogType, logUsername,  time,  actionData) {
    return time + " - " + logUsername + ": " + "Created new user " + actionData.value;
}

function toStringAddInventory (LogType, logUsername, time, actionData) {
    return time + " - " + logUsername + ": " + "Added " + actionData.quantity + " units of product " + actionData.productId + " to location " + actionData.location;
}

module.exports =
{
    _verifyKey: function (key) {
        return LogTypeMap[key] == undefined ? false : true;
    },

    displayLogs: function (cookie, callback) {

        var db = require("../imp_services/impdb.js").connect();

        require("../imp_services/impredis.js").get(cookie, function usernameReturn(error, val)
        {
            var username = val.username;

            return Q.fcall(db.beginTransaction())
                .then(db.query("USE " + db.databaseName))
                .then(db.query("CALL GetLogsUserView(\'" + username+ "\');"))
                .then(function (rows) {

                    if (rows[0][0].length == 0) { // No user by that username
                        return "Invalid Result!";
                    }

                    for (var i = 0; i < rows[0][0].length; i++) {
                        var row = rows[0][0][i];
                        var logID = row.LogID;
                        var LogType = row.LogType;
                        var logUsername = row.Username;
                        var time = row.Time;
                        var actionData = row.ActionData;

                        stringLogs.push(LogTypeMap[LogType].callFunction(LogType, logUsername, time, JSON.parse(actionData)));
                        console.log(stringLogs[i]);
                    }

                    var jsonObject = {logs:stringLogs};

                    callback(JSON.stringify(jsonObject));

                })
                .then(db.commit())
                .then(db.endTransaction())
                .catch(function (err) {
                    Q.fcall(db.rollback())
                        .then(db.endTransaction());
                    console.log("We had an error");
                    console.log("Error: " + err);
                })
                .done();
        })
    }
};<commit_msg>edited style formatting<commit_after>var Q = require("q");

var LogTypeMap = {};
LogTypeMap[100] = {
    type: "Added Inventory",
    callFunction: function (LogType, logUsername,  time,  actionData) {
        return time + " - " + logUsername + ": " + "Added " + actionData.quantity + " units of product " + actionData.productId + " to location " + actionData.location;
    }
};
LogTypeMap[800] = {
    type: "Created User",
    callFunction: function (LogType, logUsername, time, actionData) {
        return time + " - " + logUsername + ": " + "Created new user " + actionData.value;
    }
};
LogTypeMap[900] = {
    type: "temp",
    callFunction:toStringDefault
};

var stringLogs = [];

function toStringDefault (LogType, logUsername,  time,  actionData) {
    return time + " - " + LogTypeMap[LogType].type;
}

module.exports =
{
    _verifyKey: function (key) {
        return LogTypeMap[key] == undefined ? false : true;
    },

    displayLogs: function (cookie, callback) {

        var db = require("../imp_services/impdb.js").connect();

        require("../imp_services/impredis.js").get(cookie, function usernameReturn(error, val)
        {
            var username = val.username;

            return Q.fcall(db.beginTransaction())
                .then(db.query("USE " + db.databaseName))
                .then(db.query("CALL GetLogsUserView(\'" + username+ "\');"))
                .then(function (rows) {

                    if (rows[0][0].length == 0) { // No user by that username
                        return "Invalid Result!";
                    }

                    for (var i = 0; i < rows[0][0].length; i++) {
                        var row = rows[0][0][i];
                        var logID = row.LogID;
                        var LogType = row.LogType;
                        var logUsername = row.Username;
                        var time = row.Time;
                        var actionData = row.ActionData;

                        stringLogs.push(LogTypeMap[LogType].callFunction(LogType, logUsername, time, JSON.parse(actionData)));
                        console.log(stringLogs[i]);
                    }

                    var jsonObject = {logs:stringLogs};

                    callback(JSON.stringify(jsonObject));

                })
                .then(db.commit())
                .then(db.endTransaction())
                .catch(function (err) {
                    Q.fcall(db.rollback())
                        .then(db.endTransaction());
                    console.log("We had an error");
                    console.log("Error: " + err);
                })
                .done();
        })
    }
};<|endoftext|>


<commit_before>var Florence = Florence || {
    tredegarBaseUrl: window.location.origin,
    refreshAdminMenu: function () {
      var mainNavHtml = templates.mainNav(Florence);
      $('.admin-nav').html(mainNavHtml);
    },
    setActiveCollection: function (collection) {
      document.cookie = "collection=" + collection.id + ";path=/";
      if (!collection.publishDate) {
        var formattedDate = null;
      } else {
        var formattedDate = StringUtils.formatIsoDateString(collection.publishDate);
      }
      Florence.collection = {id: collection.id, name: collection.name, date: formattedDate};
    }
  };

Florence.Editor = {
  isDirty: false,
  data: {}
};

Florence.collection = {};

Florence.collectionToPublish = {};

Florence.globalVars = {pagePath: '', activeTab: false, pagePos: ''};

Florence.Authentication = {
  accessToken: function () {
    return CookieUtils.getCookieValue("access_token");
  },
  isAuthenticated: function () {
    return Florence.Authentication.accessToken() !== ''
  }
};

Florence.Handler = function () {
  if (Florence.Editor.isDirty) {
    var result = confirm("You have unsaved changes. Are you sure you want to continue");
    if (result === true) {
      Florence.Editor.isDirty = false;
      processPreviewClick(this);
      return true;
    } else {
      return false;
    }
  } else {
    processPreviewClick(this);
  }

  function processPreviewClick() {
    setTimeout(function () {
      checkForPageChanged(function (newUrl) {
        var browserLocation = document.getElementById('iframe').contentWindow.location.href;
        $('.browser-location').val(browserLocation);
        if ($('.workspace-edit').length) {
          loadPageDataIntoEditor(newUrl, Florence.collection.id);
        }
        else if ($('.workspace-browse').length) {
          treeNodeSelect(newUrl);
        }
      });
    }, 200);

    console.log('iframe inner clicked');
  }
};

// if running in a node environment export this as a module.
if (typeof module !== 'undefined') {
  module.exports = Florence;
}

<commit_msg>Make safe paths<commit_after>var Florence = Florence || {
    tredegarBaseUrl: window.location.origin,
    refreshAdminMenu: function () {
      var mainNavHtml = templates.mainNav(Florence);
      $('.admin-nav').html(mainNavHtml);
    },
    setActiveCollection: function (collection) {
      document.cookie = "collection=" + collection.id + ";path=/";
      if (!collection.publishDate) {
        var formattedDate = null;
      } else {
        var formattedDate = StringUtils.formatIsoDateString(collection.publishDate);
      }
      Florence.collection = {id: collection.id, name: collection.name, date: formattedDate};
    }
  };

Florence.Editor = {
  isDirty: false,
  data: {}
};

Florence.collection = {};

Florence.collectionToPublish = {};

Florence.globalVars = {pagePath: '', activeTab: false, pagePos: ''};

Florence.Authentication = {
  accessToken: function () {
    return CookieUtils.getCookieValue("access_token");
  },
  isAuthenticated: function () {
    return Florence.Authentication.accessToken() !== ''
  }
};

Florence.Handler = function () {
  if (Florence.Editor.isDirty) {
    var result = confirm("You have unsaved changes. Are you sure you want to continue");
    if (result === true) {
      Florence.Editor.isDirty = false;
      processPreviewClick(this);
      return true;
    } else {
      return false;
    }
  } else {
    processPreviewClick(this);
  }

  function processPreviewClick() {
    setTimeout(function () {
      checkForPageChanged(function (newUrl) {
        var safeUrl = checkPathSlashes(newUrl);
        var browserLocation = document.getElementById('iframe').contentWindow.location.href;
        $('.browser-location').val(browserLocation);
        if ($('.workspace-edit').length) {
          loadPageDataIntoEditor(safeUrl, Florence.collection.id);
        }
        else if ($('.workspace-browse').length) {
          treeNodeSelect(safeUrl);
        }
      });
    }, 200);

    console.log('iframe inner clicked');
  }
};

// if running in a node environment export this as a module.
if (typeof module !== 'undefined') {
  module.exports = Florence;
}

<|endoftext|>


<commit_before>/*
 * Copyright 2015 Open Networking Laboratory
 *
 * 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.
 */

/*
 ONOS GUI -- Widget -- Table Service - Unit Tests
 */
describe('factory: fw/widget/table.js', function () {
    var $log, $compile, $rootScope,
        fs, mast, is,
        scope, compiled,
        table, thead, tbody, mockHeader,
        mockh2Height = '10px',
        tableIconTdSize = 100,
        pdgTop = 101,
        numTestElems = 4;

    var onosFixedHeaderTags = '
' +
                                '
' +
                                '
' +
                                '	' +
                                'Device ID 	' +
                                'H/W Version 	' +
                                'S/W Version 	' +
                                '
' +
                                '
' +
                                '
' +
                                '
' +
                                '' +
                                    'No Devices found' +
                                '	' +
                                '
' +
                                '
' +
                                '' +
                                    '
' +
                                    '
' +
                                '	' +
                                'Some ID	' +
                                'Some HW	' +
                                'Some Software	' +
                                '
' +
                                '
' +
                                '
',

        onosSortableHeaderTags = '
' +
                                '
' +
                                '
' +
                                '	' +
                                'Device ID 	' +
                                'H/W Version 	' +
                                'S/W Version 	' +
                                '
' +
                                '
' +
                                '
' +
                                '
' +
                                '' +
                                    '
' +
                                    '
' +
                                '	' +
                                'Some ID	' +
                                'Some HW	' +
                                'Some Software	' +
                                '
' +
                                '
' +
                                '
';

    beforeEach(module('onosWidget', 'onosUtil', 'onosMast', 'onosSvg'));

    var mockWindow = {
        innerWidth: 400,
        innerHeight: 200,
        navigator: {
            userAgent: 'defaultUA'
        },
        on: function () {},
        addEventListener: function () {}
    };

    beforeEach(function () {
        module(function ($provide) {
            $provide.value('$window', mockWindow);
        });
    });

    beforeEach(inject(function (_$log_, _$compile_, _$rootScope_,
                                FnService, MastService, IconService) {
        $log = _$log_;
        $compile = _$compile_;
        $rootScope = _$rootScope_;
        fs = FnService;
        mast = MastService;
        is = IconService;
    }));

    beforeEach(function () {
        scope = $rootScope.$new();
    });

    beforeEach(function () {
        mockHeader = d3.select('body')
            .append('h2')
            .style('height', mockh2Height)
            .html('Some Header');
    });

    afterEach(function () {
        table = null;
        thead = null;
        tbody = null;
        mockHeader.remove();
    });

    function compileTable() {
        compiled = $compile(table);
        compiled(scope);
        scope.$digest();
    }

    function verifyGivenTags(dirName) {
        expect(table).toBeDefined();
        expect(table.attr(dirName)).toBe('');

        thead = table.find('thead');
        expect(thead).toBeDefined();
        tbody = table.find('tbody');
        expect(tbody).toBeDefined();
    }

    function verifyCssDisplay() {
        var tableHeight = fs.windowSize(pdgTop).height;

        expect(thead.css('display')).toBe('block');
        expect(tbody.css('display')).toBe('block');
        expect(tbody.css('height')).toBe(tableHeight + 'px');
        expect(tbody.css('overflow')).toBe('auto');
    }

    function verifyColWidth() {
        var winWidth = fs.windowSize().width,
            colWidth, thElems, tr, tdElem;

        colWidth = Math.floor(winWidth / numTestElems);

        thElems = thead.find('th');

        angular.forEach(thElems, function (thElem, i) {
            thElem = angular.element(thElems[i]);
            tr = angular.element(tbody.find('tr').eq(1));
            tdElem = angular.element(tr.find('td').eq(i));
            var custWidth = thElem.attr('col-width');

            if (custWidth) {
                expect(thElem.css('width')).toBe(custWidth);
                expect(tdElem.css('width')).toBe(custWidth);
            } else if (tdElem.attr('class') === 'table-icon') {
                expect(thElem.css('width')).toBe(tableIconTdSize + 'px');
                expect(tdElem.css('width')).toBe(tableIconTdSize + 'px');
            } else {
                expect(thElem.css('width')).toBe(colWidth + 'px');
                expect(tdElem.css('width')).toBe(colWidth + 'px');
            }
        });
    }

    function verifyCallbacks(thElems) {
        expect(scope.sortCallback).not.toHaveBeenCalled();

        // first test header has no 'sortable' attr
        thElems[0].click();
        expect(scope.sortCallback).not.toHaveBeenCalled();

        // the other headers have 'sortable'
        for(var i = 1; i < numTestElems; i += 1) {
            thElems[i].click();
            expect(scope.sortCallback).toHaveBeenCalled();
        }
    }

    function verifyIcons(thElems) {
        var currentTh, div;
        // make sure it has the correct icon after clicking
        thElems[1].click();
        currentTh = angular.element(thElems[1]);
        div = currentTh.find('div');
        expect(div.html()).toBe('<svg class="embeddedIcon" ' +
                                'width="10" height="10" viewBox="0 0 50 50">' +
                                '<g class="icon upArrow">' +
                                '<rect width="50" height="50" rx="5"></rect>' +
                                '<use width="50" height="50" class="glyph" ' +
                                'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
                                'xlink:href="#triangleUp">' +
                                '</use>' +
                                '</g></svg>');
        thElems[1].click();
        div = currentTh.find('div');
        expect(div.html()).toBe('<svg class="embeddedIcon" ' +
                                'width="10" height="10" viewBox="0 0 50 50">' +
                                '<g class="icon downArrow">' +
                                '<rect width="50" height="50" rx="5"></rect>' +
                                '<use width="50" height="50" class="glyph" ' +
                                'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
                                'xlink:href="#triangleDown">' +
                                '</use>' +
                                '</g></svg>');

        thElems[2].click();
        div = currentTh.children();
        // clicked on a new element, so the previous icon should have been deleted
        expect(div.html()).toBeFalsy();

        // the new element should have the ascending icon
        currentTh = angular.element(thElems[2]);
        div = currentTh.children();
        expect(div.html()).toBe('<svg class="embeddedIcon" ' +
                                'width="10" height="10" viewBox="0 0 50 50">' +
                                '<g class="icon upArrow">' +
                                '<rect width="50" height="50" rx="5"></rect>' +
                                '<use width="50" height="50" class="glyph" ' +
                                'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
                                'xlink:href="#triangleUp">' +
                                '</use>' +
                                '</g></svg>');
    }

    it('should affirm that onos-fixed-header is working', function () {
        table = angular.element(onosFixedHeaderTags);

        compileTable();
        verifyGivenTags('onos-fixed-header');

        // table will not be fixed unless it receives the 'LastElement' event
        scope.$emit('LastElement');
        scope.$digest();

        verifyCssDisplay();
        verifyColWidth();
    });

    it('should affirm that onos-sortable-header is working', function () {
        var thElems;
        table = angular.element(onosSortableHeaderTags);

        compileTable();
        verifyGivenTags('onos-sortable-header');
        // ctrlCallback functionality is tested in device-spec
        // only checking that it has been called correctly in the directive
        scope.sortCallback = jasmine.createSpy('sortCallback');

        thElems = thead.find('th');
        verifyCallbacks(thElems);
        verifyIcons(thElems);
    });

});
<commit_msg>GUI -- Fixed broken table unit tests.<commit_after>/*
 * Copyright 2015 Open Networking Laboratory
 *
 * 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.
 */

/*
 ONOS GUI -- Widget -- Table Service - Unit Tests
 */
describe('factory: fw/widget/table.js', function () {
    var $log, $compile, $rootScope,
        fs, mast, is,
        scope, compiled,
        table, thead, tbody, mockHeader,
        mockH2Height = 20,
        mockMastHeight = 20,
        mockHeaderHeight = mockH2Height + mockMastHeight,
        tableIconTdSize = 100,
        numTestElems = 4;

    var onosFixedHeaderTags =
            '
' +
            '
' +
            '
' +
            '	' +
            'Device ID 	' +
            'H/W Version 	' +
            'S/W Version 	' +
            '
' +
            '
' +
            '
' +
            '
' +
            '' +
                'No Devices found' +
            '	' +
            '
' +
            '
' +
            '' +
                '
' +
                '
' +
            '	' +
            'Some ID	' +
            'Some HW	' +
            'Some Software	' +
            '
' +
            '
' +
            '
',

        onosSortableHeaderTags =
            '
' +
            '
' +
            '
' +
            '	' +
            'Device ID 	' +
            'H/W Version 	' +
            'S/W Version 	' +
            '
' +
            '
' +
            '
' +
            '
' +
            '' +
                '
' +
                '
' +
            '	' +
            'Some ID	' +
            'Some HW	' +
            'Some Software	' +
            '
' +
            '
' +
            '
';

    beforeEach(module('onosWidget', 'onosUtil', 'onosMast', 'onosSvg'));

    var mockWindow = {
        innerWidth: 400,
        innerHeight: 200,
        navigator: {
            userAgent: 'defaultUA'
        },
        on: function () {},
        addEventListener: function () {}
    };

    beforeEach(function () {
        module(function ($provide) {
            $provide.value('$window', mockWindow);
        });
    });

    beforeEach(inject(function (_$log_, _$compile_, _$rootScope_,
                                FnService, MastService, IconService) {
        $log = _$log_;
        $compile = _$compile_;
        $rootScope = _$rootScope_;
        fs = FnService;
        mast = MastService;
        is = IconService;
    }));

    beforeEach(function () {
        scope = $rootScope.$new();
    });

    beforeEach(function () {
        mockHeader = d3.select('body')
            .append('h2')
            .classed('tabular-header', true)
            .style('height', mockHeaderHeight + 'px')
            .html('Some Header');
    });

    afterEach(function () {
        table = null;
        thead = null;
        tbody = null;
        mockHeader.remove();
    });

    function compileTable() {
        compiled = $compile(table);
        compiled(scope);
        scope.$digest();
    }

    function verifyGivenTags(dirName) {
        expect(table).toBeDefined();
        expect(table.attr(dirName)).toBe('');

        thead = table.find('thead');
        expect(thead).toBeDefined();
        tbody = table.find('tbody');
        expect(tbody).toBeDefined();
    }

    function verifyCssDisplay() {
        var padding = 21, // bottom table constant 12 + mastPadding(?) 9
            tableHeight = fs.windowSize(mockHeaderHeight).height -
                (fs.noPx(table.find('thead').css('height')) + padding);

        expect(thead.css('display')).toBe('block');
        expect(tbody.css('display')).toBe('block');
        expect(tbody.css('height')).toBe(tableHeight + 'px');
        expect(tbody.css('overflow')).toBe('auto');

        // TODO: investigate why math for calculating the height works better
        // in the browser window (thead height is 0 in this test?)
    }

    function verifyColWidth() {
        var winWidth = fs.windowSize().width,
            colWidth, thElems, tr, tdElem;

        colWidth = Math.floor(winWidth / numTestElems);

        thElems = thead.find('th');

        angular.forEach(thElems, function (thElem, i) {
            thElem = angular.element(thElems[i]);
            tr = angular.element(tbody.find('tr').eq(1));
            tdElem = angular.element(tr.find('td').eq(i));
            var custWidth = thElem.attr('col-width');

            if (custWidth) {
                expect(thElem.css('width')).toBe(custWidth);
                expect(tdElem.css('width')).toBe(custWidth);
            } else if (tdElem.attr('class') === 'table-icon') {
                expect(thElem.css('width')).toBe(tableIconTdSize + 'px');
                expect(tdElem.css('width')).toBe(tableIconTdSize + 'px');
            } else {
                expect(thElem.css('width')).toBe(colWidth + 'px');
                expect(tdElem.css('width')).toBe(colWidth + 'px');
            }
        });
    }

    function verifyCallbacks(thElems) {
        expect(scope.sortCallback).not.toHaveBeenCalled();

        // first test header has no 'sortable' attr
        thElems[0].click();
        expect(scope.sortCallback).not.toHaveBeenCalled();

        // the other headers have 'sortable'
        for(var i = 1; i < numTestElems; i += 1) {
            thElems[i].click();
            expect(scope.sortCallback).toHaveBeenCalled();
        }
    }

    function verifyIcons(thElems) {
        var currentTh, div;
        // make sure it has the correct icon after clicking
        thElems[1].click();
        currentTh = angular.element(thElems[1]);
        div = currentTh.find('div');
        expect(div.html()).toBe('<svg class="embeddedIcon" ' +
                                'width="10" height="10" viewBox="0 0 50 50">' +
                                '<g class="icon upArrow">' +
                                '<rect width="50" height="50" rx="5"></rect>' +
                                '<use width="50" height="50" class="glyph" ' +
                                'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
                                'xlink:href="#triangleUp">' +
                                '</use>' +
                                '</g></svg>');
        thElems[1].click();
        div = currentTh.find('div');
        expect(div.html()).toBe('<svg class="embeddedIcon" ' +
                                'width="10" height="10" viewBox="0 0 50 50">' +
                                '<g class="icon downArrow">' +
                                '<rect width="50" height="50" rx="5"></rect>' +
                                '<use width="50" height="50" class="glyph" ' +
                                'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
                                'xlink:href="#triangleDown">' +
                                '</use>' +
                                '</g></svg>');

        thElems[2].click();
        div = currentTh.children();
        // clicked on a new element, so the previous icon should have been deleted
        expect(div.html()).toBeFalsy();

        // the new element should have the ascending icon
        currentTh = angular.element(thElems[2]);
        div = currentTh.children();
        expect(div.html()).toBe('<svg class="embeddedIcon" ' +
                                'width="10" height="10" viewBox="0 0 50 50">' +
                                '<g class="icon upArrow">' +
                                '<rect width="50" height="50" rx="5"></rect>' +
                                '<use width="50" height="50" class="glyph" ' +
                                'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
                                'xlink:href="#triangleUp">' +
                                '</use>' +
                                '</g></svg>');
    }

    it('should affirm that onos-fixed-header is working', function () {
        table = angular.element(onosFixedHeaderTags);

        compileTable();
        verifyGivenTags('onos-fixed-header');

        // table will not be fixed unless it receives the 'LastElement' event
        scope.$emit('LastElement');
        scope.$digest();

        verifyCssDisplay();
        verifyColWidth();
    });

    it('should affirm that onos-sortable-header is working', function () {
        var thElems;
        table = angular.element(onosSortableHeaderTags);

        compileTable();
        verifyGivenTags('onos-sortable-header');
        // ctrlCallback functionality is tested in device-spec
        // only checking that it has been called correctly in the directive
        scope.sortCallback = jasmine.createSpy('sortCallback');

        thElems = thead.find('th');
        verifyCallbacks(thElems);
        verifyIcons(thElems);
    });

});
<|endoftext|>


<commit_before>/**
 * Created by lidavidm on 6/6/17.
 */

const CELL_SIZE = 1;
const PLAYER_COLORS = [0xFF704B, 0x9010B9, 0x005DD0, 0x00B553];
const PLANET_COLOR = 0x888888;

const DOCK_TURNS = 5;
const ATTACK_RADIUS = 5;
const BASE_SHIP_HEALTH = 255;

class FrameAnimation {
    constructor(frames, update, draw) {
        this.frames = frames;
        this.update = update;
        this.draw = draw;
    }
}

class HaliteVisualizer {
    constructor(replay) {
        this.replay = replay;
        this.frame = 0;
        this.substep = 0;
        // TODO: match aspect ratio of map
        this.application = new PIXI.Application(800, 900);

        this.scale = 800 / Math.max(replay.width, replay.height);

        this.planetContainer = new PIXI.Graphics();
        this.shipContainer = new PIXI.Graphics();
        this.container = new PIXI.Container();
        this.container.position.set(0, 100);
        this.container.addChild(this.planetContainer, this.shipContainer);

        this.statsDisplay = new PIXI.Graphics();

        this.currentTurnDisplay = new PIXI.Text("Frame");

        this.application.stage.addChild(this.container);
        this.application.stage.addChild(this.statsDisplay);
        this.application.stage.addChild(this.currentTurnDisplay);

        this.timer = null;

        this.animationQueue = [];
    }

    get currentSubstep() {
        return this.replay.frames[this.frame][this.substep];
    }

    get currentStatistics() {
        let substep = this.currentSubstep;
        let planets = { "unowned": 0 };
        let ships = {};
        let total_ships = 0;

        for (let planet of substep.planets) {
            if (planet.owner !== null) {
                if (typeof planets[planet.owner] === "undefined") {
                    planets[planet.owner] = 0;
                }
                planets[planet.owner]++;
            }
            else {
                planets["unowned"]++;
            }
        }

        for (let ship of substep.ships) {
            if (typeof ships[ship.owner] === "undefined") {
                ships[ship.owner] = 0;
            }
            ships[ship.owner]++;
            total_ships++;
        }

        return {
            "planets": planets,
            "ships": ships,
            "total_ships": total_ships,
        }
    }

    attach(containerEl) {
        $(containerEl).append(this.application.view);

        this.draw();
    }

    play() {
        if (this.timer) return;

        this.timer = window.setInterval(() => {
            for (let i = 0; i < 6; i++) {
                this.substep++;
                if (this.substep >= this.replay.frames[this.frame].length) {
                    this.substep = 0;
                    this.frame++;
                }

                if (this.frame >= this.replay.frames.length) {
                    this.pause();
                    this.frame = this.replay.frames.length - 1;
                    this.substep = this.replay.frames[this.frame].length - 1;
                    break;
                }

                this.update();
            }
        }, 1000/40);

        this.application.ticker.add(this.draw.bind(this));
    }

    pause() {
        if (!this.timer) return;

        window.clearInterval(this.timer);
        this.timer = null;
    }

    /**
     *
     * @param container
     * @param x
     * @param y
     * @param color
     * @param health_factor
     */
    drawCell(container, x, y, color, health_factor) {
        const side = CELL_SIZE * this.scale;
        x = x * side;
        y = y * side;
        container.lineStyle(0);
        container.beginFill(color, 0.5);
        container.drawRect(x, y, side, side);
        container.endFill();
        container.beginFill(color, 1);
        container.drawRect(
            x + health_factor * side,
            y + health_factor * side,
            (1 - 2*health_factor) * side,
            (1 - 2*health_factor) * side);
        container.endFill();
    }

    drawPlanet(planet) {
        let planetBase = this.replay.planets[planet.id];

        const side = CELL_SIZE * this.scale;

        this.planetContainer.lineStyle(0);
        for (let dx = -planetBase.r; dx <= planetBase.r; dx++) {
            for (let dy = -planetBase.r; dy <= planetBase.r; dy++) {
                if (dx*dx + dy*dy <= planetBase.r*planetBase.r) {
                    const health_factor = 0.5 *
                        (planetBase.production - planet.remaining_production) / planetBase.production;
                    this.drawCell(
                        this.planetContainer,
                        dx + planetBase.x,
                        dy + planetBase.y,
                        planet.owner === null ? PLANET_COLOR : PLAYER_COLORS[planet.owner],
                        health_factor,
                    );
                }
            }
        }

        const center_x = side * planetBase.x;
        const center_y = side * (planetBase.y + 0.5);

        const health_factor = planet.health / planetBase.health;
        const health_bar = health_factor * side * (planetBase.r - 1);
        this.planetContainer.beginFill(0xFF0000);
        this.planetContainer.lineStyle(2, 0x000000);
        this.planetContainer.drawRect(center_x, center_y - health_bar, side, 2 * health_bar);
        this.planetContainer.endFill();
    }

    drawShip(ship) {
        const side = CELL_SIZE * this.scale;
        const health_factor = 0.1 + 0.3 * (BASE_SHIP_HEALTH - ship.health) / BASE_SHIP_HEALTH;

        const x = side * ship.x;
        const y = side * ship.y;

        this.drawCell(this.shipContainer, ship.x, ship.y, PLAYER_COLORS[ship.owner], health_factor);

        if (ship.docking.status !== "undocked") {
            let progress = ship.docking.status === "docked" ? DOCK_TURNS : DOCK_TURNS - ship.docking.turns_left;

            const planetId = ship.docking.planet_id;
            const planetBase = this.replay.planets[planetId];

            const planetX = side * (planetBase.x + 0.5);
            const planetY = side * (planetBase.y + 0.5);

            const cx = x + 0.5*side;
            const cy = y + 0.5*side;

            const dx = planetX - cx;
            const dy = planetY - cy;

            this.shipContainer.beginFill(PLAYER_COLORS[ship.owner]);
            this.shipContainer.lineStyle(2, 0xFFFFFF, 1);
            if (ship.docking.status === "undocking") {
                // TODO:
            }
            else {
                progress /= DOCK_TURNS;
                this.shipContainer.moveTo(cx, cy);
                this.shipContainer.lineTo(cx + progress*dx, cy + progress*dy);
            }
            this.shipContainer.endFill();
        }
    }

    update() {
        if (this.currentSubstep.events) {
            for (let event of this.currentSubstep.events) {
                if (event.event === "destroyed") {
                    let draw = (frame) => {
                        const width = CELL_SIZE * this.scale;
                        const height = CELL_SIZE * this.scale;

                        const x = width * event.x;
                        const y = width * event.y;

                        this.shipContainer.beginFill(0xFFA500, frame / 24);
                        this.shipContainer.lineStyle(0);
                        this.shipContainer.drawRect(x, y, width, height);
                        this.shipContainer.endFill();
                    };
                    if (event.radius > 0) {
                        let r = event.radius;
                        draw = (frame) => {
                            const side = CELL_SIZE * this.scale;
                            this.planetContainer.lineStyle(0);
                            for (let dx = -r; dx <= r; dx++) {
                                for (let dy = -r; dy <= r; dy++) {
                                    if (dx*dx + dy*dy <= r*r) {
                                        const distance = (48 - frame) / 24;
                                        const x = side * (distance * dx + event.x);
                                        const y = side * (distance * dy + event.y);

                                        this.planetContainer.beginFill(0xFFA500, (frame / 48) * (1 / (1 + dx*dx + dy*dy)));
                                        this.planetContainer.drawRect(x, y, side, side);
                                        this.planetContainer.endFill();
                                    }
                                }
                            }
                        };
                    }

                    this.animationQueue.push(new FrameAnimation(
                        48, () => {}, draw,
                    ));
                }
                else if (event.event === "attack") {
                    this.animationQueue.push(new FrameAnimation(
                        24,
                        () => {
                        },
                        (frame) => {
                            const side = CELL_SIZE * this.scale;

                            const x = side * (event.x + 0.5);
                            const y = side * (event.y + 0.5);

                            this.shipContainer.lineStyle(2, PLAYER_COLORS[event.entity.owner], 0.5 * frame / 24);
                            this.shipContainer.drawCircle(x, y, side * ATTACK_RADIUS);
                            this.shipContainer.endFill();
                        },
                    ));
                }
                else if (event.event === "spawned") {
                     this.animationQueue.push(new FrameAnimation(
                        24,
                        () => {
                        },
                        (frame) => {
                            const side = CELL_SIZE * this.scale;
                            const planetX = side * (event.planet_x + 0.5);
                            const planetY = side * (event.planet_y + 0.5);
                            const ship_x = side * (event.x + 0.5);
                            const ship_y = side * (event.y + 0.5);
                            this.shipContainer.lineStyle(3, PLAYER_COLORS[event.entity.owner], 0.5 * frame / 24);
                            this.shipContainer.moveTo(planetX, planetY);
                            this.shipContainer.lineTo(ship_x, ship_y);
                            this.shipContainer.endFill();
                        },
                    ));
                }
                else {
                    console.log(event);
                }
            }
        }
    }

    draw() {
        this.planetContainer.clear();
        this.shipContainer.clear();

        for (let planet of this.currentSubstep.planets) {
            this.drawPlanet(planet);
        }

        for (let ship of this.currentSubstep.ships) {
            this.drawShip(ship);
        }

        let stats = this.currentStatistics;

        let x = 0;
        for (let player = 0; player < this.replay.num_players; player++) {
            const width = 800 * (stats.ships[player] || 0) / stats.total_ships;
            this.statsDisplay.beginFill(PLAYER_COLORS[player]);
            this.statsDisplay.drawRect(x, 0, width, 40);
            this.statsDisplay.endFill();
            x += width;
        }
        this.statsDisplay.beginFill(0x000000);
        this.statsDisplay.drawRect(0, 40, 800, 10);
        this.statsDisplay.endFill();
        x = 0;
        for (let player = 0; player < this.replay.num_players; player++) {
            const width = 800 * (stats.planets[player] || 0) / this.replay.planets.length;
            this.statsDisplay.beginFill(PLAYER_COLORS[player]);
            this.statsDisplay.drawRect(x, 50, width, 40);
            this.statsDisplay.endFill();
            x += width;
        }
        const width = 800 * (stats.planets["unowned"] || 0) / this.replay.planets.length;
        this.statsDisplay.beginFill(PLANET_COLOR);
        this.statsDisplay.drawRect(x, 50, width, 40);
        this.statsDisplay.endFill();
        this.statsDisplay.drawRect(0, 90, 800, 10);

        this.currentTurnDisplay.text = `Frame ${this.frame}.${this.substep}`;

        this.animationQueue = this.animationQueue.filter((anim) => {
            anim.draw(anim.frames);
            anim.frames--;
            return anim.frames > 0;
        });
    }
}

class HaliteVisualizerControls {
    constructor(replay) {
        this.visualizer = new HaliteVisualizer(replay);
    }

    attach(el) {
        $(el).empty();

        this.visualizer.attach(el);

        this.visualizer.play();
    }
}

function setupUpload() {
    const dragPopup = $(`<div class="halite-dnd-popup">`).appendTo(document.body);
    dragPopup.append($("
Drop to visualize
"));

    const visualizerEl = $("
").appendTo(document.body);

    document.body.addEventListener("dragenter", (e) => {
        e.stopPropagation();
        e.preventDefault();

        dragPopup.removeClass("hidden");
    });
    document.body.addEventListener("dragover", (e) => {
        e.stopPropagation();
        e.preventDefault();
    });
    document.body.addEventListener("dragexit", (e) => {
        e.stopPropagation();
        e.preventDefault();
        dragPopup.addClass("hidden");
    });

    document.body.addEventListener("drop", (e) => {
        e.stopPropagation();
        e.preventDefault();
        dragPopup.addClass("hidden");

        let dt = e.dataTransfer;
        let files = dt.files;

        let reader = new FileReader();
        reader.onload = function(e) {
            const replay = msgpack.decode(new Uint8Array(e.target.result));

            console.log(replay);

            let visualizer = new HaliteVisualizerControls(replay);
            visualizer.attach(visualizerEl);
        };
        reader.readAsArrayBuffer(files[0]);
    });
}

function setup() {
    setupUpload();
}
<commit_msg>Update animations accounting for variable frame time<commit_after>/**
 * Created by lidavidm on 6/6/17.
 */

const CELL_SIZE = 1;
const PLAYER_COLORS = [0xFF704B, 0x9010B9, 0x005DD0, 0x00B553];
const PLANET_COLOR = 0x888888;

const DOCK_TURNS = 5;
const ATTACK_RADIUS = 5;
const BASE_SHIP_HEALTH = 255;

class FrameAnimation {
    constructor(frames, update, draw) {
        this.frames = frames;
        this.update = update;
        this.draw = draw;
    }
}

class HaliteVisualizer {
    constructor(replay) {
        this.replay = replay;
        this.frame = 0;
        this.substep = 0;
        // TODO: match aspect ratio of map
        this.application = new PIXI.Application(800, 900);

        this.scale = 800 / Math.max(replay.width, replay.height);

        this.planetContainer = new PIXI.Graphics();
        this.shipContainer = new PIXI.Graphics();
        this.container = new PIXI.Container();
        this.container.position.set(0, 100);
        this.container.addChild(this.planetContainer, this.shipContainer);

        this.statsDisplay = new PIXI.Graphics();

        this.currentTurnDisplay = new PIXI.Text("Frame");

        this.application.stage.addChild(this.container);
        this.application.stage.addChild(this.statsDisplay);
        this.application.stage.addChild(this.currentTurnDisplay);

        this.timer = null;

        this.animationQueue = [];
    }

    get currentSubstep() {
        return this.replay.frames[this.frame][this.substep];
    }

    get currentStatistics() {
        let substep = this.currentSubstep;
        let planets = { "unowned": 0 };
        let ships = {};
        let total_ships = 0;

        for (let planet of substep.planets) {
            if (planet.owner !== null) {
                if (typeof planets[planet.owner] === "undefined") {
                    planets[planet.owner] = 0;
                }
                planets[planet.owner]++;
            }
            else {
                planets["unowned"]++;
            }
        }

        for (let ship of substep.ships) {
            if (typeof ships[ship.owner] === "undefined") {
                ships[ship.owner] = 0;
            }
            ships[ship.owner]++;
            total_ships++;
        }

        return {
            "planets": planets,
            "ships": ships,
            "total_ships": total_ships,
        }
    }

    attach(containerEl) {
        $(containerEl).append(this.application.view);

        this.draw();
    }

    play() {
        if (this.timer) return;

        this.timer = window.setInterval(() => {
            for (let i = 0; i < 6; i++) {
                this.substep++;
                if (this.substep >= this.replay.frames[this.frame].length) {
                    this.substep = 0;
                    this.frame++;
                }

                if (this.frame >= this.replay.frames.length) {
                    this.pause();
                    this.frame = this.replay.frames.length - 1;
                    this.substep = this.replay.frames[this.frame].length - 1;
                    break;
                }

                this.update();
            }
        }, 1000/40);

        this.application.ticker.add((dt) => {
            this.draw(dt);
        });
    }

    pause() {
        if (!this.timer) return;

        window.clearInterval(this.timer);
        this.timer = null;
    }

    /**
     *
     * @param container
     * @param x
     * @param y
     * @param color
     * @param health_factor
     */
    drawCell(container, x, y, color, health_factor) {
        const side = CELL_SIZE * this.scale;
        x = x * side;
        y = y * side;
        container.lineStyle(0);
        container.beginFill(color, 0.5);
        container.drawRect(x, y, side, side);
        container.endFill();
        container.beginFill(color, 1);
        container.drawRect(
            x + health_factor * side,
            y + health_factor * side,
            (1 - 2*health_factor) * side,
            (1 - 2*health_factor) * side);
        container.endFill();
    }

    drawPlanet(planet) {
        let planetBase = this.replay.planets[planet.id];

        const side = CELL_SIZE * this.scale;

        this.planetContainer.lineStyle(0);
        for (let dx = -planetBase.r; dx <= planetBase.r; dx++) {
            for (let dy = -planetBase.r; dy <= planetBase.r; dy++) {
                if (dx*dx + dy*dy <= planetBase.r*planetBase.r) {
                    const health_factor = 0.5 *
                        (planetBase.production - planet.remaining_production) / planetBase.production;
                    this.drawCell(
                        this.planetContainer,
                        dx + planetBase.x,
                        dy + planetBase.y,
                        planet.owner === null ? PLANET_COLOR : PLAYER_COLORS[planet.owner],
                        health_factor,
                    );
                }
            }
        }

        const center_x = side * planetBase.x;
        const center_y = side * (planetBase.y + 0.5);

        const health_factor = planet.health / planetBase.health;
        const health_bar = health_factor * side * (planetBase.r - 1);
        this.planetContainer.beginFill(0xFF0000);
        this.planetContainer.lineStyle(2, 0x000000);
        this.planetContainer.drawRect(center_x, center_y - health_bar, side, 2 * health_bar);
        this.planetContainer.endFill();
    }

    drawShip(ship) {
        const side = CELL_SIZE * this.scale;
        const health_factor = 0.1 + 0.3 * (BASE_SHIP_HEALTH - ship.health) / BASE_SHIP_HEALTH;

        const x = side * ship.x;
        const y = side * ship.y;

        this.drawCell(this.shipContainer, ship.x, ship.y, PLAYER_COLORS[ship.owner], health_factor);

        if (ship.docking.status !== "undocked") {
            let progress = ship.docking.status === "docked" ? DOCK_TURNS : DOCK_TURNS - ship.docking.turns_left;

            const planetId = ship.docking.planet_id;
            const planetBase = this.replay.planets[planetId];

            const planetX = side * (planetBase.x + 0.5);
            const planetY = side * (planetBase.y + 0.5);

            const cx = x + 0.5*side;
            const cy = y + 0.5*side;

            const dx = planetX - cx;
            const dy = planetY - cy;

            this.shipContainer.beginFill(PLAYER_COLORS[ship.owner]);
            this.shipContainer.lineStyle(2, 0xFFFFFF, 1);
            if (ship.docking.status === "undocking") {
                // TODO:
            }
            else {
                progress /= DOCK_TURNS;
                this.shipContainer.moveTo(cx, cy);
                this.shipContainer.lineTo(cx + progress*dx, cy + progress*dy);
            }
            this.shipContainer.endFill();
        }
    }

    update() {
        if (this.currentSubstep.events) {
            for (let event of this.currentSubstep.events) {
                if (event.event === "destroyed") {
                    let draw = (frame) => {
                        const width = CELL_SIZE * this.scale;
                        const height = CELL_SIZE * this.scale;

                        const x = width * event.x;
                        const y = width * event.y;

                        this.shipContainer.beginFill(0xFFA500, frame / 24);
                        this.shipContainer.lineStyle(0);
                        this.shipContainer.drawRect(x, y, width, height);
                        this.shipContainer.endFill();
                    };
                    if (event.radius > 0) {
                        let r = event.radius;
                        draw = (frame) => {
                            const side = CELL_SIZE * this.scale;
                            this.planetContainer.lineStyle(0);
                            for (let dx = -r; dx <= r; dx++) {
                                for (let dy = -r; dy <= r; dy++) {
                                    if (dx*dx + dy*dy <= r*r) {
                                        const distance = (48 - frame) / 24;
                                        const x = side * (distance * dx + event.x);
                                        const y = side * (distance * dy + event.y);

                                        this.planetContainer.beginFill(0xFFA500, (frame / 48) * (1 / (1 + dx*dx + dy*dy)));
                                        this.planetContainer.drawRect(x, y, side, side);
                                        this.planetContainer.endFill();
                                    }
                                }
                            }
                        };
                    }

                    this.animationQueue.push(new FrameAnimation(
                        48, () => {}, draw,
                    ));
                }
                else if (event.event === "attack") {
                    this.animationQueue.push(new FrameAnimation(
                        24,
                        () => {
                        },
                        (frame) => {
                            const side = CELL_SIZE * this.scale;

                            const x = side * (event.x + 0.5);
                            const y = side * (event.y + 0.5);

                            this.shipContainer.lineStyle(2, PLAYER_COLORS[event.entity.owner], 0.5 * frame / 24);
                            this.shipContainer.drawCircle(x, y, side * ATTACK_RADIUS);
                            this.shipContainer.endFill();
                        },
                    ));
                }
                else if (event.event === "spawned") {
                     this.animationQueue.push(new FrameAnimation(
                        24,
                        () => {
                        },
                        (frame) => {
                            const side = CELL_SIZE * this.scale;
                            const planetX = side * (event.planet_x + 0.5);
                            const planetY = side * (event.planet_y + 0.5);
                            const ship_x = side * (event.x + 0.5);
                            const ship_y = side * (event.y + 0.5);
                            this.shipContainer.lineStyle(3, PLAYER_COLORS[event.entity.owner], 0.5 * frame / 24);
                            this.shipContainer.moveTo(planetX, planetY);
                            this.shipContainer.lineTo(ship_x, ship_y);
                            this.shipContainer.endFill();
                        },
                    ));
                }
                else {
                    console.log(event);
                }
            }
        }
    }

    draw(dt=0) {
        this.planetContainer.clear();
        this.shipContainer.clear();

        for (let planet of this.currentSubstep.planets) {
            this.drawPlanet(planet);
        }

        for (let ship of this.currentSubstep.ships) {
            this.drawShip(ship);
        }

        let stats = this.currentStatistics;

        let x = 0;
        for (let player = 0; player < this.replay.num_players; player++) {
            const width = 800 * (stats.ships[player] || 0) / stats.total_ships;
            this.statsDisplay.beginFill(PLAYER_COLORS[player]);
            this.statsDisplay.drawRect(x, 0, width, 40);
            this.statsDisplay.endFill();
            x += width;
        }
        this.statsDisplay.beginFill(0x000000);
        this.statsDisplay.drawRect(0, 40, 800, 10);
        this.statsDisplay.endFill();
        x = 0;
        for (let player = 0; player < this.replay.num_players; player++) {
            const width = 800 * (stats.planets[player] || 0) / this.replay.planets.length;
            this.statsDisplay.beginFill(PLAYER_COLORS[player]);
            this.statsDisplay.drawRect(x, 50, width, 40);
            this.statsDisplay.endFill();
            x += width;
        }
        const width = 800 * (stats.planets["unowned"] || 0) / this.replay.planets.length;
        this.statsDisplay.beginFill(PLANET_COLOR);
        this.statsDisplay.drawRect(x, 50, width, 40);
        this.statsDisplay.endFill();
        this.statsDisplay.drawRect(0, 90, 800, 10);

        this.currentTurnDisplay.text = `Frame ${this.frame}.${this.substep}`;

        // dt comes from Pixi ticker, and the unit is essentially frames
        this.animationQueue = this.animationQueue.filter((anim) => {
            anim.draw(anim.frames);
            anim.frames -= dt;
            return anim.frames > 0;
        });
    }
}

class HaliteVisualizerControls {
    constructor(replay) {
        this.visualizer = new HaliteVisualizer(replay);
    }

    attach(el) {
        $(el).empty();

        this.visualizer.attach(el);

        this.visualizer.play();
    }
}

function setupUpload() {
    const dragPopup = $(`<div class="halite-dnd-popup">`).appendTo(document.body);
    dragPopup.append($("
Drop to visualize
"));

    const visualizerEl = $("
").appendTo(document.body);

    document.body.addEventListener("dragenter", (e) => {
        e.stopPropagation();
        e.preventDefault();

        dragPopup.removeClass("hidden");
    });
    document.body.addEventListener("dragover", (e) => {
        e.stopPropagation();
        e.preventDefault();
    });
    document.body.addEventListener("dragexit", (e) => {
        e.stopPropagation();
        e.preventDefault();
        dragPopup.addClass("hidden");
    });

    document.body.addEventListener("drop", (e) => {
        e.stopPropagation();
        e.preventDefault();
        dragPopup.addClass("hidden");

        let dt = e.dataTransfer;
        let files = dt.files;

        let reader = new FileReader();
        reader.onload = function(e) {
            const replay = msgpack.decode(new Uint8Array(e.target.result));

            console.log(replay);

            let visualizer = new HaliteVisualizerControls(replay);
            visualizer.attach(visualizerEl);
        };
        reader.readAsArrayBuffer(files[0]);
    });
}

function setup() {
    setupUpload();
}
<|endoftext|>


<commit_before>'use strict'

module.exports = function (mongoose, config, db) {
    const utils = require('../lib/utils')();

    function articleResponse(article) {
        const user = article.user;
        return {
            id: article.id,
            created_at: article.created_at,
            title: article.title,
            user_id: article.user_id,
            user: {
                id: user.id,
                name: user.name,
                email: user.email
            },
            keyword: article.keyword,
            content: article.content
        }
    }

    return {
        create(req, res) {
            db.Sequence.getNextSequence("articles", (err, nextArticleId) => {
                if (err) throw err;
                const keyword = req.body.keyword || req.body.title;
                const article = db.Article({
                    id: nextArticleId,
                    title: req.body.title,
                    user_id: req.user.id,
                    user: req.user,
                    content: req.body.content,
                    keyword: keyword.split(',')
                });
                article.save((err, article) => err ?
                    utils.error(res, 422, err.message) :
                    utils.success(res, "update success!")
                    );
            })
        },
        delete(req, res) {
            db.Article.findOne({ id: req.params.articleId, user_id: req.user.id }, (err, article) => {
                if (err) return utils.error(res, 422);
                if (!article) return utils.error(res, 404);
                article.remove(err => {
                    if (err) return utils.error(res, 422);
                    return utils.success(res);
                });
            })
        },
        update(req, res) {
            db.Article.findOne({ id: req.params.articleId, user_id: req.user.id }, (err, article) => {
                if (err) return utils.error(res, 422);
                if (!article) return utils.error(res, 404);

                if (req.body.title) article.title = req.body.title;
                if (req.body.content) article.price = req.body.content;
                if (req.body.keyword) article.keyword = req.body.keyword.split(',');

                article.save((err, article) => utils.success(res, articleResponse(article)));
            });
        },
        get(req, res) {
            const id = req.params.articleId;
            let query = {};
            let sortBy = null;

            if (id) {
                query = { id };
            } else {
                sortBy = {};
                sortBy["order_by"] = "created_at";
                sortBy["order_type"] = "desc";
                if (req.query.title) query["title"] = req.query.title;
                if (req.query.user_id) query["user_id"] = req.query.user_id;
                if (req.query.order_by === "price") sortBy["order_by"] = req.query.order_by;
                if (req.query.order_type === "asc") sortBy["order_type"] = req.query.order_type;
            }

            const queryRequest = db.Article.find(query);
            if (sortBy) {
                var sort = {};
                sort[sortBy["order_by"]] = sortBy["order_type"] === "asc" ? "ascending" : "descending";
                queryRequest.sort(sort);
            }

            queryRequest.populate("user").exec((err, dbResponse) => {
                if (err) return utils.error(res, 422, err.message);
                if (!dbResponse) return utils.error(res, 404);
                utils.responseData(res, dbResponse.count, dbResponse.map(article => articleResponse(article)));
            })
        }
    }
}<commit_msg>debug<commit_after>'use strict'

module.exports = function (mongoose, config, db) {
    const utils = require('../lib/utils')();

    function articleResponse(article) {
        const user = article.user;
        return {
            id: article.id,
            created_at: article.created_at,
            title: article.title,
            user_id: article.user_id,
            user: {
                id: user.id,
                name: user.name,
                email: user.email
            },
            keyword: article.keyword,
            content: article.content
        }
    }

    return {
        create(req, res) {
            db.Sequence.getNextSequence("articles", (err, nextArticleId) => {
                if (err) throw err;
                const keyword = req.body.keyword || req.body.title;
                const article = db.Article({
                    id: nextArticleId,
                    title: req.body.title,
                    user_id: req.user.id,
                    user: req.user,
                    content: req.body.content,
                    keyword: keyword.split(',')
                });
                console.log(article);
                article.save((err, article) => err ?
                    utils.error(res, 422, err.message) :
                    utils.success(res, "update success!")
                    );
            })
        },
        delete(req, res) {
            db.Article.findOne({ id: req.params.articleId, user_id: req.user.id }, (err, article) => {
                if (err) return utils.error(res, 422);
                if (!article) return utils.error(res, 404);
                article.remove(err => {
                    if (err) return utils.error(res, 422);
                    return utils.success(res);
                });
            })
        },
        update(req, res) {
            db.Article.findOne({ id: req.params.articleId, user_id: req.user.id }, (err, article) => {
                if (err) return utils.error(res, 422);
                if (!article) return utils.error(res, 404);

                if (req.body.title) article.title = req.body.title;
                if (req.body.content) article.price = req.body.content;
                if (req.body.keyword) article.keyword = req.body.keyword.split(',');

                article.save((err, article) => utils.success(res, articleResponse(article)));
            });
        },
        get(req, res) {
            const id = req.params.articleId;
            let query = {};
            let sortBy = null;

            if (id) {
                query = { id };
            } else {
                sortBy = {};
                sortBy["order_by"] = "created_at";
                sortBy["order_type"] = "desc";
                if (req.query.title) query["title"] = req.query.title;
                if (req.query.user_id) query["user_id"] = req.query.user_id;
                if (req.query.order_by === "price") sortBy["order_by"] = req.query.order_by;
                if (req.query.order_type === "asc") sortBy["order_type"] = req.query.order_type;
            }

            const queryRequest = db.Article.find(query);
            if (sortBy) {
                var sort = {};
                sort[sortBy["order_by"]] = sortBy["order_type"] === "asc" ? "ascending" : "descending";
                queryRequest.sort(sort);
            }

            queryRequest.populate("user").exec((err, dbResponse) => {
                if (err) return utils.error(res, 422, err.message);
                if (!dbResponse) return utils.error(res, 404);
                utils.responseData(res, dbResponse.count, dbResponse.map(article => articleResponse(article)));
            })
        }
    }
}<|endoftext|>