<commit_before>
beforeEach(function() {
    var that = this;

    // Prevents jquery animations
    $.fx.off = true;

    // Prevents embedding flash player (speeds up the tests!)
    spyOn( $.fn, 'player' ).andCallFake( function ( params ) {
        that.playerParams = params;
    });

    // Prevents css3 animations on open
    spyOn( LightPlayer.prototype, '_animateIn' ).andCallFake( function ( callback ) {
        callback && callback();
    });

    // Prevents css3 animations on close
    spyOn( LightPlayer.prototype, '_animateOut' ).andCallFake( function ( callback ) {
        callback();
    });

    this.addMatchers({
        toHaveClass: function( klass ) {
             if ( klass ) {
                 return this.actual.hasClass( klass ) === true;
             } else {
                 return this.actual.attr( 'class' ) !== '';
             }
         }
    });

    this.lightplayer = new LightPlayer();
});

afterEach(function() {
    $.fx.off = false;

    this.playerParams = null;

    $( 'div.lightplayer' ).remove();
    $( document ).unbind();
});

describe("Light Player", function() {
    beforeEach(function() {
        this.json = {
            itens: [
                {
                    id: 123,
                    title: 'Titulo 1',
                    description: 'Descricao 1',
                    views: '100',
                    url: 'http://www.globo.com',
                    current: true
                }
            ]
        };

        this.lightplayer.open( this.json );
    });

    describe("open", function() {
        it("should have a container", function() {
            expect( this.lightplayer.domRoot.size() ).toBe( 1 );
        });

        it("should have a box", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget' ).size() ).toBe( 1 );
        });

        it("should have an overlay", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget-overlay' ).size() ).toBe( 1 );
        });

        it("should set the html class", function() {
            this.json = {
                htmlClass: 'blah',
                itens: [
                    {
                        id: 123,
                        title: 'Titulo 1',
                        description: 'Descricao 1',
                        views: '100',
                        url: 'http://www.globo.com',
                        current: true
                    }
                ]
            };

            this.lightplayer.open( this.json );

            expect( this.lightplayer.domRoot ).toHaveClass( 'blah' );
        });

    });

    describe("close", function() {
        beforeEach(function() {
            this.simulateEscKey = function () {
                var evt = jQuery.Event('keydown');
                evt.which = 27;
                $( document ).trigger( evt );
            };
        });

        it("should close", function() {
            this.lightplayer.close();

            expect( $( 'div.lightplayer' ).size() ).toBe( 0 );
        });

        it("should close on Esc key", function() {
            this.simulateEscKey();

            expect( $( 'div.lightplayer' ).size() ).toBe( 0 );
        });

        it("should dettach Esc key event from document", function() {
            expect( $( document ).data( 'events' ) ).toBeDefined();

            this.simulateEscKey();

            expect( $( document ).data( 'events' ) ).not.toBeDefined();
        });

        it("should close clicking on overlay (container)", function() {
            this.lightplayer.domRoot.find( 'div.widget-container' ).click();

            expect( $( 'div.lightplayer' ).size() ).toBe( 0 );
        });

        it("should close on close event", function() {
            this.lightplayer.bus.trigger( 'lightplayer-close' );

            expect( $( 'div.lightplayer' ).size() ).toBe( 0 );
        });

    });

    describe("module", function() {
        it("should add a stage as module", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget' ).find( 'div.palco' ).size() ).toBe( 1 );
        });

        it("should add a header as module", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget' ).find( 'div.header' ).size() ).toBe( 1 );
        });

        it("should add a title as module", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget' ).find( 'div.info' ).size() ).toBe( 1 );
        });

        it("should add a social as module", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget' ).find( 'div.social' ).size() ).toBe( 1 );
        });
    });

});

describe("jQuery interface", function() {

    it("should open the lightplayer", function() {
        $.lightplayer.open( {
            itens: [
                {
                    id: 123,
                    title: 'Titulo 1',
                    description: 'Descricao 1',
                    views: '100',
                    url: 'http://www.globo.com',
                    current: true
                }
            ]
        } );

        expect( $( 'div.lightplayer' ).size() ).toBe( 1 );
    });

    it("should close the lightplayer", function() {
        $.lightplayer.open( {
            itens: [
                {
                    id: 123,
                    title: 'Titulo 1',
                    description: 'Descricao 1',
                    views: '100',
                    url: 'http://www.globo.com',
                    current: true
                }
            ]
        } );

        $.lightplayer.close();

        expect( $( 'div.lightplayer' ).size() ).toBe( 0 );

    });

});

/***********************************************
 * MODULO MOD
 ***********************************************/

describe("Module: Mod", function() {
    beforeEach(function() {
        this.bus = $( {} );
        this.json = {
            itens: [
                { id: 123, title: 'titulo 1', description: 'desc 1', views: 1000 },
                { id: 456, title: 'titulo 2', description: 'desc 2', views: 2000, current: true },
                { id: 789, title: 'titulo 3', description: 'desc 3', views: 3000 }
            ]
        };

        this.mod = new Mod();
        this.mod.init( this.bus, this.json );
    });

    it("should have a name", function() {
        expect( this.mod.name ).toBe( 'mod-name' );
    });

    it("should have a json with a list", function() {
        expect( this.mod.json ).toBe( this.json );
        expect( this.mod.json.itens ).toBe( this.json.itens );
    });

    describe("getItem", function() {
        it("should get the current item", function() {
            expect( this.mod._getItem( 'current' ) ).toBe( this.json.itens[1] );
        });

        it("should get the next item", function() {
            expect( this.mod._getItem( 'next' ) ).toBe( this.json.itens[2] );
        });

        it("should get the next item as null if current is last", function() {
            this.json.itens[1].current = false;
            this.json.itens[2].current = true;

            this.mod = new Mod();
            this.mod.init( null, this.json );

            expect( $.isEmptyObject( this.mod._getItem( 'next' ) ) ).toBe( true );
        });

        it("should get the prev item", function () {
            expect( this.mod._getItem( 'prev' ) ).toBe( this.json.itens[0] );
        });

        it("should get the prev item as null if current is first", function() {
            this.json.itens[1].current = false;
            this.json.itens[0].current = true;

            this.mod = new Mod();
            this.mod.init( null, this.json );

            expect( $.isEmptyObject( this.mod._getItem( 'prev' ) ) ).toBe( true );
        });
    });

    describe("setItemAsCurrent", function() {
        it("should mark new item as current", function() {
            this.mod._setItemAsCurrent( this.json.itens[2] );

            expect( this.json.itens[2].current ).toBe( true );
        });

        it("should unmark all the remaining itens as not current", function() {
            this.mod._setItemAsCurrent( this.json.itens[2] );

            expect( this.json.itens[0].current ).toBeFalsy();
            expect( this.json.itens[1].current ).toBeFalsy();
        });
    });

    describe("pub", function() {
        it("should publish an event", function() {
            var callback = jasmine.createSpy();

            this.mod.bus.bind( 'some-event', callback );

            this.mod.pub( 'some-event' );

            expect( callback ).toHaveBeenCalled();
        });

        it("should publish a json copy", function() {
            var event;

            this.mod.bus.bind( 'some-event', function ( evt ) {
                event = evt;
            } );

            this.mod.pub( 'some-event', this.json );

            expect( JSON.stringify(event.json) ).toBe( JSON.stringify(this.mod.json) );
            expect( event.json ).not.toBe( this.mod.json );
        });

        it("should indicate the event publisher", function() {
            var event;

            this.mod.bus.bind( 'some-event', function ( evt ) {
                event = evt;
            } );

            this.mod.pub( 'some-event', {} );

            expect( event.origin ).toBe( this.mod.name );
        });
    });

    describe("sub", function () {
        beforeEach(function() {
            this.callback = jasmine.createSpy();
        });

        it("should subscribe to an event", function() {
            this.mod.sub( 'some-event', this.callback );

            this.mod.bus.trigger( 'some-event' );

            expect( this.callback ).toHaveBeenCalled();
        });

        it("should not execute callback if published from itself", function() {
            this.mod.sub( 'some-event', this.callback );

            this.mod.bus.trigger( {
                type: 'some-event',
                origin: this.mod.name
            } );

            expect( this.callback ).not.toHaveBeenCalled();
        });

        it("should execute callback if not published from itself", function() {
            this.mod.sub( 'some-event', this.callback );

            this.mod.bus.trigger( {
                type: 'some-event',
                origin: 'test-suite'
            } );

            expect( this.callback ).toHaveBeenCalled();
        });

    });
});

<commit_msg>solving linting problemas on core.spec.js<commit_after>
beforeEach(function() {
    var that = this;

    // Prevents jquery animations
    $.fx.off = true;

    // Prevents embedding flash player (speeds up the tests!)
    spyOn( $.fn, 'player' ).andCallFake( function ( params ) {
        that.playerParams = params;
    });

    // Prevents css3 animations on open
    spyOn( LightPlayer.prototype, '_animateIn' ).andCallFake( function ( callback ) {
        if (callback) {
					callback();
				}
    });

    // Prevents css3 animations on close
    spyOn( LightPlayer.prototype, '_animateOut' ).andCallFake( function ( callback ) {
        callback();
    });

    this.addMatchers({
        toHaveClass: function( klass ) {
             if ( klass ) {
                 return this.actual.hasClass( klass ) === true;
             } else {
                 return this.actual.attr( 'class' ) !== '';
             }
         }
    });

    this.lightplayer = new LightPlayer();
});

afterEach(function() {
    $.fx.off = false;

    this.playerParams = null;

    $( 'div.lightplayer' ).remove();
    $( document ).unbind();
});

