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

    var util = require('util');

    require('time-grunt')(grunt);
    require('load-grunt-tasks')(grunt);

    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        tmpDir: '.tmp/',

        buildName: '<%= pkg.name %>',
        buildDir: 'build/',
        buildFileNameNoExt: '<%= buildDir %><%= buildName %>',
        buildJsFilePath: '<%= buildFileNameNoExt %>.js',
        buildMinJsFilePath: '<%= buildFileNameNoExt %>.min.js',
        buildCssFilePath: '<%= buildFileNameNoExt %>.css',
        buildMinCssFilePath: '<%= buildFileNameNoExt %>.min.css',
        buildArchiveFilePath: '<%= tmpDir %>release/build.zip',
        buildMinArchiveFilePath: '<%= tmpDir %>release/build.min.zip',

        srcScriptDir: 'src/',
        srcStyleDir: 'styles/',
        srcScriptFilePaths: ['<%= srcScriptDir %>*.js'],
        srcStyleFilePaths: ['<%= srcStyleDir %>*.scss'],
        srcMainStyleFilePath: '<%= srcStyleDir %>main.scss',

        testDir: 'test/',
        testReportDir: '<%= testDir %>report/',
        testFilePaths: ['<%= testDir %>**/*.html', '!<%= testReportDir %>**/*.html'],
        testHtmlReportDir: '<%= testReportDir %>coverage/',
        testLcovReportDir: '<%= testReportDir %>lcov/',

        docsDir: 'docs/',
        teststandDir: 'teststand/',
        libDir: 'lib/',
        ghPagesPublishPaths: ['<%= buildDir %>**/*', '<%= docsDir %>**/*', '<%= teststandDir %>**/*',
            '<%= libDir %>**/*'],

        banner: '/*! <%= buildName %> - v<%= pkg.version %> - ' +
                '<%= grunt.template.today("yyyy-mm-dd") %> */\n',

        concat: {
            options: {
                banner: '<%= banner %>'
            },
            debug: {
                src: ['<%= srcScriptFilePaths %>'],
                dest: '<%= buildJsFilePath %>',
                options: {
                    sourceMap: true,
                    sourceMapStyle: 'link',
                    stripBanners: false
                }
            },
            release: {
                src: ['<%= srcScriptFilePaths %>'],
                dest: '<%= buildJsFilePath %>',
                options: {
                    sourceMap: false,
                    stripBanners: true
                }
            }
        },

        uglify: {
            options: {
                banner: '<%= banner %>'
            },
            debug: {
                src: ['<%= srcScriptFilePaths %>'],
                dest: '<%= buildMinJsFilePath %>',
                options: {
                    sourceMap: true,
                    preserveComments: 'some'
                }
            },
            release: {
                src: ['<%= srcScriptFilePaths %>'],
                dest: '<%= buildMinJsFilePath %>',
                options: {
                    sourceMap: false,
                    preserveComments: 'some'
                }
            }
        },

        githooks: {
            all: {
                options: {
                    hashbang: '#!/bin/sh',
                    startMarker: '# GRUNT-GITHOOKS START',
                    endMarker: '# GRUNT-GITHOOKS END'
                },

                'pre-commit': {
                    command: 'grunt',
                    taskNames: 'check',
                    template: 'pre-commit-hook.hb',
                    preventExit: false
                },

                'post-merge': {
                    template: 'post-merge-hook.hb',
                    preventExit: true
                }
            }
        },

        lintspaces: {
            options: {
                editorconfig: '.editorconfig',
                showCodes: true,
                showTypes: true,
                showValid: true
            },
            gruntfile: {
                src: ['Gruntfile.js'],
                options: {
                    ignores: ['js-comments']
                }
            },
            scripts: {
                src: ['<%= srcScriptFilePaths %>'],
                options: {
                    ignores: ['js-comments']
                }
            },
            styles: {
                src: ['<%= srcStyleFilePaths %>'],
                options: {
                }
            },
            tests: {
                src: ['<%= testFilePaths %>'],
                options: {
                    ignores: ['html-comments']
                }
            },
            configs: {
                src: ['*.yml', '.*rc', '*-hook.hb', '*.json'],
                options: {
                }
            }
        },

        jscs: {
            options: {
                config: true
            },

            /* Please, take into account https://github.com/tschaub/grunt-newer/issues/39.
               TODO: Remove this warning when the issue will be closed. */
            gruntfile: {
                src: ['Gruntfile.js']
            },
            scripts: {
                src: ['<%= srcScriptFilePaths %>']
            }
        },

        jshint: {
            options: {
                jshintrc: true
            },

            /* Please, take into account https://github.com/tschaub/grunt-newer/issues/39.
             TODO: Remove this warning when the issue will be closed. */
            gruntfile: {
                src: ['Gruntfile.js']
            },
            scripts: {
                src: ['<%= srcScriptFilePaths %>']
            }
        },

        clean: {
            build: ['<%= buildDir %>'],
            docs: ['<%= docsDir %>'],
            tests: ['<%= testReportDir %>'],
            release: ['<%= buildArchiveFilePath %>', '<%= buildMinArchiveFilePath %>'],
            tmp: ['<%= tmpDir %>']
        },

        jsdoc: {
            dist: {
                src: ['<%= srcScriptFilePaths %>'],
                options: {
                    configure: '.jsdocrc',
                    destination: '<%= docsDir %>'
                }
            }
        },

        'gh-pages': {
            options: {
                git: 'git',
                clone: '<%= tmpDir %>gh-pages',
                branch: 'gh-pages',
                add: true
            },
            publish: {
                options: {
                    message: 'auto publish'
                },
                src: ['<%= ghPagesPublishPaths %>']
            },
            deploy: {
                options: {
                    // Travis environment variables можно посмотреть здесь:
                    // https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/data/env.rb
                    // https://github.com/travis-ci/travis-build/blob/master/spec/shared/script.rb
                    message: util.format('auto deploy\nReason: %s', process.env.TRAVIS_COMMIT || 'unknown'),

                    user: {
                        name: 'Flexberry',
                        email: 'mail@flexberry.net'
                    },
                    repo: 'https://' + process.env.GH_TOKEN + '@github.com/Flexberry/testproj.git',

                    // скрыть лог задачи, иначе github-токен будет выведен в логе билда Travis CI.
                    silent: true
                },
                src: ['<%= ghPagesPublishPaths %>']
            }
        },

        qunit: {
            options: {
                timeout: 30000,
                '--web-security': 'no',
                coverage: {
                    src: '<%= buildMinJsFilePath %>',
                    instrumentedFiles: '<%= testReportDir %>temp/',
                    htmlReport: '<%= testHtmlReportDir %>',
                    lcovReport: '<%= testLcovReportDir %>',
                    linesThresholdPct: 70
                }
            },
            all: ['<%= testFilePaths %>']
        },

        coveralls: {
            options: {
                force: true
            },
            all: {
                src: '<%= testLcovReportDir %>lcov.info'
            }
        },

        compress: {
            release: {
                options: {
                    archive: '<%= buildArchiveFilePath %>'
                },
                files: [
                    {
                        src: ['*.js', '*.css', '!*.min.js', '!*.min.css'],
                        dest: '',
                        expand: true,
                        cwd: '<%= buildDir %>'
                    }
                ]
            },

            'release-min': {
                options: {
                    archive: '<%= buildMinArchiveFilePath %>'
                },
                files: [
                    {
                        src: ['*.min.js', '*.min.css'],
                        dest: '',
                        expand: true,
                        cwd: '<%= buildDir %>'
                    }
                ]
            }
        },

        'github-release': {
            options: {
                repository: 'Flexberry/testproj',
                auth: {
                    user: process.env.GH_TOKEN,
                    password: ''
                },
                release: {
                    // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
                    // jshint camelcase:false
                    // If no specified then version field from package.json will be used.
                    tag_name: grunt.option('tag'),

                    // If no specified then tag_name will be used.
                    name: grunt.option('title'),

                    // Description of release. If no specified then last commit comment will be used.
                    body: grunt.option('desc'),

                    // true to create a draft (unpublished) release, false to create a published one.
                    draft: grunt.option('draft') || false,

                    // Pre-release label. If tag_name ends with 'rc' (release candidate), then true.
                    prerelease: grunt.option('prerelease') || /rc$/.test(grunt.option('tag')) || false
                }
            },
            files: {
                // Files that you want to attach to Release
                src: ['<%= buildArchiveFilePath %>', '<%= buildMinArchiveFilePath %>']
            }
        },

        sass: {
            debug: {
                options: {
                    noCache: true,
                    sourcemap: 'auto',
                    style: 'expanded'
                },
                files: {
                    '<%= buildCssFilePath %>': '<%= srcMainStyleFilePath %>'
                }
            },

            release: {
                options: {
                    noCache: true,
                    sourcemap: 'none',
                    style: 'expanded'
                },
                files: {
                    '<%=buildCssFilePath %>': '<%= srcMainStyleFilePath %>'
                }
            },

            'release-min': {
                options: {
                    noCache: true,
                    sourcemap: 'none',
                    style: 'compressed'
                },
                files: {
                    '<%= buildMinCssFilePath %>': '<%= srcMainStyleFilePath %>'
                }
            }
        },

        watch: {
            gruntfile: {
                files: ['Gruntfile.js']
            },
            scripts: {
                files: ['<%= srcScriptFilePaths %>'],
                tasks: ['concat:debug', 'uglify:debug']
            },
            styles: {
                files: ['<%= srcStyleFilePaths %>'],
                tasks: ['sass:debug']
            }
        },

        bower: {
            install: {
                options: {
                    targetDir: '<%= libDir %>',
                    layout: 'byComponent',
                    install: true,
                    copy: false
                }
            },
            refreshLib: {
                options: {
                    targetDir: '<%= libDir %>',
                    layout: 'byComponent',
                    install: false,
                    copy: true
                }
            }
        }
    });

    grunt.registerTask('init', ['bower:install', 'githooks']);
    grunt.registerTask('start', ['init', 'watch']);

    grunt.registerTask('default', ['clean', 'test', 'docs']);

    grunt.registerTask('check', ['lintspaces', 'jscs', 'jshint']);
    grunt.registerTask('check-new', ['newer:lintspaces', 'newer:jscs', 'newer:jshint']);
    grunt.registerTask('test', ['check', 'clean:tests', 'build-debug', 'qunit']);

    grunt.registerTask('build', ['build-debug']);

    // build with MAP files
    grunt.registerTask('build-debug', ['clean:build', 'concat:debug', 'uglify:debug', 'sass:debug']);

    // build without MAP files
    grunt.registerTask('build-release', ['clean:build', 'concat:release', 'uglify:release', 'sass:release',
        'sass:release-min']);

    grunt.registerTask('docs', ['clean:docs', 'jsdoc']);

    /* Usage:    grunt release --tag=v1.0.0 --title="First release" --desc="Release description"
     * or:       grunt release --tag=v1.0.1rc (auto title and description)
     * or just:  grunt release (tag from package.json->version)
     *
     * NOTE: for github-release task you need GH_TOKEN environment variable.
     *       Put once in cmd: SET GH_TOKEN=<YOUR GITHUB TOKEN>
     */
    grunt.registerTask('release', ['release-local', 'github-release', 'clean:release']);
    grunt.registerTask('release-local', ['clean:release', 'build-release', 'compress:release', 'compress:release-min']);

    grunt.registerTask('publish', ['check', 'build-release', 'docs', 'gh-pages:publish']);

    grunt.registerTask('travis', 'Travis CI build task', function() {
        if (process.env.TRAVIS_REPO_SLUG === undefined && !grunt.option('pleaserun')) {
            throw new Error(
                'Task "travis" is not intended to execute in the environment other than Travis CI. ' +
                'Use --pleaserun to run anyway.'
            );
        }

        grunt.task.run(['check', 'build-release', 'bower:install', 'docs', 'gh-pages:deploy', 'qunit', 'coveralls']);
    });

    grunt.registerTask('bower:removeLib', 'Remove bower components from library folder.', function() {
        var rimraf = require('rimraf'),
            path = require('path'),
            fs = require('fs'),
            dir = grunt.config('libDir'),
            cmpArg = grunt.option('components'),
            rmpaths;

        if (!cmpArg) {
            rmpaths = [dir];
        } else {
            rmpaths = cmpArg.split(' ').map(function(x) {
                return path.join(dir, x);
            });
        }

        rmpaths.forEach(function(rmpath) {
            var msg = 'Remove ' + rmpath;
            if (fs.existsSync(rmpath)) {
                try {
                    rimraf.sync(rmpath);
                    grunt.log.ok(msg);
                }
                catch (ex) {
                    grunt.log.error(msg);
                    throw ex;
                }
            } else {
                grunt.log.error(msg + ' NOTFOUND');
            }
        });
    });

    grunt.registerTask('mycustomtask', 'My custom task.', function() {
        // http://gruntjs.com/creating-tasks#custom-tasks
        grunt.log.writeln('Currently running my custom task.');

        // grunt.task.run('bar', 'baz');
        // Or:
        // grunt.task.run(['bar', 'baz']);

        // Use task args (http://gruntjs.com/api/grunt.option):
        // grunt mycustomtask --opt
        // grunt.option('opt')
    });
};
<commit_msg>grunt bower:cleanup task<commit_after>module.exports = function(grunt) {
    'use strict';

    var util = require('util');

    require('time-grunt')(grunt);
    require('load-grunt-tasks')(grunt);

    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        tmpDir: '.tmp/',

        buildName: '<%= pkg.name %>',
        buildDir: 'build/',
        buildFileNameNoExt: '<%= buildDir %><%= buildName %>',
        buildJsFilePath: '<%= buildFileNameNoExt %>.js',
        buildMinJsFilePath: '<%= buildFileNameNoExt %>.min.js',
        buildCssFilePath: '<%= buildFileNameNoExt %>.css',
        buildMinCssFilePath: '<%= buildFileNameNoExt %>.min.css',
        buildArchiveFilePath: '<%= tmpDir %>release/build.zip',
        buildMinArchiveFilePath: '<%= tmpDir %>release/build.min.zip',

        srcScriptDir: 'src/',
        srcStyleDir: 'styles/',
        srcScriptFilePaths: ['<%= srcScriptDir %>*.js'],
        srcStyleFilePaths: ['<%= srcStyleDir %>*.scss'],
        srcMainStyleFilePath: '<%= srcStyleDir %>main.scss',

        testDir: 'test/',
        testReportDir: '<%= testDir %>report/',
        testFilePaths: ['<%= testDir %>**/*.html', '!<%= testReportDir %>**/*.html'],
        testHtmlReportDir: '<%= testReportDir %>coverage/',
        testLcovReportDir: '<%= testReportDir %>lcov/',

        docsDir: 'docs/',
        teststandDir: 'teststand/',
        libDir: 'lib/',
        ghPagesPublishPaths: ['<%= buildDir %>**/*', '<%= docsDir %>**/*', '<%= teststandDir %>**/*',
            '<%= libDir %>**/*'],

        banner: '/*! <%= buildName %> - v<%= pkg.version %> - ' +
                '<%= grunt.template.today("yyyy-mm-dd") %> */\n',

        concat: {
            options: {
                banner: '<%= banner %>'
            },
            debug: {
                src: ['<%= srcScriptFilePaths %>'],
                dest: '<%= buildJsFilePath %>',
                options: {
                    sourceMap: true,
                    sourceMapStyle: 'link',
                    stripBanners: false
                }
            },
            release: {
                src: ['<%= srcScriptFilePaths %>'],
                dest: '<%= buildJsFilePath %>',
                options: {
                    sourceMap: false,
                    stripBanners: true
                }
            }
        },

        uglify: {
            options: {
                banner: '<%= banner %>'
            },
            debug: {
                src: ['<%= srcScriptFilePaths %>'],
                dest: '<%= buildMinJsFilePath %>',
                options: {
                    sourceMap: true,
                    preserveComments: 'some'
                }
            },
            release: {
                src: ['<%= srcScriptFilePaths %>'],
                dest: '<%= buildMinJsFilePath %>',
                options: {
                    sourceMap: false,
                    preserveComments: 'some'
                }
            }
        },

        githooks: {
            all: {
                options: {
                    hashbang: '#!/bin/sh',
                    startMarker: '# GRUNT-GITHOOKS START',
                    endMarker: '# GRUNT-GITHOOKS END'
                },

                'pre-commit': {
                    command: 'grunt',
                    taskNames: 'check',
                    template: 'pre-commit-hook.hb',
                    preventExit: false
                },

                'post-merge': {
                    template: 'post-merge-hook.hb',
                    preventExit: true
                }
            }
        },

        lintspaces: {
            options: {
                editorconfig: '.editorconfig',
                showCodes: true,
                showTypes: true,
                showValid: true
            },
            gruntfile: {
                src: ['Gruntfile.js'],
                options: {
                    ignores: ['js-comments']
                }
            },
            scripts: {
                src: ['<%= srcScriptFilePaths %>'],
                options: {
                    ignores: ['js-comments']
                }
            },
            styles: {
                src: ['<%= srcStyleFilePaths %>'],
                options: {
                }
            },
            tests: {
                src: ['<%= testFilePaths %>'],
                options: {
                    ignores: ['html-comments']
                }
            },
            configs: {
                src: ['*.yml', '.*rc', '*-hook.hb', '*.json'],
                options: {
                }
            }
        },

        jscs: {
            options: {
                config: true
            },

            /* Please, take into account https://github.com/tschaub/grunt-newer/issues/39.
               TODO: Remove this warning when the issue will be closed. */
            gruntfile: {
                src: ['Gruntfile.js']
            },
            scripts: {
                src: ['<%= srcScriptFilePaths %>']
            }
        },

        jshint: {
            options: {
                jshintrc: true
            },

            /* Please, take into account https://github.com/tschaub/grunt-newer/issues/39.
             TODO: Remove this warning when the issue will be closed. */
            gruntfile: {
                src: ['Gruntfile.js']
            },
            scripts: {
                src: ['<%= srcScriptFilePaths %>']
            }
        },

        clean: {
            build: ['<%= buildDir %>'],
            docs: ['<%= docsDir %>'],
            tests: ['<%= testReportDir %>'],
            release: ['<%= buildArchiveFilePath %>', '<%= buildMinArchiveFilePath %>'],
            tmp: ['<%= tmpDir %>']
        },

        jsdoc: {
            dist: {
                src: ['<%= srcScriptFilePaths %>'],
                options: {
                    configure: '.jsdocrc',
                    destination: '<%= docsDir %>'
                }
            }
        },

        'gh-pages': {
            options: {
                git: 'git',
                clone: '<%= tmpDir %>gh-pages',
                branch: 'gh-pages',
                add: true
            },
            publish: {
                options: {
                    message: 'auto publish'
                },
                src: ['<%= ghPagesPublishPaths %>']
            },
            deploy: {
                options: {
                    // Travis environment variables можно посмотреть здесь:
                    // https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/data/env.rb
                    // https://github.com/travis-ci/travis-build/blob/master/spec/shared/script.rb
                    message: util.format('auto deploy\nReason: %s', process.env.TRAVIS_COMMIT || 'unknown'),

                    user: {
                        name: 'Flexberry',
                        email: 'mail@flexberry.net'
                    },
                    repo: 'https://' + process.env.GH_TOKEN + '@github.com/Flexberry/testproj.git',

                    // скрыть лог задачи, иначе github-токен будет выведен в логе билда Travis CI.
                    silent: true
                },
                src: ['<%= ghPagesPublishPaths %>']
            }
        },

        qunit: {
            options: {
                timeout: 30000,
                '--web-security': 'no',
                coverage: {
                    src: '<%= buildMinJsFilePath %>',
                    instrumentedFiles: '<%= testReportDir %>temp/',
                    htmlReport: '<%= testHtmlReportDir %>',
                    lcovReport: '<%= testLcovReportDir %>',
                    linesThresholdPct: 70
                }
            },
            all: ['<%= testFilePaths %>']
        },

        coveralls: {
            options: {
                force: true
            },
            all: {
                src: '<%= testLcovReportDir %>lcov.info'
            }
        },

        compress: {
            release: {
                options: {
                    archive: '<%= buildArchiveFilePath %>'
                },
                files: [
                    {
                        src: ['*.js', '*.css', '!*.min.js', '!*.min.css'],
                        dest: '',
                        expand: true,
                        cwd: '<%= buildDir %>'
                    }
                ]
            },

            'release-min': {
                options: {
                    archive: '<%= buildMinArchiveFilePath %>'
                },
                files: [
                    {
                        src: ['*.min.js', '*.min.css'],
                        dest: '',
                        expand: true,
                        cwd: '<%= buildDir %>'
                    }
                ]
            }
        },

        'github-release': {
            options: {
                repository: 'Flexberry/testproj',
                auth: {
                    user: process.env.GH_TOKEN,
                    password: ''
                },
                release: {
                    // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
                    // jshint camelcase:false
                    // If no specified then version field from package.json will be used.
                    tag_name: grunt.option('tag'),

                    // If no specified then tag_name will be used.
                    name: grunt.option('title'),

                    // Description of release. If no specified then last commit comment will be used.
                    body: grunt.option('desc'),

                    // true to create a draft (unpublished) release, false to create a published one.
                    draft: grunt.option('draft') || false,

                    // Pre-release label. If tag_name ends with 'rc' (release candidate), then true.
                    prerelease: grunt.option('prerelease') || /rc$/.test(grunt.option('tag')) || false
                }
            },
            files: {
                // Files that you want to attach to Release
                src: ['<%= buildArchiveFilePath %>', '<%= buildMinArchiveFilePath %>']
            }
        },

        sass: {
            debug: {
                options: {
                    noCache: true,
                    sourcemap: 'auto',
                    style: 'expanded'
                },
                files: {
                    '<%= buildCssFilePath %>': '<%= srcMainStyleFilePath %>'
                }
            },

            release: {
                options: {
                    noCache: true,
                    sourcemap: 'none',
                    style: 'expanded'
                },
                files: {
                    '<%=buildCssFilePath %>': '<%= srcMainStyleFilePath %>'
                }
            },

            'release-min': {
                options: {
                    noCache: true,
                    sourcemap: 'none',
                    style: 'compressed'
                },
                files: {
                    '<%= buildMinCssFilePath %>': '<%= srcMainStyleFilePath %>'
                }
            }
        },

        watch: {
            gruntfile: {
                files: ['Gruntfile.js']
            },
            scripts: {
                files: ['<%= srcScriptFilePaths %>'],
                tasks: ['concat:debug', 'uglify:debug']
            },
            styles: {
                files: ['<%= srcStyleFilePaths %>'],
                tasks: ['sass:debug']
            }
        },

        bower: {
            cleanup: {
                options: {
                    targetDir: '<%= libDir %>',
                    cleanTargetDir: true,
                    cleanBowerDir: true,
                    install: false,
                    copy: false
                }
            },
            install: {
                options: {
                    targetDir: '<%= libDir %>',
                    layout: 'byComponent',
                    install: true,
                    copy: false
                }
            },
            refreshLib: {
                options: {
                    targetDir: '<%= libDir %>',
                    layout: 'byComponent',
                    install: false,
                    copy: true
                }
            }
        }
    });

    grunt.registerTask('init', ['bower:install', 'githooks']);
    grunt.registerTask('start', ['init', 'watch']);

    grunt.registerTask('default', ['clean', 'test', 'docs']);

    grunt.registerTask('check', ['lintspaces', 'jscs', 'jshint']);
    grunt.registerTask('check-new', ['newer:lintspaces', 'newer:jscs', 'newer:jshint']);
    grunt.registerTask('test', ['check', 'clean:tests', 'build-debug', 'qunit']);

    grunt.registerTask('build', ['build-debug']);

    // build with MAP files
    grunt.registerTask('build-debug', ['clean:build', 'concat:debug', 'uglify:debug', 'sass:debug']);

    // build without MAP files
    grunt.registerTask('build-release', ['clean:build', 'concat:release', 'uglify:release', 'sass:release',
        'sass:release-min']);

    grunt.registerTask('docs', ['clean:docs', 'jsdoc']);

    /* Usage:    grunt release --tag=v1.0.0 --title="First release" --desc="Release description"
     * or:       grunt release --tag=v1.0.1rc (auto title and description)
     * or just:  grunt release (tag from package.json->version)
     *
     * NOTE: for github-release task you need GH_TOKEN environment variable.
     *       Put once in cmd: SET GH_TOKEN=<YOUR GITHUB TOKEN>
     */
    grunt.registerTask('release', ['release-local', 'github-release', 'clean:release']);
    grunt.registerTask('release-local', ['clean:release', 'build-release', 'compress:release', 'compress:release-min']);

    grunt.registerTask('publish', ['check', 'build-release', 'docs', 'gh-pages:publish']);

    grunt.registerTask('travis', 'Travis CI build task', function() {
        if (process.env.TRAVIS_REPO_SLUG === undefined && !grunt.option('pleaserun')) {
            throw new Error(
                'Task "travis" is not intended to execute in the environment other than Travis CI. ' +
                'Use --pleaserun to run anyway.'
            );
        }

        grunt.task.run(['check', 'build-release', 'bower:install', 'docs', 'gh-pages:deploy', 'qunit', 'coveralls']);
    });

    grunt.registerTask('bower:removeLib', 'Remove bower components from library folder.', function() {
        var rimraf = require('rimraf'),
            path = require('path'),
            fs = require('fs'),
            dir = grunt.config('libDir'),
            cmpArg = grunt.option('components'),
            rmpaths;

        if (!cmpArg) {
            rmpaths = [dir];
        } else {
            rmpaths = cmpArg.split(' ').map(function(x) {
                return path.join(dir, x);
            });
        }

        rmpaths.forEach(function(rmpath) {
            var msg = 'Remove ' + rmpath;
            if (fs.existsSync(rmpath)) {
                try {
                    rimraf.sync(rmpath);
                    grunt.log.ok(msg);
                }
                catch (ex) {
                    grunt.log.error(msg);
                    throw ex;
                }
            } else {
                grunt.log.error(msg + ' NOTFOUND');
            }
        });
    });

    grunt.registerTask('mycustomtask', 'My custom task.', function() {
        // http://gruntjs.com/creating-tasks#custom-tasks
        grunt.log.writeln('Currently running my custom task.');

        // grunt.task.run('bar', 'baz');
        // Or:
        // grunt.task.run(['bar', 'baz']);

        // Use task args (http://gruntjs.com/api/grunt.option):
        // grunt mycustomtask --opt
        // grunt.option('opt')
    });
};
<|endoftext|>


<commit_before>import { html } from 'lit-element';
import sortAscendingIcon from '../assets/icon_sort_ascending.js';
import sortDescendingIcon from '../assets/icon_sort_descending.js';

import './volumes.js';

export default class VolumesProvider {

  constructor(optionChange, baseHost, bookreader) {
    this.optionChange = optionChange
    this.component = document.createElement('viewable-files');

    const files = bookreader.options.multipleBooksList?.by_subprefix
    this.viewableFiles = Object.keys(files).map(item => files[item]);
    this.volumeCount = Object.keys(files).length;
    this.isFirstLoad = true;
    this.isSortAscending = false;

    this.component.subPrefix = bookreader.options === undefined ? '' : bookreader.options.subPrefix;
    this.component.hostUrl = baseHost;
    this.component.viewableFiles = this.viewableFiles;

    this.id = 'volumes';
    this.label = `Viewable Files (${this.volumeCount})`
    this.icon = html`<ia-icon icon="volumes" style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon>`;
    this.actionButton = this.headerIcon;
    this.sortVolumes();
  }

  get sortAscendingIcon() {
    const iconStyle = 'background: transparent; border: none; position: absolute; right: 25px';
    return html`<button style=${iconStyle} class="sort-asc icon" aria-label="Sort volumes in ascending order" @click=${() => this.sortVolumes()}>${sortAscendingIcon}</button>`;
  }