describe("Light Player", function() {
    beforeEach(function() {
        this.json = {
            itens: [
                {
                    id: 123,
                    title: 'Titulo 1',
                    description: 'Descricao 1',
                    views: '100',
                    url: 'http://www.globo.com',
                    current: true
                }
            ]
        };

        this.lightplayer.open( this.json );
    });

    describe("open", function() {
        it("should have a container", function() {
            expect( this.lightplayer.domRoot.size() ).toBe( 1 );
        });

        it("should have a box", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget' ).size() ).toBe( 1 );
        });

        it("should have an overlay", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget-overlay' ).size() ).toBe( 1 );
        });

        it("should set the html class", function() {
            this.json = {
                htmlClass: 'blah',
                itens: [
                    {
                        id: 123,
                        title: 'Titulo 1',
                        description: 'Descricao 1',
                        views: '100',
                        url: 'http://www.globo.com',
                        current: true
                    }
                ]
            };

            this.lightplayer.open( this.json );

            expect( this.lightplayer.domRoot ).toHaveClass( 'blah' );
        });

    });

    describe("close", function() {
        beforeEach(function() {
            this.simulateEscKey = function () {
                var evt = jQuery.Event('keydown');
                evt.which = 27;
                $( document ).trigger( evt );
            };
        });

        it("should close", function() {
            this.lightplayer.close();

            expect( $( 'div.lightplayer' ).size() ).toBe( 0 );
        });

        it("should close on Esc key", function() {
            this.simulateEscKey();

            expect( $( 'div.lightplayer' ).size() ).toBe( 0 );
        });

        it("should dettach Esc key event from document", function() {
            expect( $( document ).data( 'events' ) ).toBeDefined();

            this.simulateEscKey();

            expect( $( document ).data( 'events' ) ).not.toBeDefined();
        });

        it("should close clicking on overlay (container)", function() {
            this.lightplayer.domRoot.find( 'div.widget-container' ).click();

            expect( $( 'div.lightplayer' ).size() ).toBe( 0 );
        });

        it("should close on close event", function() {
            this.lightplayer.bus.trigger( 'lightplayer-close' );

            expect( $( 'div.lightplayer' ).size() ).toBe( 0 );
        });

    });

    describe("module", function() {
        it("should add a stage as module", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget' ).find( 'div.palco' ).size() ).toBe( 1 );
        });

        it("should add a header as module", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget' ).find( 'div.header' ).size() ).toBe( 1 );
        });

        it("should add a title as module", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget' ).find( 'div.info' ).size() ).toBe( 1 );
        });

        it("should add a social as module", function() {
            expect( this.lightplayer.domRoot.find( 'div.widget' ).find( 'div.social' ).size() ).toBe( 1 );
        });
    });

});

describe("jQuery interface", function() {

    it("should open the lightplayer", function() {
        $.lightplayer.open( {
            itens: [
                {
                    id: 123,
                    title: 'Titulo 1',
                    description: 'Descricao 1',
                    views: '100',
                    url: 'http://www.globo.com',
                    current: true
                }
            ]
        } );

        expect( $( 'div.lightplayer' ).size() ).toBe( 1 );
    });

    it("should close the lightplayer", function() {
        $.lightplayer.open( {
            itens: [
                {
                    id: 123,
                    title: 'Titulo 1',
                    description: 'Descricao 1',
                    views: '100',
                    url: 'http://www.globo.com',
                    current: true
                }
            ]
        } );

        $.lightplayer.close();

        expect( $( 'div.lightplayer' ).size() ).toBe( 0 );

    });

});

/***********************************************
 * MODULO MOD
 ***********************************************/

describe("Module: Mod", function() {
    beforeEach(function() {
        this.bus = $( {} );
        this.json = {
            itens: [
                { id: 123, title: 'titulo 1', description: 'desc 1', views: 1000 },
                { id: 456, title: 'titulo 2', description: 'desc 2', views: 2000, current: true },
                { id: 789, title: 'titulo 3', description: 'desc 3', views: 3000 }
            ]
        };

        this.mod = new Mod();
        this.mod.init( this.bus, this.json );
    });

    it("should have a name", function() {
        expect( this.mod.name ).toBe( 'mod-name' );
    });

    it("should have a json with a list", function() {
        expect( this.mod.json ).toBe( this.json );
        expect( this.mod.json.itens ).toBe( this.json.itens );
    });

    describe("getItem", function() {
        it("should get the current item", function() {
            expect( this.mod._getItem( 'current' ) ).toBe( this.json.itens[1] );
        });

        it("should get the next item", function() {
            expect( this.mod._getItem( 'next' ) ).toBe( this.json.itens[2] );
        });

        it("should get the next item as null if current is last", function() {
            this.json.itens[1].current = false;
            this.json.itens[2].current = true;

            this.mod = new Mod();
            this.mod.init( null, this.json );

            expect( $.isEmptyObject( this.mod._getItem( 'next' ) ) ).toBe( true );
        });

        it("should get the prev item", function () {
            expect( this.mod._getItem( 'prev' ) ).toBe( this.json.itens[0] );
        });

        it("should get the prev item as null if current is first", function() {
            this.json.itens[1].current = false;
            this.json.itens[0].current = true;

            this.mod = new Mod();
            this.mod.init( null, this.json );

            expect( $.isEmptyObject( this.mod._getItem( 'prev' ) ) ).toBe( true );
        });
    });

    describe("setItemAsCurrent", function() {
        it("should mark new item as current", function() {
            this.mod._setItemAsCurrent( this.json.itens[2] );

            expect( this.json.itens[2].current ).toBe( true );
        });

        it("should unmark all the remaining itens as not current", function() {
            this.mod._setItemAsCurrent( this.json.itens[2] );

            expect( this.json.itens[0].current ).toBeFalsy();
            expect( this.json.itens[1].current ).toBeFalsy();
        });
    });

    describe("pub", function() {
        it("should publish an event", function() {
            var callback = jasmine.createSpy();

            this.mod.bus.bind( 'some-event', callback );

            this.mod.pub( 'some-event' );

            expect( callback ).toHaveBeenCalled();
        });

        it("should publish a json copy", function() {
            var event;

            this.mod.bus.bind( 'some-event', function ( evt ) {
                event = evt;
            } );

            this.mod.pub( 'some-event', this.json );

            expect( JSON.stringify(event.json) ).toBe( JSON.stringify(this.mod.json) );
            expect( event.json ).not.toBe( this.mod.json );
        });

        it("should indicate the event publisher", function() {
            var event;

            this.mod.bus.bind( 'some-event', function ( evt ) {
                event = evt;
            } );

            this.mod.pub( 'some-event', {} );

            expect( event.origin ).toBe( this.mod.name );
        });
    });

    describe("sub", function () {
        beforeEach(function() {
            this.callback = jasmine.createSpy();
        });

        it("should subscribe to an event", function() {
            this.mod.sub( 'some-event', this.callback );

            this.mod.bus.trigger( 'some-event' );

            expect( this.callback ).toHaveBeenCalled();
        });

        it("should not execute callback if published from itself", function() {
            this.mod.sub( 'some-event', this.callback );

            this.mod.bus.trigger( {
                type: 'some-event',
                origin: this.mod.name
            } );

            expect( this.callback ).not.toHaveBeenCalled();
        });

        it("should execute callback if not published from itself", function() {
            this.mod.sub( 'some-event', this.callback );

            this.mod.bus.trigger( {
                type: 'some-event',
                origin: 'test-suite'
            } );

            expect( this.callback ).toHaveBeenCalled();
        });

    });
});

<|endoftext|>


<commit_before>#include "FileSystemObject.js"

var Noonworks = Noonworks || {};

Noonworks.Launcher = function(){
    this.fso = new Noonworks.FileSystemObject();
    this.shell = new ActiveXObject('WScript.Shell');
};

Noonworks.Launcher.prototype = {
    makeClosure: function(func, args) {
        return function(){
            func.apply(this, args);
        };
    },

    createDirRecursive: function(path) {
        return this.fso.createDirRecursive(path);
    },

    openMery: function(path) {
        Editor.OpenFile(path, meEncodingNone, meOpenAllowNewWindow);
    },

    createWithMery: function(path) {
        this.fso.createDirRecursive(this.fso.GetParentFolderName(path));
        this.fso.CreateTextFile(path, true);
        this.openMery(path);
    },

    openCmd: function(path) {
        path = path || Document.Path || this.shell.SpecialFolders('Desktop');
        this.shell.CurrentDirectory = path;
        this.shell.Run('cmd');
    },

    openExplorer: function(path) {
        path = path || Document.Path || this.shell.SpecialFolders('Desktop');
        this.shell.Run('explorer.exe "' + path + '"');
    },

    openUrl: function(url) {
        if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0) {
            url = 'http://' + url;
        }
        this.shell.Run(url);
    },

    open: function(str) {
        this.shell.Run(str);
    },

    search: function(str, next) {
        Document.selection.Find(str, (next ? meFindNext : meFindPrevious));
    },

    searchGoogle: function(str) {
        var url = 'https://www.google.co.jp/search?q=' + encodeURI(str);
        this.openUrl(url);
    },

    searchWikipedia: function(str) {
        var url = 'http://ja.wikipedia.org/w/index.php?search=' + encodeURI(str);
        this.openUrl(url);
    },

    openGoogleTranslateJaToEn: function(str) {
        var url = 'https://translate.google.co.jp/#ja/en/' + encodeURI(str);
        this.openUrl(url);
    },

    openGoogleTranslateEnToJa: function(str) {
        var url = 'https://translate.google.co.jp/#en/ja/' + encodeURI(str);
        this.openUrl(url);
    },

    copyToClipboard: function(str) {
        if (str.replace(/\n/g, '').length > 0) {
            ClipboardData.SetData(str);
        }
    },

    select: function(i_start, i_end, posY_l) {
        Document.Selection.Collapse();
        Document.Selection.SetActivePoint(mePosLogical, i_start + 1, posY_l, false);
        Document.Selection.SetActivePoint(mePosLogical, i_end + 1, posY_l, true);
    },

    paste: function(i_start, i_end, posY_l, str) {
        this.select(i_start, i_end, posY_l);
        Document.Selection.Delete();
        Document.Write(str);
    },

    pasteClipboard: function(i_start, i_end, posY_l) {
        this.paste(i_start, i_end, posY_l, ClipboardData.getData());
    }
};
<commit_msg>Add method with ExecuteCommandByID.<commit_after>#include "FileSystemObject.js"

var Noonworks = Noonworks || {};

Noonworks.Launcher = function(){
    this.fso = new Noonworks.FileSystemObject();
    this.shell = new ActiveXObject('WScript.Shell');
};

Noonworks.Launcher.prototype = {
    makeClosure: function(func, args) {
        return function(){
            func.apply(this, args);
        };
    },

    createDirRecursive: function(path) {
        return this.fso.createDirRecursive(path);
    },

    openMery: function(path) {
        Editor.OpenFile(path, meEncodingNone, meOpenAllowNewWindow);
    },

    createWithMery: function(path) {
        this.fso.createDirRecursive(this.fso.GetParentFolderName(path));
        this.fso.CreateTextFile(path, true);
        this.openMery(path);
    },

    openCmd: function(path) {
        path = path || Document.Path || this.shell.SpecialFolders('Desktop');
        this.shell.CurrentDirectory = path;
        this.shell.Run('cmd');
    },

    openExplorer: function(path) {
        path = path || Document.Path || this.shell.SpecialFolders('Desktop');
        this.shell.Run('explorer.exe "' + path + '"');
    },

    openUrl: function(url) {
        if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0) {
            url = 'http://' + url;
        }
        this.shell.Run(url);
    },

    open: function(str) {
        this.shell.Run(str);
    },

    search: function(str, next) {
        Document.selection.Find(str, (next ? meFindNext : meFindPrevious));
    },

    openFileSearchDialog: function() {
        Editor.ExecuteCommandByID(2140);
    },

    searchGoogle: function(str) {
        var url = 'https://www.google.co.jp/search?q=' + encodeURI(str);
        this.openUrl(url);
    },

    searchWikipedia: function(str) {
        var url = 'http://ja.wikipedia.org/w/index.php?search=' + encodeURI(str);
        this.openUrl(url);
    },

    openGoogleTranslateJaToEn: function(str) {
        var url = 'https://translate.google.co.jp/#ja/en/' + encodeURI(str);
        this.openUrl(url);
    },

    openGoogleTranslateEnToJa: function(str) {
        var url = 'https://translate.google.co.jp/#en/ja/' + encodeURI(str);
        this.openUrl(url);
    },

    copyToClipboard: function(str) {
        if (str.replace(/\n/g, '').length > 0) {
            ClipboardData.SetData(str);
        }
    },

    select: function(i_start, i_end, posY_l) {
        Document.Selection.Collapse();
        Document.Selection.SetActivePoint(mePosLogical, i_start + 1, posY_l, false);
        Document.Selection.SetActivePoint(mePosLogical, i_end + 1, posY_l, true);
    },

    paste: function(i_start, i_end, posY_l, str) {
        this.select(i_start, i_end, posY_l);
        Document.Selection.Delete();
        Document.Write(str);
    },

    pasteClipboard: function(i_start, i_end, posY_l) {
        this.paste(i_start, i_end, posY_l, ClipboardData.getData());
    },

    execPlugin: function(plugin_id) {
        Editor.ExecuteCommandByID(7168 - 1 + plugin_id);
    }
};
<|endoftext|>


<commit_before>function speedingTicket (initialContainers, options) {
  var len = arguments.length;
  if (len === 1 && Array.isArray(initialContainers) === false) {
    options = initialContainers;
    initialContainers = [];
  }
  var body = document.body;
  var documentElement = document.documentElement;
  var _scrollX; // reference x
  var _scrollY; // reference y

  var o = options || {};
  if (o.movement === void 0) { o.movement = false; }
  if (o.direction === void 0) { o.direction = stopped; }

  var speedingTicket = emitter({
    movement: o.movement,
    direction: o.direction
  });

  events();

  return speedingTicket;
}
<commit_msg>Update based on production code<commit_after>function speedradar () {

    var _scrollY,
        _countingUp             = 0,
        _countingDown           = 0,
        _verifySpeed            = 0,
        _onionSkin              = [],
        _lastScrollY            = 0,
        _ticking                = false,
        scrollupWhileReading    = new CustomEvent("scrollupWhileReading", {}),
        scrolldownNormalReading = new CustomEvent("scrolldownNormalReading", {}),
        scrolldownWhileReading  = new CustomEvent("scrolldownWhileReading", {});

    events();

    /**
     * Callback for our scroll event - just
     * keeps a track on the last scroll value
     */
    function onScroll() {
        _lastScrollY = window.scrollY;
        requestTick();
    }

    /**
     * Calls rAF if it's not already
     * been done already
     */
    function requestTick() {
        if(!_ticking) {
            requestAnimFrame(update);
            _ticking = true;
        }
    }

    /**
     * Our animation callback
     */
    function update() {
        updateLocation();
        // allow further rAFs to be called
        _ticking = false;
    }

    /**
     * Scroll related event setup
     */
    function events(remove){
      var op = remove ? 'remove' : 'add';

      // only listen for scroll events
      window.addEventListener('scroll', onScroll, false);

      // shim layer with setTimeout fallback
      window.requestAnimFrame = (function(){
        return  window.requestAnimationFrame       ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame    ||
                window.oRequestAnimationFrame      ||
                window.msRequestAnimationFrame     ||
                function( callback ){
                  window.setTimeout(callback, 1000 / 60);
                };
      })();
    }

    /**
     * Keeping track of scroll history in an array
     * Using this, you can estimate the actual direction/speed of scrolls
     */
    function addToScrollHistory(y){
      _onionSkin.push(y);
      if(_onionSkin.length > 9){
        _onionSkin = _onionSkin.slice(1, 10);
        // console.log(_onionSkin);
      }
    }

    /**
     * Check if scrolling down
     */
    function checkIsReadingNormally(y){
      if(_scrollY > y){
        document.dispatchEvent(scrolldownNormalReading);
      }
    }

    /**
     * Do a check if scrolling up
     * Expand check by confirmed the scrolling up is actually happening
     */
    function checkIsScrollingUp(){
      var article = document.querySelector('#articleBody');
      var bounds = document.querySelector('#articleBody').getBoundingClientRect();
      if(bounds.top < 0 && (bounds.top+bounds.height-window.innerHeight) > 0){
        var isGoingUp = false;
        for(var i = 1; i < _onionSkin.length; i++){
          if(_onionSkin[i-1] >= _onionSkin[i]){
            isGoingUp = true;
          }
        }
        if(isGoingUp){
          _verifySpeed = 0;
          _countingDown = 0;
          _countingUp = _countingUp + 1;
          if(_countingUp > 3){
            document.dispatchEvent(scrollupWhileReading);
          }
        } else {
          _countingDown = _countingDown + 1;
          _countingUp = 0;
          if(_countingDown > 3){
            document.dispatchEvent(scrolldownWhileReading);
          }
        }
        // console.log('Going down');
      } else {
        document.dispatchEvent(scrolldownWhileReading);
      }
    }

    /**
     * This is the "controller" for this entire process
     */
    var updateLocation = function(){
      // console.log('fn running');
      var y = window.scrollY;
      addToScrollHistory(y);
      checkIsScrollingUp();
      checkIsReadingNormally(y);
      _scrollY = y;
    };

    return thespeedradar;
  }
<|endoftext|>


<commit_before>'use strict';

let paper = require('js/paper-core.min'),
    paperScope = require('js/PaperScopeManager');

const
    INITIAL_SHADOW_LENGTH = 2000,
    TARGET_ANGLE = Math.PI / 4;

class IconShadow {

    /**
     * @param iconPath path of the icon used to construct the shadow
     * @param iconBase of the icon used to cut the shadow
     */
    constructor(iconPath, iconBase) {
        this.iconBase = iconBase;
        this.iconPath = iconPath;

        this.iconShadowPaths = [];

        let outlineIconPaths = this.getOutlinePaths(iconPath);
        for (let outlineIdx = 0; outlineIdx < outlineIconPaths.length; ++outlineIdx) {
            let path = outlineIconPaths[outlineIdx];
            let subPaths = [];
            let tangent = this.findNextTangent(path);

            // cut path along tangents into sub paths
            path.split(tangent.curveIdx, tangent.timeParams[0]);
            while (path && (tangent = this.findNextTangent(path))) {
                let newPath = path.split(tangent.curveIdx, tangent.timeParams[0]);
                subPaths.push(path);
                path = newPath;
            }
            if (path) subPaths.push(path);

            // create shadow sub shapes
            let xyTranslation = Math.sqrt(INITIAL_SHADOW_LENGTH * INITIAL_SHADOW_LENGTH / 2);
            let translation = new paper.Point(xyTranslation, xyTranslation);
            for (let i = 0; i < subPaths.length; ++i) {
                let subPath = subPaths[i];

                // offset + insert all segments to create closed form
                for (let j = subPath.segments.length - 1; j >= 0; --j) {
                    let segment = subPath.segments[j];
                    // make shade edges 'sharp'
                    if (j === subPath.segments.length -1) {
                        segment.handleOut.x = 0;
                        segment.handleOut.y = 0;
                    }
                    if (j === 0) {
                        segment.handleIn.x = 0;
                        segment.handleIn.y = 0;
                    }
                    subPath.add(new paper.Segment(segment.point.add(translation), segment.handleOut, segment.handleIn));
                }
                subPath.closed = true;

                this.iconShadowPaths.push(new paper.Path(subPath.pathData));
            }
        }

        // find bottom right edge of shadow (for changing length)
        this.iconShadowBottomRightSegments = [];
        this.iconShadowPaths.forEach(function(path) {
            this.iconShadowBottomRightSegments = this.iconShadowBottomRightSegments.concat(this.findBottomRightSegments(path));
        }.bind(this));
        this.length = INITIAL_SHADOW_LENGTH;

        // store shadow template and cut with base
        this.applyShadow();
    }