  get sortDescendingIcon() {
    const iconStyle = 'background: transparent; border: none; position: absolute; right: 25px';
    return html`<button style=${iconStyle} class="sort-desc icon" aria-label="Sort volumes in descending order" @click=${() => this.sortVolumes()}>${sortDescendingIcon}</button>`;
  }

  get headerIcon() {
    return this.isSortAscending ? this.sortAscendingIcon : this.sortDescendingIcon;
  }

  sortVolumes() {
    this.isSortAscending = !this.isSortAscending;
    const sortedFiles = this.viewableFiles.sort((a, b) => {
      if (this.isSortAscending) return a.title.localeCompare(b.title);
      else return b.title.localeCompare(a.title);
    })

    this.component.viewableFiles  = [...sortedFiles]
    if (!this.isFirstLoad) {
      this.actionButton = this.headerIcon;
      this.optionChange(this.bookreader);
    } else {
      this.isFirstLoad = false;
    }
  }

  bindEvents() {
    this.component.addEventListener('click', this.multiplefilesClicked.bind(this));
  }

  multiplefilesClicked({ detail }) {
    // maybe some analytics?
    if (window.archive_analytics) {
      window.archive_analytics?.send_event_no_sampling(
        'BookReader',
        `VolumesSort`,
        window.location.path,
      );
    }
  }

}
<commit_msg>Add semicolons<commit_after>import { html } from 'lit-element';
import sortAscendingIcon from '../assets/icon_sort_ascending.js';
import sortDescendingIcon from '../assets/icon_sort_descending.js';