    findNextTangent(path) {
        // go over each curve and try finding tangent with target angle
        for (let i = 0; i < path.curves.length; ++i) {
            let curve = path.curves[i];

            // search for tangent within curve
            let timeParams = this.findNextTangentFromCurve(curve);
            if (timeParams.length > 0) {
                // don't split curve right at the beginning (otherwise it might never stop splitting there ...)
                if (i === 0 && timeParams[0] < 1E-10) {
                    timeParams.splice(0, 1);
                    if (timeParams.length === 0) continue;
                }

                return {
                    curveIdx: i,
                    timeParams: timeParams
                };
            }

            // check for hard edges between this and the next curve
            let nextCurve = path.curves[(i + 1) % path.curves.length];
            let hardEdge = (curve.handle2.x === 0 && curve.handle2.y === 0) || (nextCurve.handle1.x === 0 || nextCurve.handle1.y === 0);
            hardEdge = hardEdge || (Math.abs(curve.getTangentAt(1, true).angle - nextCurve.getTangentAt(0, true).angle) > 1E-10);
            if (!hardEdge) continue;
            // (possible) TODO
            // Currently path will be split along every hard edge. This might not be super efficient for very large paths.
            return {
                curveIdx: i,
                timeParams: [1]
            };
        }
        return undefined;
    }

    findNextTangentFromCurve(curve) {
        // in case of missing handles return
        if (curve.handle1.x === 0 && curve.handle1.y === 0 && curve.handle2.x === 0 && curve.handle2.y === 0) {
            return [];
        }

        // find tangent with target angle (45 degrees)
        let points = [
            curve.point1,
            curve.point1.add(curve.handle1),
            curve.point2.add(curve.handle2),
            curve.point2
        ];

        let a = {};
        let b = {};
        let c = {};

        a.x = 3 * points[3].x - 9 * points[2].x + 9 * points[1].x - 3 * points[0].x;
        a.y = 3 * points[3].y - 9 * points[2].y + 9 * points[1].y - 3 * points[0].y;

        b.x = 6 * points[2].x - 12 * points[1].x + 6 * points[0].x;
        b.y = 6 * points[2].y - 12 * points[1].y + 6 * points[0].y;

        c.x = 3 * points[1].x - 3 * points[0].x;
        c.y = 3 * points[1].y - 3 * points[0].y;

        let t = {
            x: Math.cos(TARGET_ANGLE),
            y: Math.sin(TARGET_ANGLE)
        };

        let timeParams = [];
        let den = 2 * a.x * t.y - 2 * a.y * t.x;
        if (Math.abs(den) < 1E-10) {
            let num = a.x * c.y - a.y * c.x;
            den = a.x * b.y - a.y * b.x;
            if (den != 0) {
                let time = -num / den;
                if (time >= 0 && time <= 1) timeParams.push(time);
            }
        } else {
            let delta = (b.x * b.x - 4 * a.x * c.x) * t.y * t.y + (-2 * b.x * b.y + 4 * a.y * c.x + 4 * a.x * c.y) * t.x * t.y + (b.y * b.y - 4 * a.y * c.y) * t.x * t.x;
            let k = b.x * t.y - b.y * t.x;
            timeParams = [];
            if (delta >= 0 && den != 0) {
                let d = Math.sqrt(delta);
                let t0 = -(k + d) / den;
                let t1 = (-k + d) / den;

                if (Math.abs(t0 - 1) < 1E-5) t0 = 1;
                if (Math.abs(t1 - 1) < 1E-5) t1 = 1;

                if (t0 >= 0 && t0 < 1) timeParams.push(t0);
                if (t1 >= 0 && t1 < 1) timeParams.push(t1);
            }
        }
        if (timeParams.length === 0) return timeParams;

        // clean values
        for (let i = 0; i < timeParams.length; ++i) {
            let time = timeParams[i];

            // if very close to 0, set value to 0
            if (time < 1E-10) {
                timeParams[i] = 0;
                continue;
            }

            // if very close to 1, set value to 1
            time = Math.abs(1 - time);
            if (time < 1E-10) {
                timeParams[i] = 1;
                continue;
            }
        }

        timeParams.sort(function(a, b) { return a - b; });
        return timeParams;
    }

    findBottomRightSegments(path) {
        let segments = [];
        if (path instanceof paper.CompoundPath) {
            for (let i = 0; i < path.children.length; ++i) {
                segments = segments.concat(this.findBottomRightSegments(path.children[i]));
            }

        } else {
            for (let i = 0; i < path.segments.length; ++i) {
                let segment = path.segments[i];
                if (segment.point.getDistance(this.iconPath.bounds.topLeft) > INITIAL_SHADOW_LENGTH) {
                    segments.push(segment);
                }
            }
        }

        return segments;
    }

    /**
     * @param scale factor to scale this shadow by.
     */
    scale(scale) {
        this.iconShadowPaths.forEach(function(path) {
            path.scale(scale, this.iconPath.position);
        }.bind(this));
        this.unitedIconShadowPath.scale(scale, this.iconPath.position);
        this.length = this.length * scale;
        this.applyShadow();
    }

    /**
     * @param {paper.Point} delta - how much this icon + shadow should be moved.
     */
    move(delta) {
        this.iconShadowPaths.forEach(function(path) {
            path.position = path.position.add(delta);
        });
        this.unitedIconShadowPath.position = this.unitedIconShadowPath.position.add(delta);
        this.applyShadow();
    }

    /**
     * Remove this shadow form the canvas.
     */
    remove() {
        this.iconShadowPaths.forEach(function(path) {
            path.remove();
        });
        if (this.unitedIconShadowPath) this.unitedIconShadowPath.remove();
        if (this.appliedIconShadowPath) this.appliedIconShadowPath.remove();
    }

    /**
     * Cut the shadow path template(s) to base size. This creates the
     * final shadow path.
     */
    applyShadow() {
        // join sub shadows to from one path
        if (!this.unitedIconShadowPath) this.createUnitedIconShadowPath();

        // cut shadow to base
        let basePath = this.iconBase.getPathWithoutShadows();
        let newAppliedIconShadowPath = this.unitedIconShadowPath.intersect(basePath);

        // save shadow
        if (this.appliedIconShadowPath) {
            this.appliedIconShadowPath.replaceWith(newAppliedIconShadowPath);
            newAppliedIconShadowPath.fillColor = this.appliedIconShadowPath.fillColor;

        } else {
            // create default gradient
            newAppliedIconShadowPath.fillColor = {
                gradient: {
                    stops: [
                        [new paper.Color(0, 0, 0, 1), 0],
                        [new paper.Color(0, 0, 0, 0), 1]
                    ]
                }
            }

        }
        this.appliedIconShadowPath = newAppliedIconShadowPath;
        this.updateAppliedIconShadowPathGradientBounds();

        // move shadow below icon
        this.appliedIconShadowPath.moveBelow(this.iconPath);
    }

    /**
     * Unites all shadow parts to one shadow shape (doesn't intersect with base though,
     * hence can be used as a 'template').
     * Don't call this too often though, uniting shapes is CPU intensive.
     */
    createUnitedIconShadowPath() {
        if (this.unitedIconShadowPath) this.unitedIconShadowPath.remove();
        this.unitedIconShadowPath = new paper.Path(this.iconShadowPaths[0].pathData);
        for (let i = 1; i < this.iconShadowPaths.length; ++i) {
            let subPathCopy = new paper.Path(this.iconShadowPaths[i].pathData);
            let newShadowPath = this.unitedIconShadowPath.unite(subPathCopy);
            subPathCopy.remove();
            this.unitedIconShadowPath.remove();
            this.unitedIconShadowPath = newShadowPath;
        }
    }

    /**
     * Sets the length of this shadow (diagonal)
     * @param {Number} length
     */
    setLength(length) {
        let deltaLength = length - this.length;
        let translation = Math.sqrt(deltaLength * deltaLength / 2);
        if (deltaLength < 0) translation = translation * -1;

        for (let i = 0; i < this.iconShadowBottomRightSegments.length; ++i) {
            let segment = this.iconShadowBottomRightSegments[i];
            segment.point = segment.point.add(translation);
        }

        this.length = length;
        this.createUnitedIconShadowPath();
        this.applyShadow();
    }

    /**
     * Sets the start intensity of this shadow.
     * @param {Number} intensity - between 0 and 1.
     */
    setIntensity(intensity) {
        this.appliedIconShadowPath.fillColor.gradient.stops[0].color.alpha = intensity;
        // hack: just changing alpha does not trigger a redraw, 'change' this as well
        this.appliedIconShadowPath.fillColor.destination = this.appliedIconShadowPath.fillColor.destination;
    }

    /**
     * Sets where the shadow should begin fading.
     * @param {Number} fading - between 0 and 1. 1 Start fading right away, 0 never fade.
     */
    setFading(fading) {
        this.appliedIconShadowPath.fillColor.gradient.stops[0].rampPoint = 1 - fading;
        // hack: just changing alpha does not trigger a redraw, 'change' this as well
        this.appliedIconShadowPath.fillColor.destination = this.appliedIconShadowPath.fillColor.destination;
    }

    updateAppliedIconShadowPathGradientBounds() {
        let bounds = this.unitedIconShadowPath.bounds;
        this.appliedIconShadowPath.fillColor.origin = bounds.topLeft;
        this.appliedIconShadowPath.fillColor.destination = bounds.bottomRight;
    }

    /**
     * Removes 'holes' from paths and returns all paths which together from the outline of
     * the given path. The return paths will have been inserted into the project and are a
     * clone from the original.
     * @param {paper.PathItem} pathItem - input path
     * @returns {paper.PathItem[]} - an array containing the paths.
     */
    getOutlinePaths(pathItem) {
        if (pathItem instanceof paper.Path) {
            return [ new paper.Path(pathItem.pathData) ];
        }
        if (!(pathItem instanceof paper.CompoundPath)) {
            console.warn('unknown path class');
            console.warn(pathItem);
            return [];
        }

        // copy children and skip non paper.Path objects
        let children = [];
        for (let i = 0; i < pathItem.children.length; ++i) {
            let path = pathItem.children[i];
            if (!(path instanceof paper.Path)) {
                console.warn('child is not a paper.Path!');
                console.warn(path);
                continue;
            }
            children.push(path);
        }

        // sort ascending by area
        children.sort(function(a, b) {
            return Math.abs(a.area) - Math.abs(b.area);
        });

        // find largest areas which do not overlap with others
        let resultPaths = [];
        outerLoop:
            for (let i = 0; i < children.length; ++i) {
                let path = children[i];
                let insidePoint = path.interiorPoint;
                for (let j = i + 1; j < children.length; ++j) {
                    if (children[j].contains(insidePoint)) continue outerLoop;
                }
                resultPaths.push(new paper.Path(path.pathData));
            }

        return resultPaths;
    }

}

module.exports = IconShadow;
<commit_msg>Fix inner edges not being used for shadows<commit_after>'use strict';

let paper = require('js/paper-core.min'),
    paperScope = require('js/PaperScopeManager');

const
    INITIAL_SHADOW_LENGTH = 2000,
    TARGET_ANGLE = Math.PI / 4;

class IconShadow {

    /**
     * @param iconPath path of the icon used to construct the shadow
     * @param iconBase of the icon used to cut the shadow
     */
    constructor(iconPath, iconBase) {
        this.iconBase = iconBase;
        this.iconPath = iconPath;

        this.iconShadowPaths = [];

        let outlineIconPaths = this.getAndCopyPaths(iconPath);
        for (let outlineIdx = 0; outlineIdx < outlineIconPaths.length; ++outlineIdx) {
            let path = outlineIconPaths[outlineIdx];
            let subPaths = [];
            let tangent = this.findNextTangent(path);

            // cut path along tangents into sub paths
            path.split(tangent.curveIdx, tangent.timeParams[0]);
            while (path && (tangent = this.findNextTangent(path))) {
                let newPath = path.split(tangent.curveIdx, tangent.timeParams[0]);
                subPaths.push(path);
                path = newPath;
            }
            if (path) subPaths.push(path);

            // create shadow sub shapes
            let xyTranslation = Math.sqrt(INITIAL_SHADOW_LENGTH * INITIAL_SHADOW_LENGTH / 2);
            let translation = new paper.Point(xyTranslation, xyTranslation);
            for (let i = 0; i < subPaths.length; ++i) {
                let subPath = subPaths[i];

                // offset + insert all segments to create closed form
                for (let j = subPath.segments.length - 1; j >= 0; --j) {
                    let segment = subPath.segments[j];
                    // make shade edges 'sharp'
                    if (j === subPath.segments.length -1) {
                        segment.handleOut.x = 0;
                        segment.handleOut.y = 0;
                    }
                    if (j === 0) {
                        segment.handleIn.x = 0;
                        segment.handleIn.y = 0;
                    }
                    subPath.add(new paper.Segment(segment.point.add(translation), segment.handleOut, segment.handleIn));
                }
                subPath.closed = true;

                this.iconShadowPaths.push(new paper.Path(subPath.pathData));
            }
        }

        // find bottom right edge of shadow (for changing length)
        this.iconShadowBottomRightSegments = [];
        this.iconShadowPaths.forEach(function(path) {
            this.iconShadowBottomRightSegments = this.iconShadowBottomRightSegments.concat(this.findBottomRightSegments(path));
        }.bind(this));
        this.length = INITIAL_SHADOW_LENGTH;

        // store shadow template and cut with base
        this.applyShadow();
    }

    findNextTangent(path) {
        // go over each curve and try finding tangent with target angle
        for (let i = 0; i < path.curves.length; ++i) {
            let curve = path.curves[i];

            // search for tangent within curve
            let timeParams = this.findNextTangentFromCurve(curve);
            if (timeParams.length > 0) {
                // don't split curve right at the beginning (otherwise it might never stop splitting there ...)
                if (i === 0 && timeParams[0] < 1E-10) {
                    timeParams.splice(0, 1);
                    if (timeParams.length === 0) continue;
                }

                return {
                    curveIdx: i,
                    timeParams: timeParams
                };
            }

            // check for hard edges between this and the next curve
            let nextCurve = path.curves[(i + 1) % path.curves.length];
            let hardEdge = (curve.handle2.x === 0 && curve.handle2.y === 0) || (nextCurve.handle1.x === 0 || nextCurve.handle1.y === 0);
            hardEdge = hardEdge || (Math.abs(curve.getTangentAt(1, true).angle - nextCurve.getTangentAt(0, true).angle) > 1E-10);
            if (!hardEdge) continue;
            // (possible) TODO
            // Currently path will be split along every hard edge. This might not be super efficient for very large paths.
            return {
                curveIdx: i,
                timeParams: [1]
            };
        }
        return undefined;
    }

    findNextTangentFromCurve(curve) {
        // in case of missing handles return
        if (curve.handle1.x === 0 && curve.handle1.y === 0 && curve.handle2.x === 0 && curve.handle2.y === 0) {
            return [];
        }

        // find tangent with target angle (45 degrees)
        let points = [
            curve.point1,
            curve.point1.add(curve.handle1),
            curve.point2.add(curve.handle2),
            curve.point2
        ];

        let a = {};
        let b = {};
        let c = {};

        a.x = 3 * points[3].x - 9 * points[2].x + 9 * points[1].x - 3 * points[0].x;
        a.y = 3 * points[3].y - 9 * points[2].y + 9 * points[1].y - 3 * points[0].y;

        b.x = 6 * points[2].x - 12 * points[1].x + 6 * points[0].x;
        b.y = 6 * points[2].y - 12 * points[1].y + 6 * points[0].y;

        c.x = 3 * points[1].x - 3 * points[0].x;
        c.y = 3 * points[1].y - 3 * points[0].y;

        let t = {
            x: Math.cos(TARGET_ANGLE),
            y: Math.sin(TARGET_ANGLE)
        };

        let timeParams = [];
        let den = 2 * a.x * t.y - 2 * a.y * t.x;
        if (Math.abs(den) < 1E-10) {
            let num = a.x * c.y - a.y * c.x;
            den = a.x * b.y - a.y * b.x;
            if (den != 0) {
                let time = -num / den;
                if (time >= 0 && time <= 1) timeParams.push(time);
            }
        } else {
            let delta = (b.x * b.x - 4 * a.x * c.x) * t.y * t.y + (-2 * b.x * b.y + 4 * a.y * c.x + 4 * a.x * c.y) * t.x * t.y + (b.y * b.y - 4 * a.y * c.y) * t.x * t.x;
            let k = b.x * t.y - b.y * t.x;
            timeParams = [];
            if (delta >= 0 && den != 0) {
                let d = Math.sqrt(delta);
                let t0 = -(k + d) / den;
                let t1 = (-k + d) / den;

                if (Math.abs(t0 - 1) < 1E-5) t0 = 1;
                if (Math.abs(t1 - 1) < 1E-5) t1 = 1;

                if (t0 >= 0 && t0 < 1) timeParams.push(t0);
                if (t1 >= 0 && t1 < 1) timeParams.push(t1);
            }
        }
        if (timeParams.length === 0) return timeParams;

        // clean values
        for (let i = 0; i < timeParams.length; ++i) {
            let time = timeParams[i];

            // if very close to 0, set value to 0
            if (time < 1E-10) {
                timeParams[i] = 0;
                continue;
            }

            // if very close to 1, set value to 1
            time = Math.abs(1 - time);
            if (time < 1E-10) {
                timeParams[i] = 1;
                continue;
            }
        }

        timeParams.sort(function(a, b) { return a - b; });
        return timeParams;
    }

    findBottomRightSegments(path) {
        let segments = [];
        if (path instanceof paper.CompoundPath) {
            for (let i = 0; i < path.children.length; ++i) {
                segments = segments.concat(this.findBottomRightSegments(path.children[i]));
            }

        } else {
            for (let i = 0; i < path.segments.length; ++i) {
                let segment = path.segments[i];
                if (segment.point.getDistance(this.iconPath.bounds.topLeft) > INITIAL_SHADOW_LENGTH) {
                    segments.push(segment);
                }
            }
        }

        return segments;
    }

    /**
     * @param scale factor to scale this shadow by.
     */
    scale(scale) {
        this.iconShadowPaths.forEach(function(path) {
            path.scale(scale, this.iconPath.position);
        }.bind(this));
        this.unitedIconShadowPath.scale(scale, this.iconPath.position);
        this.length = this.length * scale;
        this.applyShadow();
    }

    /**
     * @param {paper.Point} delta - how much this icon + shadow should be moved.
     */
    move(delta) {
        this.iconShadowPaths.forEach(function(path) {
            path.position = path.position.add(delta);
        });
        this.unitedIconShadowPath.position = this.unitedIconShadowPath.position.add(delta);
        this.applyShadow();
    }

    /**
     * Remove this shadow form the canvas.
     */
    remove() {
        this.iconShadowPaths.forEach(function(path) {
            path.remove();
        });
        if (this.unitedIconShadowPath) this.unitedIconShadowPath.remove();
        if (this.appliedIconShadowPath) this.appliedIconShadowPath.remove();
    }

    /**
     * Cut the shadow path template(s) to base size. This creates the
     * final shadow path.
     */
    applyShadow() {
        // join sub shadows to from one path
        if (!this.unitedIconShadowPath) this.createUnitedIconShadowPath();

        // cut shadow to base
        let basePath = this.iconBase.getPathWithoutShadows();
        let newAppliedIconShadowPath = this.unitedIconShadowPath.intersect(basePath);

        // save shadow
        if (this.appliedIconShadowPath) {
            this.appliedIconShadowPath.replaceWith(newAppliedIconShadowPath);
            newAppliedIconShadowPath.fillColor = this.appliedIconShadowPath.fillColor;

        } else {
            // create default gradient
            newAppliedIconShadowPath.fillColor = {
                gradient: {
                    stops: [
                        [new paper.Color(0, 0, 0, 1), 0],
                        [new paper.Color(0, 0, 0, 0), 1]
                    ]
                }
            }

        }
        this.appliedIconShadowPath = newAppliedIconShadowPath;
        this.updateAppliedIconShadowPathGradientBounds();

        // move shadow below icon
        this.appliedIconShadowPath.moveBelow(this.iconPath);
    }