import './volumes.js';

export default class VolumesProvider {

  constructor(optionChange, baseHost, bookreader) {
    this.optionChange = optionChange
    this.component = document.createElement('viewable-files');

    const files = bookreader.options.multipleBooksList?.by_subprefix
    this.viewableFiles = Object.keys(files).map(item => files[item]);
    this.volumeCount = Object.keys(files).length;
    this.isFirstLoad = true;
    this.isSortAscending = false;

    this.component.subPrefix = bookreader.options === undefined ? '' : bookreader.options.subPrefix;
    this.component.hostUrl = baseHost;
    this.component.viewableFiles = this.viewableFiles;

    this.id = 'volumes';
    this.label = `Viewable Files (${this.volumeCount})`
    this.icon = html`<ia-icon icon="volumes" style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon>`;
    this.actionButton = this.headerIcon;
    this.sortVolumes();
  }

  get sortAscendingIcon() {
    const iconStyle = 'background: transparent; border: none; position: absolute; right: 25px';
    return html`<button style=${iconStyle} class="sort-asc icon" aria-label="Sort volumes in ascending order" @click=${() => this.sortVolumes()}>${sortAscendingIcon}</button>`;
  }

  get sortDescendingIcon() {
    const iconStyle = 'background: transparent; border: none; position: absolute; right: 25px';
    return html`<button style=${iconStyle} class="sort-desc icon" aria-label="Sort volumes in descending order" @click=${() => this.sortVolumes()}>${sortDescendingIcon}</button>`;
  }

  get headerIcon() {
    return this.isSortAscending ? this.sortAscendingIcon : this.sortDescendingIcon;
  }

  sortVolumes() {
    this.isSortAscending = !this.isSortAscending;
    const sortedFiles = this.viewableFiles.sort((a, b) => {
      if (this.isSortAscending) return a.title.localeCompare(b.title);
      else return b.title.localeCompare(a.title);
    });

    this.component.viewableFiles  = [...sortedFiles];
    if (!this.isFirstLoad) {
      this.actionButton = this.headerIcon;
      this.optionChange(this.bookreader);
    } else {
      this.isFirstLoad = false;
    }
  }

  bindEvents() {
    this.component.addEventListener('click', this.multiplefilesClicked.bind(this));
  }

  multiplefilesClicked({ detail }) {
    // maybe some analytics?
    if (window.archive_analytics) {
      window.archive_analytics?.send_event_no_sampling(
        'BookReader',
        `VolumesSort`,
        window.location.path,
      );
    }
  }

}
<|endoftext|>


<commit_before>var Stacker = new Class({

	Implements: [Options, Events],

	options: {
		zIndexBase: 0,
		offset: {
			x: 20,
			y: 10
		}
	},

	instances: [],

	initialize: function(options) {
		this.setOptions(options);
		this.layers = $H();
		this.setLayer('default', this.options.zIndexBase);
	},

	setLayer: function(name, zIndex) {
		if (this.layers[name]) {
			this.setZIndex(zIndex, name);
			return this.getLayer(name);
		}
		this.layers[name] = {
			zIndex: zIndex,
			name: name,
			instances: []
		};
	},

	getLayer: function(name, defaultZIndex) {
		if (!this.layers[name] && defaultZIndex) this.addLayer(name, defaultZIndex);
		return this.layers[name];
	},

	register: function(instance, layer){
		layer = layer || 'default';
		var registered = this.instances.contains(instance);
		if (registered) {
			var instanceLayer = this.getLayerForInstance(instance);
			if (instanceLayer == layer) return;
			instanceLayer.instances.erase(instance);
		} else  {
			$(instance).addEvent('mousedown', function(){
				if (instance.disabled) this.enable(instance);
			}.bind(this));
			this.instances.push(instance);
		}
		this.layers[layer].instances.push(instance);
	},

	unregister: function(instance) {
		this.instances.erase(instance);
		this.layers.each(function(layer) {
			if (layer.instances.contains(instance)) layer.instances.erase(instance);
		});
	},

	getLayerForInstance: function(instance) {
		var ret;
		this.layers.each(function(layer) {
			if (layer.instances.contains(instance)) ret = layer;
		});
		return ret;
	},

	enable: function(instance){
		if (!instance) return;
		this.instances.erase(instance).push(instance);
		this.layers.each(function(layer) {
			var i = 0;
			if (!layer.instances.contains(instance)) return;
			layer.instances.erase(instance).push(instance);
			layer.instances.each(function(current){
				$(current).setStyle('z-index', layer.zIndex + (i*2));
				if (current === instance) current.enable(true);
				i++;
			}, this);
		}, this);
		if (this.enabled && this.enabled != instance) this.enabled.disable();
		this.enabled = instance;
	},

	cascade: function(noAnim, x, y){
		x = $pick(x, this.options.offset.x);
		y = $pick(y, this.options.offset.y);
		this.instances.each(function(current, i){
			var styles = {top: (y * i) + y, left: (x * i) + x};
			(noAnim || !current.morph) ? $(current).setStyles(styles) : current.morph.start(styles);
		});
	},

	positionNew: function(instance, options){
		var pos = true;
		//if there are no instances other than this one, or one instance
		//or the position is not set or is equal to the enabled instances options
		//then return; and let the window be positioned as the class would normally.
		var current;
		var instances = this.instances.filter(function(instance){
			return !instance.hidden && $(instance);
		});
		instances.reverse().some(function(win){
			if (win != instance && $(win) && $(win).getStyle('display') != 'none') {
				current = win;
				return true;
			}
		});
		if (options) {
			//if the position is not defined in the options
			//or, if it is, the position is the same as the enabled instance's options
			//such that opening with the same options will place them on top of each other
			//(assuming the first one hasn't moved)
			pos = ['top', 'left', 'edge', 'position', 'relativeTo'].every(function(opt){
				curOpt = current ? current.options[opt] : null;
				return curOpt == options[opt];
			}, this);
		}
		this.enable(instance);
		if (instances.length < 1 || !pos || !current) return false;
		//position near the enabled instance, with an offset as defined in the options
		$(instance).position({
			relativeTo: $(current),
			offset: this.options.offset,
			edge: 'upperLeft',
			position: 'upperLeft'
		});
		pos = instance.element.getPosition();
		var size = instance.element.getSize();
		var bottom = pos.y + size.y;
		var right = pos.x + size.x;
		var containerSize = $(document.body).getSize();
		if (bottom > containerSize.y || right > containerSize.x) {
			$(instance).position({
				relativeTo: instance.options.inject.target,
				offset: this.options.offset,
				edge: 'upperLeft',
				position: 'upperLeft'
			});
		}

		return true;
	}

});
<commit_msg>fixing a few flaws in stacker layers...	<commit_after>var Stacker = new Class({

	Implements: [Options, Events],

	options: {
		zIndexBase: 0,
		offset: {
			x: 20,
			y: 10
		}
	},

	instances: [],

	initialize: function(options) {
		this.setOptions(options);
		this.layers = $H();
		this.setLayer('default', this.options.zIndexBase);
	},

	setLayer: function(name, zIndex) {
		if (this.layers[name]) {
			this.setZIndex(zIndex, name);
			return this.getLayer(name);
		}
		this.layers[name] = {
			zIndex: zIndex,
			name: name,
			instances: []
		};
	},

	getLayer: function(name, defaultZIndex) {
		if (!this.layers[name] && defaultZIndex != undefined) this.setLayer(name, defaultZIndex);
		return this.layers[name];
	},

	register: function(instance, layer){
		layer = layer || 'default';
		var registered = this.instances.contains(instance);
		if (registered && this.getLayerForInstance(instance)) {
			var instanceLayer = this.getLayerForInstance(instance);
			if (instanceLayer == layer) return;
			instanceLayer.instances.erase(instance);
		} else  {
			$(instance).addEvent('mousedown', function(){
				if (instance.disabled) this.enable(instance);
			}.bind(this));
			this.instances.push(instance);
		}
		if (!this.layers[layer]) this.getLayer(layer, this.options.zIndexBase);
		this.layers[layer].instances.push(instance);
	},

	unregister: function(instance) {
		this.instances.erase(instance);
		this.layers.each(function(layer) {
			if (layer.instances.contains(instance)) layer.instances.erase(instance);
		});
	},

	getLayerForInstance: function(instance) {
		var ret;
		this.layers.each(function(layer) {
			if (layer.instances.contains(instance)) ret = layer;
		});
		return ret;
	},

	enable: function(instance){
		if (!instance) return;
		this.instances.erase(instance).push(instance);
		this.layers.each(function(layer) {
			var i = 0;
			if (!layer.instances.contains(instance)) return;
			layer.instances.erase(instance).push(instance);
			layer.instances.each(function(current){
				$(current).setStyle('z-index', layer.zIndex + (i*2));
				if (current === instance) current.enable(true);
				i++;
			}, this);
		}, this);
		if (this.enabled && this.enabled != instance) this.enabled.disable();
		this.enabled = instance;
	},

	cascade: function(noAnim, x, y){
		x = $pick(x, this.options.offset.x);
		y = $pick(y, this.options.offset.y);
		this.instances.each(function(current, i){
			var styles = {top: (y * i) + y, left: (x * i) + x};
			(noAnim || !current.morph) ? $(current).setStyles(styles) : current.morph.start(styles);
		});
	},

	positionNew: function(instance, options){
		var pos = true;
		//if there are no instances other than this one, or one instance
		//or the position is not set or is equal to the enabled instances options
		//then return; and let the window be positioned as the class would normally.
		var current;
		var instances = this.instances.filter(function(instance){
			return !instance.hidden && $(instance);
		});
		instances.reverse().some(function(win){
			if (win != instance && $(win) && $(win).getStyle('display') != 'none') {
				current = win;
				return true;
			}
		});
		if (options) {
			//if the position is not defined in the options
			//or, if it is, the position is the same as the enabled instance's options
			//such that opening with the same options will place them on top of each other
			//(assuming the first one hasn't moved)
			pos = ['top', 'left', 'edge', 'position', 'relativeTo'].every(function(opt){
				curOpt = current ? current.options[opt] : null;
				return curOpt == options[opt];
			}, this);
		}
		this.enable(instance);
		if (instances.length < 1 || !pos || !current) return false;
		//position near the enabled instance, with an offset as defined in the options
		$(instance).position({
			relativeTo: $(current),
			offset: this.options.offset,
			edge: 'upperLeft',
			position: 'upperLeft'
		});
		pos = instance.element.getPosition();
		var size = instance.element.getSize();
		var bottom = pos.y + size.y;
		var right = pos.x + size.x;
		var containerSize = $(document.body).getSize();
		if (bottom > containerSize.y || right > containerSize.x) {
			$(instance).position({
				relativeTo: instance.options.inject.target,
				offset: this.options.offset,
				edge: 'upperLeft',
				position: 'upperLeft'
			});
		}

		return true;
	}

});
<|endoftext|>