    /**
     * Unites all shadow parts to one shadow shape (doesn't intersect with base though,
     * hence can be used as a 'template').
     * Don't call this too often though, uniting shapes is CPU intensive.
     */
    createUnitedIconShadowPath() {
        if (this.unitedIconShadowPath) this.unitedIconShadowPath.remove();
        this.unitedIconShadowPath = new paper.Path(this.iconShadowPaths[0].pathData);
        for (let i = 1; i < this.iconShadowPaths.length; ++i) {
            let subPathCopy = new paper.Path(this.iconShadowPaths[i].pathData);
            let newShadowPath = this.unitedIconShadowPath.unite(subPathCopy);
            subPathCopy.remove();
            this.unitedIconShadowPath.remove();
            this.unitedIconShadowPath = newShadowPath;
        }
    }

    /**
     * Sets the length of this shadow (diagonal)
     * @param {Number} length
     */
    setLength(length) {
        let deltaLength = length - this.length;
        let translation = Math.sqrt(deltaLength * deltaLength / 2);
        if (deltaLength < 0) translation = translation * -1;

        for (let i = 0; i < this.iconShadowBottomRightSegments.length; ++i) {
            let segment = this.iconShadowBottomRightSegments[i];
            segment.point = segment.point.add(translation);
        }

        this.length = length;
        this.createUnitedIconShadowPath();
        this.applyShadow();
    }

    /**
     * Sets the start intensity of this shadow.
     * @param {Number} intensity - between 0 and 1.
     */
    setIntensity(intensity) {
        this.appliedIconShadowPath.fillColor.gradient.stops[0].color.alpha = intensity;
        // hack: just changing alpha does not trigger a redraw, 'change' this as well
        this.appliedIconShadowPath.fillColor.destination = this.appliedIconShadowPath.fillColor.destination;
    }

    /**
     * Sets where the shadow should begin fading.
     * @param {Number} fading - between 0 and 1. 1 Start fading right away, 0 never fade.
     */
    setFading(fading) {
        this.appliedIconShadowPath.fillColor.gradient.stops[0].rampPoint = 1 - fading;
        // hack: just changing alpha does not trigger a redraw, 'change' this as well
        this.appliedIconShadowPath.fillColor.destination = this.appliedIconShadowPath.fillColor.destination;
    }

    updateAppliedIconShadowPathGradientBounds() {
        let bounds = this.unitedIconShadowPath.bounds;
        this.appliedIconShadowPath.fillColor.origin = bounds.topLeft;
        this.appliedIconShadowPath.fillColor.destination = bounds.bottomRight;
    }

    /**
     * Copies and returns all paths from a given Path / CompoundPath object.
     */
    getAndCopyPaths(pathItem) {
        if (pathItem instanceof paper.Path) {
            return [ new paper.Path(pathItem.pathData) ];
        }

        // copy children
        let children = [];
        for (let i = 0; i < pathItem.children.length; ++i) {
            children.push(new paper.Path(pathItem.children[i].pathData));
        }
        return children;
    }

}

module.exports = IconShadow;
<|endoftext|>


<commit_before>/**
 * Client model
 */
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = require('./user');

// define schema
var schema = mongoose.Schema({
  _user: { type: Schema.Types.ObjectId, ref: 'User' },
  name: { type: String, required: true },
  company_number: String,
  address: {
    street_address: { type: String, required: true },
    town: { type: String, required: true },
    county: { type: String, required: true },
    postcode: { type: String, required: true },
    country: { type: String, required: true }
  },
  created_at: {type: Date, default: Date.now},
  contact: {
    name: { type: String, required: true },
    email: { type: String, required: true },
    phone: String
  }
});

// create model
var Client = mongoose.model('clients', schema);

module.exports = Client;

<commit_msg>naming fix<commit_after>/**
 * Client model
 */
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = require('./user');

// define schema
var schema = mongoose.Schema({
  _user: { type: Schema.Types.ObjectId, ref: 'User' },
  name: { type: String, required: true },
  company_number: String,
  address: {
    street: { type: String, required: true },
    town: { type: String, required: true },
    county: { type: String, required: true },
    postcode: { type: String, required: true },
    country: { type: String, required: true }
  },
  created_at: {type: Date, default: Date.now},
  contact: {
    name: { type: String, required: true },
    email: { type: String, required: true },
    phone: String
  }
});

// create model
var Client = mongoose.model('clients', schema);

module.exports = Client;

<|endoftext|>


<commit_before>/*
 * Class for fetching tube status by parsing an RSS feed from tubeupdates.com
 * This code is cribbed heavily from the Feeds class from the Palm News sample
 * app from the WebOS O'Reilly book.
 */

var Status = Class.create ({
    dummyList: [
        {
            line:"bakerloo",
            status:"unknown",
            style:"unknown",
            details:"none"
        },
        {
            line:"central",
            status:"unknown",
            style:"good-service",
            details:"none"
        },
        {
            line:"circle",
            status:"unknown",
            style:"part-closure",
            details:"none"
        },
        {
            line:"district",
            status:"unknown",
            style:"good-service",
            details:"none"
        },
        {
            line:"hammersmith & city",
            status:"unknown",
            style:"good-service",
            details:"none"
        },
        {
            line:"jubilee",
            status:"unknown",
            style:"good-service",
            details:"none"
        },
        {
            line:"metropolitan",
            status:"unknown",
            style:"good-service",
            details:"none"
        },
        {
            line:"northern",
            status:"unknown",
            style:"good-service",
            details:"none"
        },
        {
            line:"picadilly",
            status:"unknown",
            style:"good-service",
            details:"none"
        },
        {
            line:"victoria",
            status:"unknown",
            style:"good-service",
            details:"none"
        },
        {
            line:"waterloo & city",
            status:"unknown",
            style:"good-service",
            details:"none"
        }
    ],

    initialize: function() {
        this.list = this.dummyList;
        this.statusUrl = "http://api.tubeupdates.com";
        this.lastUpdated = "";
    },

    updateStatus: function() {
        Mojo.Log.info("Fetching tube status from " + this.statusUrl);

        var request = new Ajax.Request(this.statusUrl, {
            method: "get",
            evalJSON: "false",
            parameters: "method=get.status&lines=all&format=xml",
            onSuccess: this.updateStatusSuccess.bind(this),
            onFailure: this.updateStatusFailure.bind(this)
        });
    },

    updateStatusFailure: function(transport) {
        var t = new Template("Status fetch failed with #{status}.");
        var m = new t.evaluate(transport);

        Mojo.Log.info(m);
    },

    updateStatusSuccess: function(transport) {
        Mojo.Log.info("Success");

        // work around occasional XML errors
        if (transport.responseXML == null && transport.responseText !== null) {
            Mojo.log.info("Request not XML, converting");
            transport.responseXML = new DOMParser().parseFromString(
                transport.responseText, "text/xml");
        }

        this.processStatus(transport);

        this.updateListModel();
        this.statusListChanged = true;
    },

    processStatus: function(transport) {
        var lines = $(transport.responseXML).getElementsByTagName("line");
        var stati = [];
        Mojo.Log.info("Processing status");

        /*
         * Iterate the returned lines XML and pull out the data into our
         * this.list
         */
        for (var i = 0; i < lines.length; i++) {
            var s = lines[i].getElementsByTagName("status").item(0).textContent;
            var d = lines[i].getElementsByTagName("messages").item(0).textContent.gsub('\t', '');
            if (d.length < 1)
                d = "";

            stati[i] = {
                line:lines[i].getElementsByTagName("name").item(0).textContent,
                status:s.camelize(),
                style:s.gsub(' ', '-'),
                details:d
            };

            /* Sanitise some statuses, we only have three "styles" */
            switch (stati[i].style)
            {
                case "planned-closure":
                stati[i].style = "bad-service";
                break;
                case "part-suspended":
                case "minor-delays":
                stati[i].style = "part-closure";
                break;
                // this seems weird, but I've seen it in a live feed
                case "home":
                stati[i].style = "unknown";
                break;
            }
        }

        // Set the last update
        this.list = stati;
        this.lastUpdate = new Date();
    },

    registerListModel: function(sceneAssistant) {
        Mojo.Log.info("Model Registered");
        this.listAssistant = sceneAssistant;
    },

    removeListModel: function() {
        Mojo.Log.info("Model Removed");
        this.listAssistant = undefined;
    },

    updateListModel: function() {
        Mojo.Log.info("Model Updated");
        if (this.listAssistant !== undefined) {
            this.listAssistant.statusListModel.items = this.list;
            this.listAssistant.controller.modelChanged(this.listAssistant.statusListModel, this);
        }
    }
});
<commit_msg>Don't show the dummy list, suggest an update<commit_after>/*
 * Class for fetching tube status by parsing an RSS feed from tubeupdates.com
 * This code is cribbed heavily from the Feeds class from the Palm News sample
 * app from the WebOS O'Reilly book.
 */