<commit_before>import emitter from 'mitt';
import audioPool from './utils/audio-pool';
import feature from './utils/feature';
import { sleep } from './utils/async';

const AudioContext = window.AudioContext || window.webkitAudioContext;

let context;
let source;
let gainNode;
let loopCount;
let duration = 0;
let lastTime = 0;
let startTime;
let audioElement;
let request;
let onCanPlayThrough;

const FADE_OUT_SECONDS = 2;
const ALMOST_ZERO = 1e-4;

let scheduledTime;

const audio = Object.assign(emitter(), {
  tick() {
    if (!audioElement && !context) return;
    this.currentTime = audioElement
      ? audioElement.currentTime
      : (context.currentTime - startTime);
    const time = this.time = this.currentTime % duration;

    const { loopDuration } = this;
    // The position within the track as a multiple of loopDuration:
    this.progress = time / loopDuration;

    // The position within the individual loop as a value between 0 - 1:
    this.loopProgress = (time % loopDuration) / loopDuration;

    // True when the audio looped, false otherwise:
    this.looped = time < lastTime;

    if (this.looped) {
      // The index of the loop, used when the audio has more than one loops:
      this.loopIndex = Math.floor(this.progress * loopCount);
      this.loopCount++;
    }

    this.totalProgress = this.loopCount * loopCount + this.progress;
    lastTime = time;
  },

  load(param) {
    return new Promise(async (resolve, reject) => {
      if (context) context.close();
      context = new AudioContext();
      gainNode = context.createGain();
      // Reset time, set loop count
      lastTime = 0;
      loopCount = param.loops === undefined
        ? 1
        : param.loops;
      this.loopCount = 0;
      const canPlay = () => {
        this.duration = duration;
        this.loopDuration = duration / loopCount;
        startTime = context.currentTime;
        context.suspend();
        resolve(param.src);
      };

      if (param.loopOffset) {
        this.loopOffset = param.loopOffset;
      }

      if (param.progressive) {
        audioElement = feature.isMobile
          ? audioPool.get()
          : new Audio();
        audioElement.addEventListener('ended', () => audio.emit('ended'));
        audioElement.autoplay = true;
        audioElement.src = param.src;
        audioElement.loop = param.loop === undefined ? true : param.loop;
        onCanPlayThrough = () => {
          duration = audioElement.duration;
          canPlay();
          audioElement.removeEventListener('canplaythrough', onCanPlayThrough);
        };
        audioElement.addEventListener('canplaythrough', onCanPlayThrough);
        source = context.createMediaElementSource(audioElement);
      } else {
        source = context.createBufferSource();
        request = new XMLHttpRequest();
        request.open('GET', param.src, true);
        request.responseType = 'arraybuffer';
        request.onload = () => {
          context.decodeAudioData(
            request.response,
            (response) => {
              // Load the file into the buffer
              source.buffer = response;
              duration = source.buffer.duration;
              source.loop = param.loop === undefined ? true : param.loop;
              source.start(0);
              canPlay();
            },
            reject
          );
        };
        request.send();
      }
      source.connect(gainNode);
      gainNode.connect(context.destination);
    });
  },

  play() {
    if (context) context.resume();
    if (feature.isMobile) {
      audioElement.play();
    }
  },

  pause() {
    if (context) context.suspend();
  },

  gotoTime(time) {
    audioElement.currentTime = time;
  },

  reset() {
    audio.fadeOut();
    // Cancel loading of audioElement:
    if (audioElement) {
      audioElement.removeEventListener('canplaythrough', onCanPlayThrough);
      if (feature.isMobile) {
        audioPool.release(audioElement);
      }
      audioElement = null;
      onCanPlayThrough = null;
    }
    if (request) {
      request.abort();
      request.onload = null;
      request = null;
    }
  },

  rewind() {
    if (audioElement) {
      audioElement.currentTime = 0;
      gainNode.gain.value = 1;
    }
  },

  mute() {
    gainNode.gain.value = 0.001;
  },

  async fadeOut(fadeDuration = FADE_OUT_SECONDS) {
    if (!context) return;
    if (scheduledTime) {
      gainNode.gain.cancelScheduledValues(scheduledTime);
    }
    scheduledTime = context.currentTime;
    gainNode.gain.exponentialRampToValueAtTime(
      ALMOST_ZERO,
      scheduledTime + fadeDuration
    );
    return sleep(fadeDuration * 1000);
  },

  async fadeIn(fadeDuration = FADE_OUT_SECONDS) {
    if (!context) return;
    if (scheduledTime) {
      gainNode.gain.cancelScheduledValues(scheduledTime);
    }
    scheduledTime = context.currentTime;
    gainNode.gain.exponentialRampToValueAtTime(
      1,
      scheduledTime + fadeDuration
    );
    return sleep(fadeDuration * 1000);
  },

  time: 0,
  loopIndex: 0,
});

export default audio;
<commit_msg>use Date.now() for audio current time<commit_after>import emitter from 'mitt';
import audioPool from './utils/audio-pool';
import feature from './utils/feature';
import { sleep } from './utils/async';

const AudioContext = window.AudioContext || window.webkitAudioContext;

let context;
let source;
let gainNode;
let loopCount;
let duration = 0;
let lastTime = 0;
let startTime;
let audioElement;
let request;
let onCanPlayThrough;
let onPause;
let onPlay;
let pauseTime;

const FADE_OUT_SECONDS = 2;
const ALMOST_ZERO = 1e-4;