var Status = Class.create ({
    // dummyList: [
    //     {
    //         line:"bakerloo",
    //         status:"unknown",
    //         style:"unknown",
    //         details:"none"
    //     },
    //     {
    //         line:"central",
    //         status:"unknown",
    //         style:"good-service",
    //         details:"none"
    //     },
    //     {
    //         line:"circle",
    //         status:"unknown",
    //         style:"part-closure",
    //         details:"none"
    //     },
    //     {
    //         line:"district",
    //         status:"unknown",
    //         style:"good-service",
    //         details:"none"
    //     },
    //     {
    //         line:"hammersmith & city",
    //         status:"unknown",
    //         style:"good-service",
    //         details:"none"
    //     },
    //     {
    //         line:"jubilee",
    //         status:"unknown",
    //         style:"good-service",
    //         details:"none"
    //     },
    //     {
    //         line:"metropolitan",
    //         status:"unknown",
    //         style:"good-service",
    //         details:"none"
    //     },
    //     {
    //         line:"northern",
    //         status:"unknown",
    //         style:"good-service",
    //         details:"none"
    //     },
    //     {
    //         line:"picadilly",
    //         status:"unknown",
    //         style:"good-service",
    //         details:"none"
    //     },
    //     {
    //         line:"victoria",
    //         status:"unknown",
    //         style:"good-service",
    //         details:"none"
    //     },
    //     {
    //         line:"waterloo & city",
    //         status:"unknown",
    //         style:"good-service",
    //         details:"none"
    //     }
    // ],

    initialize: function() {
        //this.list = this.dummyList;
        this.list = [{
                         line: "Press 'Update Status' to fetch",
                         style: "bad-service"
                     }];
        this.statusUrl = "http://api.tubeupdates.com";
        this.lastUpdated = "";
    },

    updateStatus: function() {
        Mojo.Log.info("Fetching tube status from " + this.statusUrl);

        var request = new Ajax.Request(this.statusUrl, {
            method: "get",
            evalJSON: "false",
            parameters: "method=get.status&lines=all&format=xml",
            onSuccess: this.updateStatusSuccess.bind(this),
            onFailure: this.updateStatusFailure.bind(this)
        });
    },

    updateStatusFailure: function(transport) {
        var t = new Template("Status fetch failed with #{status}.");
        var m = new t.evaluate(transport);

        Mojo.Log.info(m);
    },

    updateStatusSuccess: function(transport) {
        Mojo.Log.info("Success");

        // work around occasional XML errors
        if (transport.responseXML == null && transport.responseText !== null) {
            Mojo.log.info("Request not XML, converting");
            transport.responseXML = new DOMParser().parseFromString(
                transport.responseText, "text/xml");
        }

        this.processStatus(transport);

        this.updateListModel();
        this.statusListChanged = true;
    },

    processStatus: function(transport) {
        var lines = $(transport.responseXML).getElementsByTagName("line");
        var stati = [];
        Mojo.Log.info("Processing status");

        /*
         * Iterate the returned lines XML and pull out the data into our
         * this.list
         */
        for (var i = 0; i < lines.length; i++) {
            var s = lines[i].getElementsByTagName("status").item(0).textContent;
            var d = lines[i].getElementsByTagName("messages").item(0).textContent.gsub('\t', '');
            if (d.length < 1)
                d = "";

            stati[i] = {
                line:lines[i].getElementsByTagName("name").item(0).textContent,
                status:s.camelize(),
                style:s.gsub(' ', '-'),
                details:d
            };

            /* Sanitise some statuses, we only have three "styles" */
            switch (stati[i].style)
            {
                case "planned-closure":
                stati[i].style = "bad-service";
                break;
                case "part-suspended":
                case "minor-delays":
                stati[i].style = "part-closure";
                break;
                // this seems weird, but I've seen it in a live feed
                case "home":
                stati[i].style = "unknown";
                break;
            }
        }

        // Set the last update
        this.list = stati;
        this.lastUpdate = new Date();
    },

    registerListModel: function(sceneAssistant) {
        Mojo.Log.info("Model Registered");
        this.listAssistant = sceneAssistant;
    },

    removeListModel: function() {
        Mojo.Log.info("Model Removed");
        this.listAssistant = undefined;
    },

    updateListModel: function() {
        Mojo.Log.info("Model Updated");
        if (this.listAssistant !== undefined) {
            this.listAssistant.statusListModel.items = this.list;
            this.listAssistant.controller.modelChanged(this.listAssistant.statusListModel, this);
        }
    }
});
<|endoftext|>


<commit_before>// Token calculating functions found inline on translate.google.com

var cM = function(a) {
            return function() {
                return a
            }
        };
var of = "=";
var dM = function(a, b) {
    for (var c = 0; c < b.length - 2; c += 3) {
        var d = b.charAt(c + 2),
            d = d >= t ? d.charCodeAt(0) - 87 : Number(d),
            d = b.charAt(c + 1) == Tb ? a >>> d : a << d;
        a = b.charAt(c) == Tb ? a + d & 4294967295 : a ^ d
    }
    return a
};

var eM = null;
var cb = 0;
var k = "";
var Vb = "+-a^+6";
var Ub = "+-3^+b+-f";
var t = "a";
var Tb = "+";
var dd = ".";

fM = function(a) {
    var b;
    if (null === eM) {
        var c = cM(String.fromCharCode(84)); // char 84 is T
        b = cM(String.fromCharCode(75)); // char 75 is K
        c = [c(), c()];
        c[1] = b();
        // So basically we're getting window.TKK
        eM = Number(window[c.join(b())]) || 0
    }
    b = eM;

    // All this does is convert turn d into the byte values of the utf-8 representation of a
    var d = cM(String.fromCharCode(116)),
        c = cM(String.fromCharCode(107)),
        d = [d(), d()];
    d[1] = c();
    for (var c = cb + d.join(k) +
            of, d = [], e = 0, f = 0; f < a.length; f++) {
        var g = a.charCodeAt(f);

        128 > g ? d[e++] = g : (2048 > g ? d[e++] = g >> 6 | 192 : (55296 == (g & 64512) && f + 1 < a.length && 56320 == (a.charCodeAt(f + 1) & 64512) ? (g = 65536 + ((g & 1023) << 10) + (a.charCodeAt(++f) & 1023), d[e++] = g >> 18 | 240, d[e++] = g >> 12 & 63 | 128) : d[e++] = g >> 12 | 224, d[e++] = g >> 6 & 63 | 128), d[e++] = g & 63 | 128)
    }
    // So now utf8(d) == a

    a = b || 0;
    for (e = 0; e < d.length; e++) a += d[e], a = dM(a, Vb);
    a = dM(a, Ub);
    0 > a && (a = (a & 2147483647) + 2147483648);
    a %= 1E6;
    return a.toString() + dd + (a ^ b)
};

console.log(fM("Hello person"));
<commit_msg>Removed cruft token-script.js<commit_after><|endoftext|>


<commit_before>Crafty.c("Button", {
    settings: {
        css: {
            "border-color" : "white",
            "border-style" : "solid",
            "border-width" : "1px",
            "color"        : "white",
            "text-align"   : "center",
        }
    },

    init: function () {
        var self = this;

        self.requires("2D, DOM, Mouse, Text");

        self._label         = "";
        self._callback      = null;
        self._enabled       = true;
        self._default_alpha = null;

        self.css(self.settings.css);
        self.textFont({
            size   : "12px",
            family : "monospace"
        });

        self.bind("Click", function () {
            if (self._enabled) {
                self._callback();
            }
        });

        self.bind("MouseOver", function () {
            if (self._enabled) {
                self._default_alpha = self.alpha;
                self.alpha = 1;
            }
        });
        self.bind("MouseOut", function () {
            if (self._enabled) {
                self.alpha = self._default_alpha;
            }
        });

        self.bind("Draw", function (e) {
            var line_height = self._h;

            self._element.innerHTML = "
"
                                    + self._label
                                    + "
";
        });

        return self;
    },

    label: function (label) {
        var self = this;

        if (typeof(label) !== "undefined") {
            self._label = label;
        } else {
            return self._label;
        }

        return self;
    },

    callback: function (callback) {
        var self = this;

        if (typeof(callback) !== "undefined") {
            self._callback = callback;
        } else {
            return self._callback;
        }

        return self;
    },

    enable: function () {
        var self = this;

        self._enabled = true;
        self.alpha = self._default_alpha;

        return self;
    },

    disable: function () {
        var self = this;

        self._enabled = false;
        self._default_alpha = self.alpha;
        self.alpha = 0.2;

        return self;
    }
});

Crafty.c("Selector", {
    settings: {
        selection_css: {
            "color"       : "white",
            "font-family" : "sans-serif",
            "padding-top" : "10px",
            "text-align"  : "center"
        },
        left_css: {
            "font-size" : "xx-small"
        },
        right_css: {
            "font-size" : "xx-small"
        },
        selector_button: {
            "width"  : 30,
            "height" : 30
        }
    },

    init: function () {
        var self = this;

        self.requires("2D, DOM");

        self._left      = null;
        self._right     = null;
        self._selection = null;

        self._options   = [];
        self._current   = 0;

        self._callback  = function () {};

        self._add_child_components();

        self.bind("Change", function () {
            self._left.attr(self._get_selector_button_attributes());
            self._right.attr(self._get_selector_button_attributes(1));
            self._selection.attr(self._get_selection_attributes());

            self._selection.text(self._options[self._current]);
            self._callback(self._options[self._current]);
        });

        return self;
    },

    _add_child_components: function () {
        var self = this;

        self._selection = Crafty.e("2D, DOM, Text")
                                .css(self.settings.selection_css);

        self._left = Crafty.e("Button")
                           .label("<")
                           .css(self.settings.left_css)
                           .callback(function () {
                               self._current = self._current - 1;
                               if (self._current < 0) {
                                   self._current = self._options.length - 1;
                               }

                               self.trigger("Change");
                           });
        self._right = Crafty.e("Button")
                            .label(">")
                            .css(self.settings.right_css)
                            .callback(function () {
                                self._current = self._current + 1;
                                if (self._current >= self._options.length) {
                                    self._current = 0;
                                }

                                self.trigger("Change");
                            });

        self.attach(self._selection, self._left, self._right);

        return self;
    },

    _get_selector_button_attributes: function (is_right) {
        var self = this;

        var x = self._x;
        var y = self._y;

        if (is_right) {
            x += self._w - self.settings.selector_button.width;
        }

        return {
            "w"     : self.settings.selector_button.width,
            "h"     : self.settings.selector_button.height,
            "x"     : x,
            "y"     : y,
            "alpha" : 0.8
        };
    },

    _get_selection_attributes: function () {
        var self = this;

        return {
            "x" : self._x + self.settings.selector_button.width,
            "y" : self._y,
            "w" : self._w - (self.settings.selector_button.width * 2),
            "h" : self.settings.selector_button.height
        };
    },

    options: function (options) {
        var self = this;

        if (typeof(options) !== "undefined") {
            self._options = options;
        } else {
            return self._options;
        }

        self.trigger("Change");

        return self;
    },

    callback: function (callback) {
        var self = this;

        if (typeof(callback) !== "undefined") {
            self._callback = callback;
        } else {
            return self._callback;
        }

        self.trigger("Change");

        return self;
    },

    get_selected: function () {
        var self = this;

        return self._options[self._current_select];
    },

    reset: function () {
        var self = this;

        self._current_select = 0;

        return self;
    }
});