let scheduledTime;
const audio = Object.assign(emitter(), {
  tick() {
    if ((!audioElement && !context) || !startTime) return;
    this.currentTime = (pauseTime || (Date.now() - startTime)) / 1000;
    const time = this.time = this.currentTime % duration;

    const { loopDuration } = this;
    // The position within the track as a multiple of loopDuration:
    this.progress = time / loopDuration;

    // The position within the individual loop as a value between 0 - 1:
    this.loopProgress = (time % loopDuration) / loopDuration;

    // True when the audio looped, false otherwise:
    this.looped = time < lastTime;

    if (this.looped) {
      // The index of the loop, used when the audio has more than one loops:
      this.loopIndex = Math.floor(this.progress * loopCount);
      this.loopCount++;
    }

    this.totalProgress = this.loopCount * loopCount + this.progress;
    lastTime = time;
  },

  load(param) {
    return new Promise(async (resolve, reject) => {
      if (context) context.close();
      context = new AudioContext();
      gainNode = context.createGain();
      // Reset time, set loop count
      lastTime = 0;
      loopCount = param.loops === undefined
        ? 1
        : param.loops;
      this.loopCount = 0;
      const canPlay = () => {
        this.duration = duration;
        this.loopDuration = duration / loopCount;
        context.suspend();
        resolve(param.src);
      };

      if (param.loopOffset) {
        this.loopOffset = param.loopOffset;
      }

      if (param.progressive) {
        audioElement = feature.isMobile
          ? audioPool.get()
          : new Audio();
        audioElement.addEventListener('ended', () => audio.emit('ended'));
        audioElement.autoplay = true;
        audioElement.src = param.src;
        audioElement.loop = param.loop === undefined ? true : param.loop;
        onCanPlayThrough = () => {
          duration = audioElement.duration;
          canPlay();
          audioElement.removeEventListener('canplaythrough', onCanPlayThrough);
        };
        onPause = () => {
          this.paused = true;
          this.pauseTime = audioElement.currentTime * 1000;
        };
        onPlay = () => {
          startTime = Date.now() - audioElement.currentTime * 1000;
          this.paused = false;
          this.pauseTime = null;
        };
        audioElement.addEventListener('canplaythrough', onCanPlayThrough);
        audioElement.addEventListener('pause', onPause)
        audioElement.addEventListener('play', onPlay)
        source = context.createMediaElementSource(audioElement);
      } else {
        source = context.createBufferSource();
        request = new XMLHttpRequest();
        request.open('GET', param.src, true);
        request.responseType = 'arraybuffer';
        request.onload = () => {
          context.decodeAudioData(
            request.response,
            (response) => {
              // Load the file into the buffer
              source.buffer = response;
              duration = source.buffer.duration;
              source.loop = param.loop === undefined ? true : param.loop;
              source.start(0);
              canPlay();
            },
            reject
          );
        };
        request.send();
      }
      source.connect(gainNode);
      gainNode.connect(context.destination);
    });
  },

  play() {
    if (context) context.resume();
    if (feature.isMobile) {
      audioElement.play();
    }
  },

  pause() {
    if (context) context.suspend();
  },

  gotoTime(time) {
    audioElement.currentTime = time;
    startTime = Date.now() - time * 1000;
  },

  reset() {
    audio.fadeOut();
    // Cancel loading of audioElement:
    if (audioElement) {
      audioElement.removeEventListener('canplaythrough', onCanPlayThrough);
      audioElement.removeEventListener('pause', onPause);
      audioElement.removeEventListener('play', onPlay);
      if (feature.isMobile) {
        audioPool.release(audioElement);
      }
      audioElement = null;
      onCanPlayThrough = null;
    }
    if (request) {
      request.abort();
      request.onload = null;
      request = null;
    }
    pauseTime = startTime = null;
  },

  rewind() {
    if (audioElement) {
      audioElement.currentTime = 0;
      gainNode.gain.value = 1;
    }
  },

  mute() {
    gainNode.gain.value = 0.001;
  },

  async fadeOut(fadeDuration = FADE_OUT_SECONDS) {
    if (!context) return;
    if (scheduledTime) {
      gainNode.gain.cancelScheduledValues(scheduledTime);
    }
    scheduledTime = context.currentTime;
    gainNode.gain.exponentialRampToValueAtTime(
      ALMOST_ZERO,
      scheduledTime + fadeDuration
    );
    return sleep(fadeDuration * 1000);
  },

  async fadeIn(fadeDuration = FADE_OUT_SECONDS) {
    if (!context) return;
    if (scheduledTime) {
      gainNode.gain.cancelScheduledValues(scheduledTime);
    }
    scheduledTime = context.currentTime;
    gainNode.gain.exponentialRampToValueAtTime(
      1,
      scheduledTime + fadeDuration
    );
    return sleep(fadeDuration * 1000);
  },

  time: 0,
  loopIndex: 0,
});

export default audio;
<|endoftext|>


<commit_before>/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
 * http://scratchdisk.com/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * A function scope holding all the functionality needed to convert a
 * Paper.js DOM to a SVG DOM.
 */
new function() {
    // TODO: Consider moving formatter into options object, and pass it along.
    var formatter;

    function getTransform(matrix, coordinates, center) {
        // Use new Base() so we can use Base#set() on it.
        var attrs = new Base(),
            trans = matrix.getTranslation();
        if (coordinates) {
            // If the item suppports x- and y- coordinates, we're taking out the
            // translation part of the matrix and move it to x, y attributes, to
            // produce more readable markup, and not have to use center points
            // in rotate(). To do so, SVG requries us to inverse transform the
            // translation point by the matrix itself, since they are provided
            // in local coordinates.
            matrix = matrix._shiftless();
            var point = matrix._inverseTransform(trans);
            attrs[center ? 'cx' : 'x'] = point.x;
            attrs[center ? 'cy' : 'y'] = point.y;
            trans = null;
        }
        if (!matrix.isIdentity()) {
            // See if we can decompose the matrix and can formulate it as a
            // simple translate/scale/rotate command sequence.
            var decomposed = matrix.decompose();
            if (decomposed && !decomposed.shearing) {
                var parts = [],
                    angle = decomposed.rotation,
                    scale = decomposed.scaling;
                if (trans && !trans.isZero())
                    parts.push('translate(' + formatter.point(trans) + ')');
                if (!Numerical.isZero(scale.x - 1)
                        || !Numerical.isZero(scale.y - 1))
                    parts.push('scale(' + formatter.point(scale) +')');
                if (angle)
                    parts.push('rotate(' + formatter.number(angle) + ')');
                attrs.transform = parts.join(' ');
            } else {
                attrs.transform = 'matrix(' + matrix.getValues().join(',') + ')';
            }
        }
        return attrs;
    }

    function exportGroup(item, options) {
        var attrs = getTransform(item._matrix),
            children = item._children;
        var node = SvgElement.create('g', attrs, formatter);
        for (var i = 0, l = children.length; i < l; i++) {
            var child = children[i];
            var childNode = exportSVG(child, options);
            if (childNode) {
                if (child.isClipMask()) {
                    var clip =  SvgElement.create('clipPath');
                    clip.appendChild(childNode);
                    setDefinition(child, clip, 'clip');
                     SvgElement.set(node, {
                        'clip-path': 'url(#' + clip.id + ')'
                    });
                } else {
                    node.appendChild(childNode);
                }
            }
        }
        return node;
    }

    function exportRaster(item, options) {
        var attrs = getTransform(item._matrix, true),
            size = item.getSize(),
            image = item.getImage();
        // Take into account that rasters are centered:
        attrs.x -= size.width / 2;
        attrs.y -= size.height / 2;
        attrs.width = size.width;
        attrs.height = size.height;
        attrs.href = options.embedImages === false && image && image.src
                || item.toDataURL();
        return SvgElement.create('image', attrs, formatter);
    }

    function exportPath(item, options) {
        var matchShapes = options.matchShapes;
        if (matchShapes) {
            var shape = item.toShape(false);
            if (shape)
                return exportShape(shape, options);
        }
        var segments = item._segments,
            type,
            attrs = getTransform(item._matrix);
        if (segments.length === 0)
            return null;
        if (matchShapes && !item.hasHandles()) {
            if (segments.length >= 3) {
                type = item._closed ? 'polygon' : 'polyline';
                var parts = [];
                for(var i = 0, l = segments.length; i < l; i++)
                    parts.push(formatter.point(segments[i]._point));
                attrs.points = parts.join(' ');
            } else {
                type = 'line';
                var first = segments[0]._point,
                    last = segments[segments.length - 1]._point;
                attrs.set({
                    x1: first.x,
                    y1: first.y,
                    x2: last.x,
                    y2: last.y
                });
            }
        } else {
            type = 'path';
            attrs.d = item.getPathData(null, options.precision);
        }
        return SvgElement.create(type, attrs, formatter);
    }

    function exportShape(item) {
        var type = item._type,
            radius = item._radius,
            attrs = getTransform(item._matrix, true, type !== 'rectangle');
        if (type === 'rectangle') {
            type = 'rect'; // SVG
            var size = item._size,
                width = size.width,
                height = size.height;
            attrs.x -= width / 2;
            attrs.y -= height / 2;
            attrs.width = width;
            attrs.height = height;
            if (radius.isZero())
                radius = null;
        }
        if (radius) {
            if (type === 'circle') {
                attrs.r = radius;
            } else {
                attrs.rx = radius.width;
                attrs.ry = radius.height;
            }
        }
        return SvgElement.create(type, attrs, formatter);
    }

    function exportCompoundPath(item, options) {
        var attrs = getTransform(item._matrix);
        var data = item.getPathData(null, options.precision);
        if (data)
            attrs.d = data;
        return SvgElement.create('path', attrs, formatter);
    }

    function exportSymbolItem(item, options) {
        var attrs = getTransform(item._matrix, true),
            definition = item._definition,
            node = getDefinition(definition, 'symbol'),
            definitionItem = definition._item,
            bounds = definitionItem.getBounds();
        if (!node) {
            node = SvgElement.create('symbol', {
                viewBox: formatter.rectangle(bounds)
            });
            node.appendChild(exportSVG(definitionItem, options));
            setDefinition(definition, node, 'symbol');
        }
        attrs.href = '#' + node.id;
        attrs.x += bounds.x;
        attrs.y += bounds.y;
        attrs.width = bounds.width;
        attrs.height = bounds.height;
        attrs.overflow = 'visible';
        return SvgElement.create('use', attrs, formatter);
    }

    function exportGradient(color) {
        // NOTE: As long as the fillTransform attribute is not implemented,
        // we need to create a separate gradient object for each gradient,
        // even when they share the same gradient defintion.
        // http://www.svgopen.org/2011/papers/20-Separating_gradients_from_geometry/
        // TODO: Implement gradient merging in SvgImport
        var gradientNode = getDefinition(color, 'color');
        if (!gradientNode) {
            var gradient = color.getGradient(),
                radial = gradient._radial,
                origin = color.getOrigin().transform(),
                destination = color.getDestination().transform(),
                attrs;
            if (radial) {
                attrs = {
                    cx: origin.x,
                    cy: origin.y,
                    r: origin.getDistance(destination)
                };
                var highlight = color.getHighlight();
                if (highlight) {
                    highlight = highlight.transform();
                    attrs.fx = highlight.x;
                    attrs.fy = highlight.y;
                }
            } else {
                attrs = {
                    x1: origin.x,
                    y1: origin.y,
                    x2: destination.x,
                    y2: destination.y
                };
            }
            attrs.gradientUnits = 'userSpaceOnUse';
            gradientNode = SvgElement.create((radial ? 'radial' : 'linear')
                    + 'Gradient', attrs, formatter);
            var stops = gradient._stops;
            for (var i = 0, l = stops.length; i < l; i++) {
                var stop = stops[i],
                    stopColor = stop._color,
                    alpha = stopColor.getAlpha();
                attrs = {
                    offset: stop._rampPoint,
                    'stop-color': stopColor.toCSS(true)
                };
                // See applyStyle for an explanation of why there are separated
                // opacity / color attributes.
                if (alpha < 1)
                    attrs['stop-opacity'] = alpha;
                gradientNode.appendChild(
                        SvgElement.create('stop', attrs, formatter));
            }
            setDefinition(color, gradientNode, 'color');
        }
        return 'url(#' + gradientNode.id + ')';
    }

    function exportText(item) {
        var node = SvgElement.create('text', getTransform(item._matrix, true),
                formatter);
        node.textContent = item._content;
        return node;
    }

    var exporters = {
        Group: exportGroup,
        Layer: exportGroup,
        Raster: exportRaster,
        Path: exportPath,
        Shape: exportShape,
        CompoundPath: exportCompoundPath,
        SymbolItem: exportSymbolItem,
        PointText: exportText
    };

    function applyStyle(item, node, isRoot) {
        var attrs = {},
            parent = !isRoot && item.getParent(),
            style = [];

        if (item._name != null)
            attrs.id = item._name;

        Base.each(SvgStyles, function(entry) {
            // Get a given style only if it differs from the value on the parent
            // (A layer or group which can have style values in SVG).
            var get = entry.get,
                type = entry.type,
                value = item[get]();
            if (entry.exportFilter
                    ? entry.exportFilter(item, value)
                    : !parent || !Base.equals(parent[get](), value)) {
                if (type === 'color' && value != null) {
                    // Support for css-style rgba() values is not in SVG 1.1, so
                    // separate the alpha value of colors with alpha into the
                    // separate fill- / stroke-opacity attribute:
                    var alpha = value.getAlpha();
                    if (alpha < 1)
                        attrs[entry.attribute + '-opacity'] = alpha;
                }
                if (type === 'style') {
                    style.push(entry.attribute + ': ' + value);
                } else {
                    attrs[entry.attribute] = value == null ? 'none'
                            : type === 'color' ? value.gradient
                                // true for noAlpha, see above
                                ? exportGradient(value, item)
                                : value.toCSS(true)
                            : type === 'array' ? value.join(',')
                            : type === 'lookup' ? entry.toSVG[value]
                            : value;
                }
            }
        });

        if (style.length)
            attrs.style = style.join(';');

        if (attrs.opacity === 1)
            delete attrs.opacity;

        if (!item._visible)
            attrs.visibility = 'hidden';

        return  SvgElement.set(node, attrs, formatter);
    }

    var definitions;
    function getDefinition(item, type) {
        if (!definitions)
            definitions = { ids: {}, svgs: {} };
        return item && definitions.svgs[type + '-' + item._id];
    }

    function setDefinition(item, node, type) {
        // Make sure the definitions lookup is created before we use it.
        // This is required by 'clip', where getDefinition() is not called.
        if (!definitions)
            getDefinition();
        // Have different id ranges per type
        var id = definitions.ids[type] = (definitions.ids[type] || 0) + 1;
        // Give the svg node an id, and link to it from the item id.
        node.id = type + '-' + id;
        definitions.svgs[type + '-' + item._id] = node;
    }

    function exportDefinitions(node, options) {
        var svg = node,
            defs = null;
        if (definitions) {
            // We can only use svg nodes as defintion containers. Have the loop
            // produce one if it's a single item of another type (when calling
            // #exportSVG() on an item rather than a whole project)
            // jsdom in Node.js uses uppercase values for nodeName...
            svg = node.nodeName.toLowerCase() === 'svg' && node;
            for (var i in definitions.svgs) {
                // This code is inside the loop so we only create a container if
                // we actually have svgs.
                if (!defs) {
                    if (!svg) {
                        svg = SvgElement.create('svg');
                        svg.appendChild(node);
                    }
                    defs = svg.insertBefore(SvgElement.create('defs'),
                            svg.firstChild);
                }
                defs.appendChild(definitions.svgs[i]);
            }
            // Clear definitions at the end of export
            definitions = null;
        }
        return options.asString
                ? new window.XMLSerializer().serializeToString(svg)
                : svg;
    }

    function exportSVG(item, options, isRoot) {
        var exporter = exporters[item._class],
            node = exporter && exporter(item, options);
        if (node) {
            // Support onExportItem callback, to provide mechanism to handle
            // special attributes (e.g. inkscape:transform-center)
            var onExport = options.onExport;
            if (onExport)
                node = onExport(item, node, options) || node;
            var data = JSON.stringify(item._data);
            if (data && data !== '{}' && data !== 'null')
                node.setAttribute('data-paper-data', data);
        }
        return node && applyStyle(item, node, isRoot);
    }

    function setOptions(options) {
        if (!options)
            options = {};
        formatter = new Formatter(options.precision);
        return options;
    }

    Item.inject({
        exportSVG: function(options) {
            options = setOptions(options);
            return exportDefinitions(exportSVG(this, options, true), options);
        }
    });

    Project.inject({
        exportSVG: function(options) {
            options = setOptions(options);
            var children = this._children,
                view = this.getView(),
                size = view.getViewSize(),
                node = SvgElement.create('svg', {
                    x: 0,
                    y: 0,
                    width: size.width,
                    height: size.height,
                    version: '1.1',
                    xmlns:  SvgElement.xmlns,
                    'xmlns:xlink':  SvgElement.xlink
                }, formatter),
                parent = node,
                matrix = view._matrix;
            // If the view has a transformation, wrap all layers in a group with
            // that transformation applied to.
            if (!matrix.isIdentity())
                parent = node.appendChild(SvgElement.create('g',
                        getTransform(matrix), formatter));
            for (var i = 0, l = children.length; i < l; i++)
                parent.appendChild(exportSVG(children[i], options, true));
            return exportDefinitions(node, options);
        }
    });
};
<commit_msg>Use correct SVG namespace again.<commit_after>/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
 * http://scratchdisk.com/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * A function scope holding all the functionality needed to convert a
 * Paper.js DOM to a SVG DOM.
 */