Crafty.c("GridContainer", {
    init: function () {
        var self = this;

        self.requires("2D, DOM");

        self._padding_top    = 0;
        self._padding_bottom = 0;
        self._padding_left   = 0;
        self._padding_right  = 0;

        self._grid_num_columns = 0;
        self._grid_num_rows    = 0;

        self.css({
            "border-color" : "white",
            "border-style" : "solid",
            "border-width" : "1px",
            "color"        : "white"
        });

        self.alpha = 0.8;

        return self;
    },

    set_position: function (x, y) {
        var self = this;

        self.x = x;
        self.y = y;

        return self;
    },

    set_size: function (width, height) {
        var self = this;

        self.w = width;
        self.h = height;

        return self;
    },

    set_padding: function (top, bottom, left, right) {
        var self = this;

        self._padding_top    = top;
        self._padding_bottom = bottom;
        self._padding_left   = left;
        self._padding_right  = right;

        return self;
    },

    set_grid: function (columns, rows) {
        var self = this;

        self._grid_num_columns = columns;
        self._grid_num_rows    = rows;

        return self;
    },

    add: function () {
        var self = this;

        var items = arguments[0] instanceof Array
                  ? Array.prototype.slice.call(arguments)
                  : [ Array.prototype.slice.call(arguments) ];

        items.forEach(function (item) {
            var element = item[0];
            var column  = item[1];
            var row     = item[2];

            self._place_element(element, column, row);
            self.attach(element);
        });

        return self;
    },

    _place_element: function (element, column, row) {
        var self = this;

        var grid_element_width = (self._w - self._padding_left - self._padding_right)
                               / self._grid_num_columns;
        var grid_element_height = (self._h - self._padding_top - self._padding_bottom)
                                / self._grid_num_rows;

        var left = (grid_element_width * column) + self._padding_left;
        var top = (grid_element_height * row) + self._padding_top;

        element.x = parseInt(left + (grid_element_width / 2) - (element._w / 2) + self._x);
        element.y = parseInt(top + (grid_element_height / 2) - (element._h / 2) + self._y);

        return self;
    }
});
<commit_msg>Elements added to GridComponent react to resizing, repositiong, and repadding properly now.<commit_after>Crafty.c("Button", {
    settings: {
        css: {
            "border-color" : "white",
            "border-style" : "solid",
            "border-width" : "1px",
            "color"        : "white",
            "text-align"   : "center",
        }
    },

    init: function () {
        var self = this;

        self.requires("2D, DOM, Mouse, Text");

        self._label         = "";
        self._callback      = null;
        self._enabled       = true;
        self._default_alpha = null;

        self.css(self.settings.css);
        self.textFont({
            size   : "12px",
            family : "monospace"
        });

        self.bind("Click", function () {
            if (self._enabled) {
                self._callback();
            }
        });

        self.bind("MouseOver", function () {
            if (self._enabled) {
                self._default_alpha = self.alpha;
                self.alpha = 1;
            }
        });
        self.bind("MouseOut", function () {
            if (self._enabled) {
                self.alpha = self._default_alpha;
            }
        });

        self.bind("Draw", function (e) {
            var line_height = self._h;

            self._element.innerHTML = "
"
                                    + self._label
                                    + "
";
        });

        return self;
    },

    label: function (label) {
        var self = this;

        if (typeof(label) !== "undefined") {
            self._label = label;
        } else {
            return self._label;
        }

        return self;
    },

    callback: function (callback) {
        var self = this;

        if (typeof(callback) !== "undefined") {
            self._callback = callback;
        } else {
            return self._callback;
        }

        return self;
    },

    enable: function () {
        var self = this;

        self._enabled = true;
        self.alpha = self._default_alpha;

        return self;
    },

    disable: function () {
        var self = this;

        self._enabled = false;
        self._default_alpha = self.alpha;
        self.alpha = 0.2;

        return self;
    }
});

Crafty.c("Selector", {
    settings: {
        selection_css: {
            "color"       : "white",
            "font-family" : "sans-serif",
            "padding-top" : "10px",
            "text-align"  : "center"
        },
        left_css: {
            "font-size" : "xx-small"
        },
        right_css: {
            "font-size" : "xx-small"
        },
        selector_button: {
            "width"  : 30,
            "height" : 30
        }
    },

    init: function () {
        var self = this;

        self.requires("2D, DOM");

        self._left      = null;
        self._right     = null;
        self._selection = null;

        self._options   = [];
        self._current   = 0;

        self._callback  = function () {};

        self._add_child_components();

        self.bind("Change", function () {
            self._left.attr(self._get_selector_button_attributes());
            self._right.attr(self._get_selector_button_attributes(1));
            self._selection.attr(self._get_selection_attributes());

            self._selection.text(self._options[self._current]);
            self._callback(self._options[self._current]);
        });

        return self;
    },

    _add_child_components: function () {
        var self = this;

        self._selection = Crafty.e("2D, DOM, Text")
                                .css(self.settings.selection_css);

        self._left = Crafty.e("Button")
                           .label("<")
                           .css(self.settings.left_css)
                           .callback(function () {
                               self._current = self._current - 1;
                               if (self._current < 0) {
                                   self._current = self._options.length - 1;
                               }

                               self.trigger("Change");
                           });
        self._right = Crafty.e("Button")
                            .label(">")
                            .css(self.settings.right_css)
                            .callback(function () {
                                self._current = self._current + 1;
                                if (self._current >= self._options.length) {
                                    self._current = 0;
                                }

                                self.trigger("Change");
                            });

        self.attach(self._selection, self._left, self._right);

        return self;
    },

    _get_selector_button_attributes: function (is_right) {
        var self = this;

        var x = self._x;
        var y = self._y;

        if (is_right) {
            x += self._w - self.settings.selector_button.width;
        }

        return {
            "w"     : self.settings.selector_button.width,
            "h"     : self.settings.selector_button.height,
            "x"     : x,
            "y"     : y,
            "alpha" : 0.8
        };
    },

    _get_selection_attributes: function () {
        var self = this;

        return {
            "x" : self._x + self.settings.selector_button.width,
            "y" : self._y,
            "w" : self._w - (self.settings.selector_button.width * 2),
            "h" : self.settings.selector_button.height
        };
    },

    options: function (options) {
        var self = this;

        if (typeof(options) !== "undefined") {
            self._options = options;
        } else {
            return self._options;
        }

        self.trigger("Change");

        return self;
    },

    callback: function (callback) {
        var self = this;

        if (typeof(callback) !== "undefined") {
            self._callback = callback;
        } else {
            return self._callback;
        }

        self.trigger("Change");

        return self;
    },

    get_selected: function () {
        var self = this;

        return self._options[self._current_select];
    },

    reset: function () {
        var self = this;

        self._current_select = 0;

        return self;
    }
});

Crafty.c("GridContainer", {
    init: function () {
        var self = this;

        self.requires("2D, DOM");

        self._padding_top    = 0;
        self._padding_bottom = 0;
        self._padding_left   = 0;
        self._padding_right  = 0;

        self._grid_num_columns = 0;
        self._grid_num_rows    = 0;

        self._elements = {};

        self.css({
            "border-color" : "white",
            "border-style" : "solid",
            "border-width" : "1px",
            "color"        : "white"
        });

        self.alpha = 0.8;

        return self;
    },

    set_position: function (x, y) {
        var self = this;

        self.x = x;
        self.y = y;

        self._place_all_elements();

        return self;
    },

    set_size: function (width, height) {
        var self = this;

        self.w = width;
        self.h = height;

        self._place_all_elements();

        return self;
    },

    set_padding: function (top, bottom, left, right) {
        var self = this;

        self._padding_top    = top;
        self._padding_bottom = bottom;
        self._padding_left   = left;
        self._padding_right  = right;

        self._place_all_elements();

        return self;
    },

    set_grid: function (columns, rows) {
        var self = this;

        self._grid_num_columns = columns;
        self._grid_num_rows    = rows;

        self._place_all_elements();

        return self;
    },

    add: function () {
        var self = this;

        var items = arguments[0] instanceof Array
                  ? Array.prototype.slice.call(arguments)
                  : [ Array.prototype.slice.call(arguments) ];

        items.forEach(function (item) {
            var id      = item[0];
            var element = item[1];
            var column  = item[2];
            var row     = item[3];

            self._place_element(element, column, row);

            self._elements[id] = {
                element : element,
                column  : column,
                row     : row
            };
        });

        return self;
    },

    remove: function () {
        var self = this;

        var ids = arguments[0] instanceof Array
                ? Array.prototype.slice.call(arguments)
                : [ Array.prototype.slice.call(arguments) ];

        ids.forEach(function (id) {
            self._elements[id].element.destroy();

            delete self._elements[id];
        });

        return self;
    },

    _place_element: function (element, column, row) {
        var self = this;

        var grid_element_width = (self._w - self._padding_left - self._padding_right)
                               / self._grid_num_columns;
        var grid_element_height = (self._h - self._padding_top - self._padding_bottom)
                                / self._grid_num_rows;

        var left = (grid_element_width * column) + self._padding_left;
        var top = (grid_element_height * row) + self._padding_top;

        element.x = parseInt(left + (grid_element_width / 2) - (element._w / 2) + self._x);
        element.y = parseInt(top + (grid_element_height / 2) - (element._h / 2) + self._y);

        return self;
    },

    _place_all_elements: function () {
        var self = this;

        Object.keys(self._elements).forEach(function (id) {
            var item = self._elements[id];

            self._place_element(item.element, item.column, item.row);
        });

        return self;
    }
});
<|endoftext|>