new function() {
    // TODO: Consider moving formatter into options object, and pass it along.
    var formatter;

    function getTransform(matrix, coordinates, center) {
        // Use new Base() so we can use Base#set() on it.
        var attrs = new Base(),
            trans = matrix.getTranslation();
        if (coordinates) {
            // If the item suppports x- and y- coordinates, we're taking out the
            // translation part of the matrix and move it to x, y attributes, to
            // produce more readable markup, and not have to use center points
            // in rotate(). To do so, SVG requries us to inverse transform the
            // translation point by the matrix itself, since they are provided
            // in local coordinates.
            matrix = matrix._shiftless();
            var point = matrix._inverseTransform(trans);
            attrs[center ? 'cx' : 'x'] = point.x;
            attrs[center ? 'cy' : 'y'] = point.y;
            trans = null;
        }
        if (!matrix.isIdentity()) {
            // See if we can decompose the matrix and can formulate it as a
            // simple translate/scale/rotate command sequence.
            var decomposed = matrix.decompose();
            if (decomposed && !decomposed.shearing) {
                var parts = [],
                    angle = decomposed.rotation,
                    scale = decomposed.scaling;
                if (trans && !trans.isZero())
                    parts.push('translate(' + formatter.point(trans) + ')');
                if (!Numerical.isZero(scale.x - 1)
                        || !Numerical.isZero(scale.y - 1))
                    parts.push('scale(' + formatter.point(scale) +')');
                if (angle)
                    parts.push('rotate(' + formatter.number(angle) + ')');
                attrs.transform = parts.join(' ');
            } else {
                attrs.transform = 'matrix(' + matrix.getValues().join(',') + ')';
            }
        }
        return attrs;
    }

    function exportGroup(item, options) {
        var attrs = getTransform(item._matrix),
            children = item._children;
        var node = SvgElement.create('g', attrs, formatter);
        for (var i = 0, l = children.length; i < l; i++) {
            var child = children[i];
            var childNode = exportSVG(child, options);
            if (childNode) {
                if (child.isClipMask()) {
                    var clip =  SvgElement.create('clipPath');
                    clip.appendChild(childNode);
                    setDefinition(child, clip, 'clip');
                     SvgElement.set(node, {
                        'clip-path': 'url(#' + clip.id + ')'
                    });
                } else {
                    node.appendChild(childNode);
                }
            }
        }
        return node;
    }

    function exportRaster(item, options) {
        var attrs = getTransform(item._matrix, true),
            size = item.getSize(),
            image = item.getImage();
        // Take into account that rasters are centered:
        attrs.x -= size.width / 2;
        attrs.y -= size.height / 2;
        attrs.width = size.width;
        attrs.height = size.height;
        attrs.href = options.embedImages === false && image && image.src
                || item.toDataURL();
        return SvgElement.create('image', attrs, formatter);
    }

    function exportPath(item, options) {
        var matchShapes = options.matchShapes;
        if (matchShapes) {
            var shape = item.toShape(false);
            if (shape)
                return exportShape(shape, options);
        }
        var segments = item._segments,
            type,
            attrs = getTransform(item._matrix);
        if (segments.length === 0)
            return null;
        if (matchShapes && !item.hasHandles()) {
            if (segments.length >= 3) {
                type = item._closed ? 'polygon' : 'polyline';
                var parts = [];
                for(var i = 0, l = segments.length; i < l; i++)
                    parts.push(formatter.point(segments[i]._point));
                attrs.points = parts.join(' ');
            } else {
                type = 'line';
                var first = segments[0]._point,
                    last = segments[segments.length - 1]._point;
                attrs.set({
                    x1: first.x,
                    y1: first.y,
                    x2: last.x,
                    y2: last.y
                });
            }
        } else {
            type = 'path';
            attrs.d = item.getPathData(null, options.precision);
        }
        return SvgElement.create(type, attrs, formatter);
    }

    function exportShape(item) {
        var type = item._type,
            radius = item._radius,
            attrs = getTransform(item._matrix, true, type !== 'rectangle');
        if (type === 'rectangle') {
            type = 'rect'; // SVG
            var size = item._size,
                width = size.width,
                height = size.height;
            attrs.x -= width / 2;
            attrs.y -= height / 2;
            attrs.width = width;
            attrs.height = height;
            if (radius.isZero())
                radius = null;
        }
        if (radius) {
            if (type === 'circle') {
                attrs.r = radius;
            } else {
                attrs.rx = radius.width;
                attrs.ry = radius.height;
            }
        }
        return SvgElement.create(type, attrs, formatter);
    }

    function exportCompoundPath(item, options) {
        var attrs = getTransform(item._matrix);
        var data = item.getPathData(null, options.precision);
        if (data)
            attrs.d = data;
        return SvgElement.create('path', attrs, formatter);
    }

    function exportSymbolItem(item, options) {
        var attrs = getTransform(item._matrix, true),
            definition = item._definition,
            node = getDefinition(definition, 'symbol'),
            definitionItem = definition._item,
            bounds = definitionItem.getBounds();
        if (!node) {
            node = SvgElement.create('symbol', {
                viewBox: formatter.rectangle(bounds)
            });
            node.appendChild(exportSVG(definitionItem, options));
            setDefinition(definition, node, 'symbol');
        }
        attrs.href = '#' + node.id;
        attrs.x += bounds.x;
        attrs.y += bounds.y;
        attrs.width = bounds.width;
        attrs.height = bounds.height;
        attrs.overflow = 'visible';
        return SvgElement.create('use', attrs, formatter);
    }

    function exportGradient(color) {
        // NOTE: As long as the fillTransform attribute is not implemented,
        // we need to create a separate gradient object for each gradient,
        // even when they share the same gradient defintion.
        // http://www.svgopen.org/2011/papers/20-Separating_gradients_from_geometry/
        // TODO: Implement gradient merging in SvgImport
        var gradientNode = getDefinition(color, 'color');
        if (!gradientNode) {
            var gradient = color.getGradient(),
                radial = gradient._radial,
                origin = color.getOrigin().transform(),
                destination = color.getDestination().transform(),
                attrs;
            if (radial) {
                attrs = {
                    cx: origin.x,
                    cy: origin.y,
                    r: origin.getDistance(destination)
                };
                var highlight = color.getHighlight();
                if (highlight) {
                    highlight = highlight.transform();
                    attrs.fx = highlight.x;
                    attrs.fy = highlight.y;
                }
            } else {
                attrs = {
                    x1: origin.x,
                    y1: origin.y,
                    x2: destination.x,
                    y2: destination.y
                };
            }
            attrs.gradientUnits = 'userSpaceOnUse';
            gradientNode = SvgElement.create((radial ? 'radial' : 'linear')
                    + 'Gradient', attrs, formatter);
            var stops = gradient._stops;
            for (var i = 0, l = stops.length; i < l; i++) {
                var stop = stops[i],
                    stopColor = stop._color,
                    alpha = stopColor.getAlpha();
                attrs = {
                    offset: stop._rampPoint,
                    'stop-color': stopColor.toCSS(true)
                };
                // See applyStyle for an explanation of why there are separated
                // opacity / color attributes.
                if (alpha < 1)
                    attrs['stop-opacity'] = alpha;
                gradientNode.appendChild(
                        SvgElement.create('stop', attrs, formatter));
            }
            setDefinition(color, gradientNode, 'color');
        }
        return 'url(#' + gradientNode.id + ')';
    }

    function exportText(item) {
        var node = SvgElement.create('text', getTransform(item._matrix, true),
                formatter);
        node.textContent = item._content;
        return node;
    }

    var exporters = {
        Group: exportGroup,
        Layer: exportGroup,
        Raster: exportRaster,
        Path: exportPath,
        Shape: exportShape,
        CompoundPath: exportCompoundPath,
        SymbolItem: exportSymbolItem,
        PointText: exportText
    };

    function applyStyle(item, node, isRoot) {
        var attrs = {},
            parent = !isRoot && item.getParent(),
            style = [];

        if (item._name != null)
            attrs.id = item._name;

        Base.each(SvgStyles, function(entry) {
            // Get a given style only if it differs from the value on the parent
            // (A layer or group which can have style values in SVG).
            var get = entry.get,
                type = entry.type,
                value = item[get]();
            if (entry.exportFilter
                    ? entry.exportFilter(item, value)
                    : !parent || !Base.equals(parent[get](), value)) {
                if (type === 'color' && value != null) {
                    // Support for css-style rgba() values is not in SVG 1.1, so
                    // separate the alpha value of colors with alpha into the
                    // separate fill- / stroke-opacity attribute:
                    var alpha = value.getAlpha();
                    if (alpha < 1)
                        attrs[entry.attribute + '-opacity'] = alpha;
                }
                if (type === 'style') {
                    style.push(entry.attribute + ': ' + value);
                } else {
                    attrs[entry.attribute] = value == null ? 'none'
                            : type === 'color' ? value.gradient
                                // true for noAlpha, see above
                                ? exportGradient(value, item)
                                : value.toCSS(true)
                            : type === 'array' ? value.join(',')
                            : type === 'lookup' ? entry.toSVG[value]
                            : value;
                }
            }
        });

        if (style.length)
            attrs.style = style.join(';');

        if (attrs.opacity === 1)
            delete attrs.opacity;

        if (!item._visible)
            attrs.visibility = 'hidden';

        return  SvgElement.set(node, attrs, formatter);
    }

    var definitions;
    function getDefinition(item, type) {
        if (!definitions)
            definitions = { ids: {}, svgs: {} };
        return item && definitions.svgs[type + '-' + item._id];
    }

    function setDefinition(item, node, type) {
        // Make sure the definitions lookup is created before we use it.
        // This is required by 'clip', where getDefinition() is not called.
        if (!definitions)
            getDefinition();
        // Have different id ranges per type
        var id = definitions.ids[type] = (definitions.ids[type] || 0) + 1;
        // Give the svg node an id, and link to it from the item id.
        node.id = type + '-' + id;
        definitions.svgs[type + '-' + item._id] = node;
    }

    function exportDefinitions(node, options) {
        var svg = node,
            defs = null;
        if (definitions) {
            // We can only use svg nodes as defintion containers. Have the loop
            // produce one if it's a single item of another type (when calling
            // #exportSVG() on an item rather than a whole project)
            // jsdom in Node.js uses uppercase values for nodeName...
            svg = node.nodeName.toLowerCase() === 'svg' && node;
            for (var i in definitions.svgs) {
                // This code is inside the loop so we only create a container if
                // we actually have svgs.
                if (!defs) {
                    if (!svg) {
                        svg = SvgElement.create('svg');
                        svg.appendChild(node);
                    }
                    defs = svg.insertBefore(SvgElement.create('defs'),
                            svg.firstChild);
                }
                defs.appendChild(definitions.svgs[i]);
            }
            // Clear definitions at the end of export
            definitions = null;
        }
        return options.asString
                ? new window.XMLSerializer().serializeToString(svg)
                : svg;
    }

    function exportSVG(item, options, isRoot) {
        var exporter = exporters[item._class],
            node = exporter && exporter(item, options);
        if (node) {
            // Support onExportItem callback, to provide mechanism to handle
            // special attributes (e.g. inkscape:transform-center)
            var onExport = options.onExport;
            if (onExport)
                node = onExport(item, node, options) || node;
            var data = JSON.stringify(item._data);
            if (data && data !== '{}' && data !== 'null')
                node.setAttribute('data-paper-data', data);
        }
        return node && applyStyle(item, node, isRoot);
    }

    function setOptions(options) {
        if (!options)
            options = {};
        formatter = new Formatter(options.precision);
        return options;
    }

    Item.inject({
        exportSVG: function(options) {
            options = setOptions(options);
            return exportDefinitions(exportSVG(this, options, true), options);
        }
    });

    Project.inject({
        exportSVG: function(options) {
            options = setOptions(options);
            var children = this._children,
                view = this.getView(),
                size = view.getViewSize(),
                node = SvgElement.create('svg', {
                    x: 0,
                    y: 0,
                    width: size.width,
                    height: size.height,
                    version: '1.1',
                    xmlns:  SvgElement.svg,
                    'xmlns:xlink':  SvgElement.xlink
                }, formatter),
                parent = node,
                matrix = view._matrix;
            // If the view has a transformation, wrap all layers in a group with
            // that transformation applied to.
            if (!matrix.isIdentity())
                parent = node.appendChild(SvgElement.create('g',
                        getTransform(matrix), formatter));
            for (var i = 0, l = children.length; i < l; i++)
                parent.appendChild(exportSVG(children[i], options, true));
            return exportDefinitions(node, options);
        }
    });
};
<|endoftext|>