<commit_before>(function() {
    // Globals
    var punchIter = 0;

    // Main class which holds all game elements such as players, enemies and items.
    // Game class runs main game loop (tick). It defines two main steps for each game
    // loop iterations - update (react on current state and time) and render.
    var Game = function() {
        this.size = { width: 1600, height: 700 };
        this.level = new Level('tlo.png');
        this.players = [];
        this.enemies = [];
        this.items = [];

        // Bootload
        /* Players */
        this.players = this.players.concat(new Player(
            'rudydres',
            50,
            500,
            'rudydres.png',
            new Mask(100, 500, 150, 650),
            100,    /* statStrength */
            70,     /* statRange */
            new Keys(37, 39, 38, 16, 13, 191, 222)));
        this.players = this.players.concat(new Player(
            'rudydres2',
            150,
            500,
            'rudydres2.png',
            new Mask(200, 500, 350, 650),
            100,    /* statStrength */
            70,     /* statRange */
            new Keys(65, 68, 87, 81, 49, 50, 51)));
        /* Enemies */
        this.enemies = this.enemies.concat(new Enemy(
            'lysyblokers',
            500,
            500,
            'lysyblokers.png',
            new Mask(490, 500, 510, 650),
            70,    /* statStrength */
            60     /* statRange */));
        this.enemies = this.enemies.concat(new Enemy(
            'nerbisDres',
            600,
            500,
            'nerbisDres.png',
            new Mask(590, 500, 610, 650),
            110,   /* statStrength */
            50     /* statRange */));

        var gameDiv = document.getElementById('game');
        gameDiv.style.width = this.size.width;
        gameDiv.style.height = this.size.height;
        this.level.render();

        var self = this;
        var tick = function() {
            self.update();
            self.render();
            requestAnimationFrame(tick);
        };
        tick();
    };
    Game.prototype = {
        update: function() {
            for (var i = 0; i < this.players.length; i++) {
                for (var j = 0; j < this.players.length; j++) {
                    if (this.players[i] != this.players[j]) {

                        var dres1 = this.players[i].x;
                        var dres2 = this.players[j].x;

                        if (dres1 > dres2 - 50 && dres1 < dres2 + 50) {
                            if (dres1 > dres2) {
                                this.players[i].x += 3;
                            }
                            if (dres1 < dres2) {
                                this.players[i].x -= 3;
                            }
                        }
                    }
                }
                this.players[i].update(this.players.concat(this.enemies));
            }
            for (var i = 0; i < this.enemies.length; i++) {
                this.enemies[i].update(this.players);
            }
            for (var i = 0; i < this.items.length; i++) {
                this.items[i].update();
            }
            this.level.update();
        },

        render: function() {
            var gameDiv = document.getElementById('game');
            gameDiv.style.top = Math.max(0, (document.body.clientHeight - this.size.height) / 2);
            gameDiv.style.left = Math.max(0, (document.body.clientWidth - this.size.width) / 2);
            clientWidth = document.body.clientWidth;
            if (clientWidth < this.size.width) {
                window.scrollTo(Math.min(this.players[0].x + 75 - clientWidth / 2, this.size.width - clientWidth), this.size.width);
            }
            for (var i = 0; i < this.players.length; i++) {
                this.players[i].render();
            }
            for (var i = 0; i < this.enemies.length; i++) {
                this.enemies[i].render();
            }
            for (var i = 0; i < this.items.length; i++) {
                this.items[i].render();
            }
        },

        takeItem: function(name) {
            for (var i = 0; i < this.items; i++) {
                if (this.items[i].name == name) {
                    var item = this.items[i];
                    this.items.splice(i, 1);
                    return item;
                }
            }
        },

        changeLevel: function(level, newX, newY) {
            // remove all items
            for (var i = 0; i < this.items.length; i++) {
                var itemDiv = document.getElementById(this.items[i].name);
                itemDiv.parentNode.removeChild(itemDiv);
            }
            this.items = [];
            // remove all enemies
            for (var i = 0; i < this.enemies.length; i++) {
                var enemyDiv = document.getElementById(this.enemies[i].name);
                enemyDiv.parentNode.removeChild(enemyDiv);
            }
            this.enemies = [];
            // move all players
            for (var i = 0; i < this.players.length; i++) {
                var player = this.players[i];
                player.x = newX;
                player.y = newY;
            }
            // change level and let it render
            this.level = level;
        }
    };

    // Level defines background and masks (platforms, walls, etc.) which define where elements can move.
    var Level = function(background) {
        this.background = background;
        // colision rectangles for elements
        this.masks = [];
        // doors to other levels
        this.doors = [];

        // initialization
        this.masks = this.masks.concat(new Mask(0, 650, 1600, 660));
        this.masks = this.masks.concat(new Mask(-100, 0, -1, 700));
        this.masks = this.masks.concat(new Mask(1600, 0, 1700, 700));
    };
    Level.prototype = {
        update: function() {
        },
        render: function() {
            document.getElementById('game').style.backgroundImage = 'url(' + this.background + ')';
        }
    };

    // Masks are invisible collision rectangles which define when elements interact with Levels and other elements.
    var Mask = function(minX, maxX, minY, maxY) {
        this.minX = minX;
        this.maxX = maxX;
        this.minY = minY;
        this.maxY = maxY;
    };

    // Rectangle area which defines where player can switch the level to the one mapped to one configured by this door.
    var Door = function(mask, level) {
        this.mask = mask;
        this.level = level;
    };

    // Element is a class for items, laying weapons, effects like blood or explosion. It is also base class for players and enemies.
    var Element = function(name, x, y, image, mask) {
        this.name = name;
        this.x = x;
        this.y = y;
        this.image = image;
        this.animationFrame = 0;
        this.scaleX = 1;
        this.mask = mask;
        this.state = 'nothing';
        this.stateStart = new Date().getTime();

        // initialization
        var img = document.createElement('img');
        img.setAttribute('id', this.name + '-image');
        img.setAttribute('src', this.image);

        var div = document.createElement('div');
        div.setAttribute('id', this.name);
        div.setAttribute('class', 'element');
        div.style.left = this.x;
        div.style.top = this.y;
        div.appendChild(img);

        document.getElementById('game').appendChild(div);
    };
    Element.prototype = {
        update: function() {
        },
        render: function() {
        }
    };

    // TODO Add statHitSpeed, statWalkSpeed
    // Base class for Player and Enemy
    var Character = function(name, x, y, image, mask, statStrength, statRange) {
        // call super constructor
        Element.call(this, name, x, y, image, mask);

        /* Assign stats */
        this.statStrength = statStrength;   /* Defines how far the enemy is knocked back after a hit */
        this.statRange = statRange;         /* Defines range of the punches */
    };
    // TODO Caleb: not sure if this is correct way to inherit the prototype, and most likely it's not supported in Opera
    // TODO (see http://javascript.info/tutorial/inheritance)
    Character.prototype = Element;

    // NPC - simple walking motherfucker
    var Enemy = function(name, x, y, image, mask, statStrength, statRange) {
        // call super constructor
        Character.call(this, name, x, y, image, mask, statStrength, statRange);

        // initialization
        this.state = 'walk';
        document.getElementById(this.name).setAttribute('class', 'element dres');
    };
    Enemy.prototype = {
        update: function(players) {
            // TODO add detection of players in range and punching them.

            /* Move 3px in direction indicated by scaleX */
            this.x += 3 * this.scaleX;
            this.animationFrame++;
            /* When character is reaching the end of the game screen, change its move direction */
            // TODO change to colision with Level masks
            if ((this.x < 200 && this.scaleX < 0) || (this.x > 1250 && this.scaleX > 0))
                this.scaleX *= -1;
        },
        render: function() {
            var enemy = document.getElementById(this.name);
            var enemyimage = document.getElementById(this.name + '-image');
            enemy.style.left = this.x + 'px';
            enemy.style.top = this.y + 'px';

            enemy.style.transform = 'scaleX(' + this.scaleX + ')';
            enemyimage.style.marginLeft = -((Math.floor(this.animationFrame / 5) % 8) * 150) + 'px';
        }
    };

    // Player is strearable element with health and items.
    var Player = function(name, x, y, image, mask, statStrength, statRange, keys) {
        Character.call(this, name, x, y, image, mask, statStrength, statRange);
        this.keys = keys;
        this.jump = 0;
        this.jumpStart = 0;

        // initialization
        this.state = 'walk';
        document.getElementById(this.name).setAttribute('class', 'element dres');
    };
    Player.prototype = {
        update: function(bodies) {
            var punchSound;
            if (this.keys.isKeyDown(this.keys.punch)) {
                if (this.state != "punch") {
                    this.animationFrame = 0;
                    this.state = "punch";
                    /* This is actually a whoosh sound and will always be played, despite if punch hits or misses.
                     * TODO: change name from punchSoundMiss to punchSoundWhoosh or something. */
                    punchSound = document.getElementById("punchSoundMiss" + (punchIter++ % 2));

                } else if (this.animationFrame < 15) {
                    this.animationFrame += 5;
                    /* Punch other character if it's within the punch range. We only punch when the arm is stretched
                     * (this.anim === 10 ?) */
                    var hit = false;
                    if (this.animationFrame === 10)
                    /* Check if any other character is within punch range */
                        for (var i = 0; i < bodies.length; i++) {
                            var otherDres = bodies[i];
                            if (this === otherDres)
                                continue;

                            var thisDresPosition = this.x * this.scaleX;
                            var otherDresPosition = otherDres.x * this.scaleX;
                            /* If the otherDres is within punch range, knock him 100 pixels back */
                            if (thisDresPosition < otherDresPosition && thisDresPosition + this.statRange > otherDresPosition) {
                                otherDres.x += this.statStrength * this.scaleX;
                                /* Turn the otherDres back after he has been hit. */
                                otherDres.scaleX = this.scaleX;
                                hit = true;
                            }
                        }
                    if (hit === true)
                        punchSound = document.getElementById("punchSoundHit");
                }
                if (punchSound) {
                    punchSound.load();
                    punchSound.play();
                }
            } else if (this.keys.isKeyDown(this.keys.left) && this.x > 0) {
                // TODO change those x limits to colision model based on Level masks.
                this.state = "walk";
                this.x -= 3;
                this.animationFrame++;
                this.scaleX = -1;
            } else if (this.keys.isKeyDown(this.keys.right) && this.x < 1450) {
                this.state = "walk";
                this.x += 3;
                this.animationFrame++;
                this.scaleX = 1;
            } else {
                this.state = "walk";
                this.animationFrame = 0;
            }

            if (this.keys.isKeyDown(this.keys.jump) && this.jump === 0) {
                this.jump = 1;
                this.jumpStart = new Date().getTime();
                sound = document.getElementById(this.name + "JumpSound");
                if (sound) {
                    sound.load();
                    sound.play();
                }
            }
            if (this.jump < 0) {
                this.jump = 0;
            } else if (this.jump > 0) {
                var x = new Date().getTime() - this.jumpStart;
                if (x > 0) {
                    this.jump = x * x / -625 + 4 * x / 5;
                }
            }
        },
        render: function() {
            var rudydres = document.getElementById(this.name);
            var rudydresimage = document.getElementById(this.name + '-image');
            rudydres.style.left = this.x + 'px';
            rudydres.style.top = this.y - this.jump + 'px';

            rudydres.style.transform = 'scaleX(' + this.scaleX + ')';
            rudydresimage.style.marginLeft = -((Math.floor(this.animationFrame / 5) % 8) * 150) + 'px';
            if (this.state === "walk") {
                rudydresimage.style.marginTop = null;
            } else if (this.state === "punch") {
                rudydresimage.style.marginTop = '-150px';
            }
        }
    };

    // Keys class maps keys ascii codes into action names like left, right, punch, etc.
    var Keys = function(left, right, jump, punch, kick, head, itemSwitch) {
        this.left = left;
        this.right = right;
        this.jump = jump;
        this.punch = punch;
        this.kick = kick;
        this.head = head;
        this.itemSwitch = itemSwitch;

        // initialization
        var keyState = {};
        window.addEventListener('keydown', function(e) { keyState[e.keyCode] = true; });
        window.addEventListener('keyup', function(e) { keyState[e.keyCode] = false; });
        this.isKeyDown = function(key) {
            return keyState[key] === true;
        };
    };

    // run game when ready
    window.addEventListener('load', function() { new Game(); });
})();<commit_msg>Added simple skeleton for Enemy punching. No animations or sounds yet, but Players get knocked back after encounter with an Enemy.<commit_after>(function() {
    // Globals
    var punchIter = 0;

    // Main class which holds all game elements such as players, enemies and items.
    // Game class runs main game loop (tick). It defines two main steps for each game
    // loop iterations - update (react on current state and time) and render.
    var Game = function() {
        this.size = { width: 1600, height: 700 };
        this.level = new Level('tlo.png');
        this.players = [];
        this.enemies = [];
        this.items = [];

        // Bootload
        /* Players */
        this.players = this.players.concat(new Player(
            'rudydres',
            50,
            500,
            'rudydres.png',
            new Mask(100, 500, 150, 650),
            100,    /* statStrength */
            70,     /* statRange */
            new Keys(37, 39, 38, 16, 13, 191, 222)));
        this.players = this.players.concat(new Player(
            'rudydres2',
            150,
            500,
            'rudydres2.png',
            new Mask(200, 500, 350, 650),
            100,    /* statStrength */
            70,     /* statRange */
            new Keys(65, 68, 87, 81, 49, 50, 51)));
        /* Enemies */
        this.enemies = this.enemies.concat(new Enemy(
            'lysyblokers',
            500,
            500,
            'lysyblokers.png',
            new Mask(490, 500, 510, 650),
            70,    /* statStrength */
            60     /* statRange */));
        this.enemies = this.enemies.concat(new Enemy(
            'nerbisDres',
            600,
            500,
            'nerbisDres.png',
            new Mask(590, 500, 610, 650),
            110,   /* statStrength */
            50     /* statRange */));

        var gameDiv = document.getElementById('game');
        gameDiv.style.width = this.size.width;
        gameDiv.style.height = this.size.height;
        this.level.render();

        var self = this;
        var tick = function() {
            self.update();
            self.render();
            requestAnimationFrame(tick);
        };
        tick();
    };
    Game.prototype = {
        update: function() {
            for (var i = 0; i < this.players.length; i++) {
                for (var j = 0; j < this.players.length; j++) {
                    if (this.players[i] != this.players[j]) {

                        var dres1 = this.players[i].x;
                        var dres2 = this.players[j].x;

                        if (dres1 > dres2 - 50 && dres1 < dres2 + 50) {
                            if (dres1 > dres2) {
                                this.players[i].x += 3;
                            }
                            if (dres1 < dres2) {
                                this.players[i].x -= 3;
                            }
                        }
                    }
                }
                this.players[i].update(this.players.concat(this.enemies));
            }
            for (var i = 0; i < this.enemies.length; i++) {
                this.enemies[i].update(this.players);
            }
            for (var i = 0; i < this.items.length; i++) {
                this.items[i].update();
            }
            this.level.update();
        },

        render: function() {
            var gameDiv = document.getElementById('game');
            gameDiv.style.top = Math.max(0, (document.body.clientHeight - this.size.height) / 2);
            gameDiv.style.left = Math.max(0, (document.body.clientWidth - this.size.width) / 2);
            clientWidth = document.body.clientWidth;
            if (clientWidth < this.size.width) {
                window.scrollTo(Math.min(this.players[0].x + 75 - clientWidth / 2, this.size.width - clientWidth), this.size.width);
            }
            for (var i = 0; i < this.players.length; i++) {
                this.players[i].render();
            }
            for (var i = 0; i < this.enemies.length; i++) {
                this.enemies[i].render();
            }
            for (var i = 0; i < this.items.length; i++) {
                this.items[i].render();
            }
        },

        takeItem: function(name) {
            for (var i = 0; i < this.items; i++) {
                if (this.items[i].name == name) {
                    var item = this.items[i];
                    this.items.splice(i, 1);
                    return item;
                }
            }
        },

        changeLevel: function(level, newX, newY) {
            // remove all items
            for (var i = 0; i < this.items.length; i++) {
                var itemDiv = document.getElementById(this.items[i].name);
                itemDiv.parentNode.removeChild(itemDiv);
            }
            this.items = [];
            // remove all enemies
            for (var i = 0; i < this.enemies.length; i++) {
                var enemyDiv = document.getElementById(this.enemies[i].name);
                enemyDiv.parentNode.removeChild(enemyDiv);
            }
            this.enemies = [];
            // move all players
            for (var i = 0; i < this.players.length; i++) {
                var player = this.players[i];
                player.x = newX;
                player.y = newY;
            }
            // change level and let it render
            this.level = level;
        }
    };

    // Level defines background and masks (platforms, walls, etc.) which define where elements can move.
    var Level = function(background) {
        this.background = background;
        // colision rectangles for elements
        this.masks = [];
        // doors to other levels
        this.doors = [];

        // initialization
        this.masks = this.masks.concat(new Mask(0, 650, 1600, 660));
        this.masks = this.masks.concat(new Mask(-100, 0, -1, 700));
        this.masks = this.masks.concat(new Mask(1600, 0, 1700, 700));
    };
    Level.prototype = {
        update: function() {
        },
        render: function() {
            document.getElementById('game').style.backgroundImage = 'url(' + this.background + ')';
        }
    };

    // Masks are invisible collision rectangles which define when elements interact with Levels and other elements.
    var Mask = function(minX, maxX, minY, maxY) {
        this.minX = minX;
        this.maxX = maxX;
        this.minY = minY;
        this.maxY = maxY;
    };

    // Rectangle area which defines where player can switch the level to the one mapped to one configured by this door.
    var Door = function(mask, level) {
        this.mask = mask;
        this.level = level;
    };

    // Element is a class for items, laying weapons, effects like blood or explosion. It is also base class for players and enemies.
    var Element = function(name, x, y, image, mask) {
        this.name = name;
        this.x = x;
        this.y = y;
        this.image = image;
        this.animationFrame = 0;
        this.scaleX = 1;
        this.mask = mask;
        this.state = 'nothing';
        this.stateStart = new Date().getTime();

        // initialization
        var img = document.createElement('img');
        img.setAttribute('id', this.name + '-image');
        img.setAttribute('src', this.image);

        var div = document.createElement('div');
        div.setAttribute('id', this.name);
        div.setAttribute('class', 'element');
        div.style.left = this.x;
        div.style.top = this.y;
        div.appendChild(img);

        document.getElementById('game').appendChild(div);
    };
    Element.prototype = {
        update: function() {
        },
        render: function() {
        }
    };

    // TODO Add statHitSpeed, statWalkSpeed
    // Base class for Player and Enemy
    var Character = function(name, x, y, image, mask, statStrength, statRange) {
        // call super constructor
        Element.call(this, name, x, y, image, mask);

        /* Assign stats */
        this.statStrength = statStrength;   /* Defines how far the enemy is knocked back after a hit */
        this.statRange = statRange;         /* Defines range of the punches */
    };
    // TODO Caleb: not sure if this is correct way to inherit the prototype
    Character.prototype = new Element();
    Character.prototype.constructor = Character;
    // Checks if otherDres (Element) is within punching range of this Character
    Character.prototype.isInPunchRange = function(otherDres) {
        var thisDresPosition = this.x * this.scaleX;
        var otherDresPosition = otherDres.x * this.scaleX;
        return (thisDresPosition < otherDresPosition && thisDresPosition + this.statRange > otherDresPosition);
    };

    // NPC - simple walking motherfucker
    var Enemy = function(name, x, y, image, mask, statStrength, statRange) {
        // call super constructor
        Character.call(this, name, x, y, image, mask, statStrength, statRange);

        // initialization
        this.state = 'walk';
        document.getElementById(this.name).setAttribute('class', 'element dres');
    };
    Enemy.prototype = new Character();
    Enemy.prototype.constructor = Enemy;
    Enemy.prototype.update = function(players) {
        var hit = false;
        /* Detect if any players are in punch range */
        for (var i = 0; i < players.length; i++) {
            var otherDres = players[i];

            /* If the otherDres is within punch range, knock him 100 pixels back */
            if (this.isInPunchRange(otherDres)) {
                otherDres.x += this.statStrength * this.scaleX;
                /* Turn the otherDres back after he has been hit. */
                otherDres.scaleX = this.scaleX;
                hit = true;
            }
        }

        /* If no players were hit, just keep on walking */
        if (!hit) {
            /* Move 3px in direction indicated by scaleX */
            this.x += 3 * this.scaleX;
            this.animationFrame++;
            /* When character is reaching the end of the game screen, change its move direction */
            // TODO change to colision with Level masks
            if ((this.x < 200 && this.scaleX < 0) || (this.x > 1250 && this.scaleX > 0))
                this.scaleX *= -1;
        }
    };
    Enemy.prototype.render = function() {
        var enemy = document.getElementById(this.name);
        var enemyimage = document.getElementById(this.name + '-image');
        enemy.style.left = this.x + 'px';
        enemy.style.top = this.y + 'px';

        enemy.style.transform = 'scaleX(' + this.scaleX + ')';
        enemyimage.style.marginLeft = -((Math.floor(this.animationFrame / 5) % 8) * 150) + 'px';
    };

    // Player is strearable element with health and items.
    var Player = function(name, x, y, image, mask, statStrength, statRange, keys) {
        Character.call(this, name, x, y, image, mask, statStrength, statRange);
        this.keys = keys;
        this.jump = 0;
        this.jumpStart = 0;

        // initialization
        this.state = 'walk';
        document.getElementById(this.name).setAttribute('class', 'element dres');
    };
    Player.prototype = new Character();
    Player.prototype.constructor = Player;
    Player.prototype.update = function(bodies) {
        var punchSound;
        if (this.keys.isKeyDown(this.keys.punch)) {
            if (this.state != "punch") {
                this.animationFrame = 0;
                this.state = "punch";
                /* This is actually a whoosh sound and will always be played, despite if punch hits or misses.
                     * TODO: change name from punchSoundMiss to punchSoundWhoosh or something. */
                punchSound = document.getElementById("punchSoundMiss" + (punchIter++ % 2));

            } else if (this.animationFrame < 15) {
                this.animationFrame += 5;
                /* Punch other character if it's within the punch range. We only punch when the arm is stretched
                     * (this.anim === 10 ?) */
                var hit = false;
                if (this.animationFrame === 10)
                /* Check if any other character is within punch range */
                    for (var i = 0; i < bodies.length; i++) {
                        var otherDres = bodies[i];
                        if (this === otherDres)
                            continue;

                        /* If the otherDres is within punch range, knock him 100 pixels back */
                        if (this.isInPunchRange(otherDres)) {
                            otherDres.x += this.statStrength * this.scaleX;
                            /* Turn the otherDres back after he has been hit. */
                            otherDres.scaleX = this.scaleX;
                            hit = true;
                        }
                    }
                if (hit === true)
                    punchSound = document.getElementById("punchSoundHit");
            }
            if (punchSound) {
                punchSound.load();
                punchSound.play();
            }
        } else if (this.keys.isKeyDown(this.keys.left) && this.x > 0) {
            // TODO change those x limits to colision model based on Level masks.
            this.state = "walk";
            this.x -= 3;
            this.animationFrame++;
            this.scaleX = -1;
        } else if (this.keys.isKeyDown(this.keys.right) && this.x < 1450) {
            this.state = "walk";
            this.x += 3;
            this.animationFrame++;
            this.scaleX = 1;
        } else {
            this.state = "walk";
            this.animationFrame = 0;
        }

        if (this.keys.isKeyDown(this.keys.jump) && this.jump === 0) {
            this.jump = 1;
            this.jumpStart = new Date().getTime();
            sound = document.getElementById(this.name + "JumpSound");
            if (sound) {
                sound.load();
                sound.play();
            }
        }
        if (this.jump < 0) {
            this.jump = 0;
        } else if (this.jump > 0) {
            var x = new Date().getTime() - this.jumpStart;
            if (x > 0) {
                this.jump = x * x / -625 + 4 * x / 5;
            }
        }
    };
    Player.prototype.render = function() {
        var rudydres = document.getElementById(this.name);
        var rudydresimage = document.getElementById(this.name + '-image');
        rudydres.style.left = this.x + 'px';
        rudydres.style.top = this.y - this.jump + 'px';

        rudydres.style.transform = 'scaleX(' + this.scaleX + ')';
        rudydresimage.style.marginLeft = -((Math.floor(this.animationFrame / 5) % 8) * 150) + 'px';
        if (this.state === "walk") {
            rudydresimage.style.marginTop = null;
        } else if (this.state === "punch") {
            rudydresimage.style.marginTop = '-150px';
        }
    };

    // Keys class maps keys ascii codes into action names like left, right, punch, etc.
    var Keys = function(left, right, jump, punch, kick, head, itemSwitch) {
        this.left = left;
        this.right = right;
        this.jump = jump;
        this.punch = punch;
        this.kick = kick;
        this.head = head;
        this.itemSwitch = itemSwitch;

        // initialization
        var keyState = {};
        window.addEventListener('keydown', function(e) { keyState[e.keyCode] = true; });
        window.addEventListener('keyup', function(e) { keyState[e.keyCode] = false; });
        this.isKeyDown = function(key) {
            return keyState[key] === true;
        };
    };

    // run game when ready
    window.addEventListener('load', function() { new Game(); });
})();<|endoftext|>


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

couchTests.reader_acl = function(debug) {
  // this tests read access control

  var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
  var secretDb = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
  function testFun() {
    try {
      usersDb.deleteDb();
      usersDb.createDb();
      secretDb.deleteDb();
      secretDb.createDb();

      // create a user with top-secret-clearance
      var jchrisUserDoc = CouchDB.prepareUserDoc({
        name: "jchris@apache.org",
        roles : ["top-secret"]
      }, "funnybone");
      T(usersDb.save(jchrisUserDoc).ok);

      T(CouchDB.session().userCtx.name == null);

      // set secret db to be read controlled
      T(secretDb.save({_id:"baz",foo:"bar"}).ok);
      T(secretDb.open("baz").foo == "bar");

      T(secretDb.setSecObj({
        "readers" : {
          roles : ["super-secret-club"],
          names : ["joe","barb"]
        }
      }).ok);
      // can't read it as jchris
      T(CouchDB.login("jchris@apache.org", "funnybone").ok);
      T(CouchDB.session().userCtx.name == "jchris@apache.org");

      try {
        secretDb.open("baz");
        T(false && "can't open a doc from a secret db") ;
      } catch(e) {
        T(true)
      }

      CouchDB.logout();

      // make anyone with the top-secret role an admin
      // db admins are automatically readers
      T(secretDb.setSecObj({
        "admins" : {
          roles : ["top-secret"],
          names : []
        },
        "readers" : {
          roles : ["super-secret-club"],
          names : ["joe","barb"]
        }
      }).ok);

      T(CouchDB.login("jchris@apache.org", "funnybone").ok);

      T(secretDb.open("baz").foo == "bar");

      CouchDB.logout();
      T(CouchDB.session().userCtx.roles.indexOf("_admin") != -1);

      // admin now adds the top-secret role to the db's readers
      // and removes db-admins
      T(secretDb.setSecObj({
        "admins" : {
          roles : [],
          names : []
        },
        "readers" : {
          roles : ["super-secret-club", "top-secret"],
          names : ["joe","barb"]
        }
      }).ok);

      // server _admin can always read
      T(secretDb.open("baz").foo == "bar");

      // and run temp views
      TEquals(secretDb.query(function(doc) {
        emit(null, null)
      }).total_rows, 1);

      T(secretDb.save({
        "_id" : "_design/foo",
        views : {
          bar : {
            map : "function(doc){emit(null, null)}"
          }
        }
      }).ok)

      // now top-secret users can read too
      T(CouchDB.login("jchris@apache.org", "funnybone").ok);
      T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1);
      T(secretDb.open("baz").foo == "bar");
      // readers can query stored views
      T(secretDb.view("foo/bar").total_rows == 1);

      CouchDB.logout();

      // can't set non string reader names or roles
      try {
        secretDb.setSecObj({
          "readers" : {
            roles : ["super-secret-club", {"top-secret":"awesome"}],
            names : ["joe","barb"]
          }
        })
        T(false && "only string roles");
      } catch (e) {}

      try {
        secretDb.setSecObj({
          "readers" : {
            roles : ["super-secret-club", {"top-secret":"awesome"}],
            names : ["joe",22]
          }
        });
        T(false && "only string names");
      } catch (e) {}

      try {
        secretDb.setSecObj({
          "readers" : {
            roles : ["super-secret-club", {"top-secret":"awesome"}],
            names : "joe"
          }
        });
        T(false && "only lists of names");
      } catch (e) {}
    } finally {
      CouchDB.logout();
    }
  }

  run_on_modified_server(
    [{section: "httpd",
      key: "authentication_handlers",
      value: "{couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}"},
     {section: "couch_httpd_auth",
      key: "authentication_db", value: "test_suite_users"}],
    testFun
  );
}
<commit_msg>ensure full commit when the security object is updated<commit_after>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License.  You may obtain a copy
// of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
// License for the specific language governing permissions and limitations under
// the License.

couchTests.reader_acl = function(debug) {
  // this tests read access control

  var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
  var secretDb = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
  function testFun() {
    try {
      usersDb.deleteDb();
      usersDb.createDb();
      secretDb.deleteDb();
      secretDb.createDb();

      // create a user with top-secret-clearance
      var jchrisUserDoc = CouchDB.prepareUserDoc({
        name: "jchris@apache.org",
        roles : ["top-secret"]
      }, "funnybone");
      T(usersDb.save(jchrisUserDoc).ok);

      T(CouchDB.session().userCtx.name == null);

      // set secret db to be read controlled
      T(secretDb.save({_id:"baz",foo:"bar"}).ok);
      T(secretDb.open("baz").foo == "bar");

      T(secretDb.setSecObj({
        "readers" : {
          roles : ["super-secret-club"],
          names : ["joe","barb"]
        }
      }).ok);

      usersDb.ensureFullCommit();
      // security changes will always commit synchronously
      restartServer();

      // can't read it as jchris
      T(CouchDB.login("jchris@apache.org", "funnybone").ok);
      T(CouchDB.session().userCtx.name == "jchris@apache.org");

      try {
        secretDb.open("baz");
        T(false && "can't open a doc from a secret db") ;
      } catch(e) {
        T(true)
      }

      CouchDB.logout();

      // make anyone with the top-secret role an admin
      // db admins are automatically readers
      T(secretDb.setSecObj({
        "admins" : {
          roles : ["top-secret"],
          names : []
        },
        "readers" : {
          roles : ["super-secret-club"],
          names : ["joe","barb"]
        }
      }).ok);

      T(CouchDB.login("jchris@apache.org", "funnybone").ok);

      T(secretDb.open("baz").foo == "bar");

      CouchDB.logout();
      T(CouchDB.session().userCtx.roles.indexOf("_admin") != -1);

      // admin now adds the top-secret role to the db's readers
      // and removes db-admins
      T(secretDb.setSecObj({
        "admins" : {
          roles : [],
          names : []
        },
        "readers" : {
          roles : ["super-secret-club", "top-secret"],
          names : ["joe","barb"]
        }
      }).ok);

      // server _admin can always read
      T(secretDb.open("baz").foo == "bar");

      // and run temp views
      TEquals(secretDb.query(function(doc) {
        emit(null, null)
      }).total_rows, 1);

      T(secretDb.save({
        "_id" : "_design/foo",
        views : {
          bar : {
            map : "function(doc){emit(null, null)}"
          }
        }
      }).ok)

      // now top-secret users can read too
      T(CouchDB.login("jchris@apache.org", "funnybone").ok);
      T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1);
      T(secretDb.open("baz").foo == "bar");
      // readers can query stored views
      T(secretDb.view("foo/bar").total_rows == 1);

      CouchDB.logout();

      // can't set non string reader names or roles
      try {
        secretDb.setSecObj({
          "readers" : {
            roles : ["super-secret-club", {"top-secret":"awesome"}],
            names : ["joe","barb"]
          }
        })
        T(false && "only string roles");
      } catch (e) {}

      try {
        secretDb.setSecObj({
          "readers" : {
            roles : ["super-secret-club", {"top-secret":"awesome"}],
            names : ["joe",22]
          }
        });
        T(false && "only string names");
      } catch (e) {}

      try {
        secretDb.setSecObj({
          "readers" : {
            roles : ["super-secret-club", {"top-secret":"awesome"}],
            names : "joe"
          }
        });
        T(false && "only lists of names");
      } catch (e) {}
    } finally {
      CouchDB.logout();
    }
  }

  run_on_modified_server(
    [{section: "httpd",
      key: "authentication_handlers",
      value: "{couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}"},
     {section: "couch_httpd_auth",
      key: "authentication_db", value: "test_suite_users"}],
    testFun
  );
}
<|endoftext|>


<commit_before>// from http://bl.ocks.org/rkirsling/5001347#app.js

(function(){
  // set up SVG for D3
  var width  = 960,
      height = 500,
      colors = d3.scale.category10();

  var svg = d3.select('.graph')
    .append('svg')
    .attr('width', width)
    .attr('height', height);

  // set up initial nodes and links
  //  - nodes are known by 'id', not by index in array.
  //  - reflexive edges are indicated on the node (as a bold black circle).
  //  - links are always source < target; edge directions are set by 'left' and 'right'.
  var nodes = gon.topics,
    lastNodeId = nodes[nodes.length - 1].id,
    links = [
      {source: nodes[0], target: nodes[1], left: false, right: true },
      {source: nodes[1], target: nodes[2], left: false, right: true }
    ];

  // just to match what was there before
  nodes.forEach(function(n){ n.reflexive = true; });

  // init D3 force layout
  var force = d3.layout.force()
      .nodes(nodes)
      .links(links)
      .size([width, height])
      .linkDistance(150)
      .charge(-500)
      .on('tick', tick)

  // define arrow markers for graph links
  svg.append('svg:defs').append('svg:marker')
      .attr('id', 'end-arrow')
      .attr('viewBox', '0 -5 10 10')
      .attr('refX', 6)
      .attr('markerWidth', 3)
      .attr('markerHeight', 3)
      .attr('orient', 'auto')
    .append('svg:path')
      .attr('d', 'M0,-5L10,0L0,5')
      .attr('fill', '#000');

  svg.append('svg:defs').append('svg:marker')
      .attr('id', 'start-arrow')
      .attr('viewBox', '0 -5 10 10')
      .attr('refX', 4)
      .attr('markerWidth', 3)
      .attr('markerHeight', 3)
      .attr('orient', 'auto')
    .append('svg:path')
      .attr('d', 'M10,-5L0,0L10,5')
      .attr('fill', '#000');

  // line displayed when dragging new nodes
  var drag_line = svg.append('svg:path')
    .attr('class', 'link dragline hidden')
    .attr('d', 'M0,0L0,0');

  // handles to link and node element groups
  var path = svg.append('svg:g').selectAll('path'),
      circle = svg.append('svg:g').selectAll('g');

  // mouse event vars
  var selected_node = null,
      selected_link = null,
      mousedown_link = null,
      mousedown_node = null,
      mouseup_node = null;

  function resetMouseVars() {
    mousedown_node = null;
    mouseup_node = null;
    mousedown_link = null;
  }

  // update force layout (called automatically each iteration)
  function tick() {
    // draw directed edges with proper padding from node centers
    path.attr('d', function(d) {
      var deltaX = d.target.x - d.source.x,
          deltaY = d.target.y - d.source.y,
          dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
          normX = deltaX / dist,
          normY = deltaY / dist,
          sourcePadding = d.left ? 17 : 12,
          targetPadding = d.right ? 17 : 12,
          sourceX = d.source.x + (sourcePadding * normX),
          sourceY = d.source.y + (sourcePadding * normY),
          targetX = d.target.x - (targetPadding * normX),
          targetY = d.target.y - (targetPadding * normY);
      return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
    });

    circle.attr('transform', function(d) {
      return 'translate(' + d.x + ',' + d.y + ')';
    });
  }

  // update graph (called when needed)
  function restart() {
    // path (link) group
    path = path.data(links);

    // update existing links
    path.classed('selected', function(d) { return d === selected_link; })
      .style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
      .style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; });

    // add new links
    path.enter().append('svg:path')
      .attr('class', 'link')
      .classed('selected', function(d) { return d === selected_link; })
      .style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
      .style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; })
      .on('mousedown', function(d) {
        if(d3.event.ctrlKey) return;

        // select link
        mousedown_link = d;
        if(mousedown_link === selected_link) selected_link = null;
        else selected_link = mousedown_link;
        selected_node = null;
        restart();
      });

    // remove old links
    path.exit().remove();

    // circle (node) group
    // NB: the function arg is crucial here! nodes are known by id, not by index!
    circle = circle.data(nodes, function(d) { return d.id; });

    // update existing nodes (reflexive & selected visual states)
    circle.selectAll('circle')
      .style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
      .classed('reflexive', function(d) { return d.reflexive; });

    // add new nodes
    var g = circle.enter().append('svg:g');

    g.append('svg:circle')
      .attr('class', 'node')
      .attr('r', 12)
      .style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
      .style('stroke', function(d) { return d3.rgb(colors(d.id)).darker().toString(); })
      .classed('reflexive', function(d) { return d.reflexive; })
      .on('mouseover', function(d) {
        if(!mousedown_node || d === mousedown_node) return;
        // enlarge target node
        d3.select(this).attr('transform', 'scale(1.1)');
      })
      .on('mouseout', function(d) {
        if(!mousedown_node || d === mousedown_node) return;
        // unenlarge target node
        d3.select(this).attr('transform', '');
      })
      .on('mousedown', function(d) {
        if(d3.event.ctrlKey) return;

        // select node
        mousedown_node = d;
        if(mousedown_node === selected_node) selected_node = null;
        else selected_node = mousedown_node;
        selected_link = null;

        // reposition drag line
        drag_line
          .style('marker-end', 'url(#end-arrow)')
          .classed('hidden', false)
          .attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + mousedown_node.x + ',' + mousedown_node.y);

        restart();
      })
      .on('mouseup', function(d) {
        if(!mousedown_node) return;

        // needed by FF
        drag_line
          .classed('hidden', true)
          .style('marker-end', '');

        // check for drag-to-self
        mouseup_node = d;
        if(mouseup_node === mousedown_node) { resetMouseVars(); return; }

        // unenlarge target node
        d3.select(this).attr('transform', '');

        // add link to graph (update if exists)
        // NB: links are strictly source < target; arrows separately specified by booleans
        var source, target, direction;
        if(mousedown_node.id < mouseup_node.id) {
          source = mousedown_node;
          target = mouseup_node;
          direction = 'right';
        } else {
          source = mouseup_node;
          target = mousedown_node;
          direction = 'left';
        }

        var link;
        link = links.filter(function(l) {
          return (l.source === source && l.target === target);
        })[0];

        if(link) {
          link[direction] = true;
        } else {
          link = {source: source, target: target, left: false, right: false};
          link[direction] = true;
          links.push(link);
        }

        // select new link
        selected_link = link;
        selected_node = null;
        restart();
      });

    // show node IDs
    g.append('svg:text')
        .attr('x', 0)
        .attr('y', 4)
        .attr('class', 'id')
        .text(function(d) { return d.title; });

    // remove old nodes
    circle.exit().remove();

    // set the graph in motion
    force.start();
  }

  function mousedown() {
    // prevent I-bar on drag
    //d3.event.preventDefault();

    // because :active only works in WebKit?
    svg.classed('active', true);

    if(d3.event.ctrlKey || mousedown_node || mousedown_link) return;

    // insert new node at point
    var point = d3.mouse(this),
        node = {id: ++lastNodeId, reflexive: false};
    node.x = point[0];
    node.y = point[1];
    nodes.push(node);

    restart();
  }

  function mousemove() {
    if(!mousedown_node) return;

    // update drag line
    drag_line.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + d3.mouse(this)[0] + ',' + d3.mouse(this)[1]);

    restart();
  }

  function mouseup() {
    if(mousedown_node) {
      // hide drag line
      drag_line
        .classed('hidden', true)
        .style('marker-end', '');
    }

    // because :active only works in WebKit?
    svg.classed('active', false);

    // clear mouse event vars
    resetMouseVars();
  }

  function spliceLinksForNode(node) {
    var toSplice = links.filter(function(l) {
      return (l.source === node || l.target === node);
    });
    toSplice.map(function(l) {
      links.splice(links.indexOf(l), 1);
    });
  }

  // only respond once per keydown
  var lastKeyDown = -1;

  function keydown() {
    d3.event.preventDefault();

    if(lastKeyDown !== -1) return;
    lastKeyDown = d3.event.keyCode;

    // ctrl
    if(d3.event.keyCode === 17) {
      circle.call(force.drag);
      svg.classed('ctrl', true);
    }

    if(!selected_node && !selected_link) return;
    switch(d3.event.keyCode) {
      case 8: // backspace
      case 46: // delete
        if(selected_node) {
          nodes.splice(nodes.indexOf(selected_node), 1);
          spliceLinksForNode(selected_node);
        } else if(selected_link) {
          links.splice(links.indexOf(selected_link), 1);
        }
        selected_link = null;
        selected_node = null;
        restart();
        break;
      case 66: // B
        if(selected_link) {
          // set link direction to both left and right
          selected_link.left = true;
          selected_link.right = true;
        }
        restart();
        break;
      case 76: // L
        if(selected_link) {
          // set link direction to left only
          selected_link.left = true;
          selected_link.right = false;
        }
        restart();
        break;
      case 82: // R
        if(selected_node) {
          // toggle node reflexivity
          selected_node.reflexive = !selected_node.reflexive;
        } else if(selected_link) {
          // set link direction to right only
          selected_link.left = false;
          selected_link.right = true;
        }
        restart();
        break;
    }
  }

  function keyup() {
    lastKeyDown = -1;

    // ctrl
    if(d3.event.keyCode === 17) {
      circle
        .on('mousedown.drag', null)
        .on('touchstart.drag', null);
      svg.classed('ctrl', false);
    }
  }

  // app starts here
  svg.on('mousedown', mousedown)
    .on('mousemove', mousemove)
    .on('mouseup', mouseup);
  d3.select(window)
    .on('keydown', keydown)
    .on('keyup', keyup);
  restart();
})();
<commit_msg>populate graph with real relationships<commit_after>// from http://bl.ocks.org/rkirsling/5001347#app.js

(function(){
  // set up SVG for D3
  var width  = 960,
      height = 500,
      colors = d3.scale.category10();

  var svg = d3.select('.graph')
    .append('svg')
    .attr('width', width)
    .attr('height', height);

  // set up initial nodes and links
  //  - nodes are known by 'id', not by index in array.
  //  - reflexive edges are indicated on the node (as a bold black circle).
  //  - links are always source < target; edge directions are set by 'left' and 'right'.
  var nodesById = {};
  var nodes = gon.topics.map(function(topic){
    var node = $.extend({}, topic, {
      reflexive: true
    });
    nodesById[node.id] = node;
    return node;
  });
  var lastNodeId = nodes[nodes.length - 1].id;
  var links = gon.relationships.map(function(rel){
    return $.extend({}, rel, {
      source: nodesById[rel.parent_id],
      target: nodesById[rel.child_id],
      left: false,
      right: true
    });
  });

  // init D3 force layout
  var force = d3.layout.force()
      .nodes(nodes)
      .links(links)
      .size([width, height])
      .linkDistance(150)
      .charge(-500)
      .on('tick', tick)

  // define arrow markers for graph links
  svg.append('svg:defs').append('svg:marker')
      .attr('id', 'end-arrow')
      .attr('viewBox', '0 -5 10 10')
      .attr('refX', 6)
      .attr('markerWidth', 3)
      .attr('markerHeight', 3)
      .attr('orient', 'auto')
    .append('svg:path')
      .attr('d', 'M0,-5L10,0L0,5')
      .attr('fill', '#000');

  svg.append('svg:defs').append('svg:marker')
      .attr('id', 'start-arrow')
      .attr('viewBox', '0 -5 10 10')
      .attr('refX', 4)
      .attr('markerWidth', 3)
      .attr('markerHeight', 3)
      .attr('orient', 'auto')
    .append('svg:path')
      .attr('d', 'M10,-5L0,0L10,5')
      .attr('fill', '#000');

  // line displayed when dragging new nodes
  var drag_line = svg.append('svg:path')
    .attr('class', 'link dragline hidden')
    .attr('d', 'M0,0L0,0');

  // handles to link and node element groups
  var path = svg.append('svg:g').selectAll('path'),
      circle = svg.append('svg:g').selectAll('g');

  // mouse event vars
  var selected_node = null,
      selected_link = null,
      mousedown_link = null,
      mousedown_node = null,
      mouseup_node = null;

  function resetMouseVars() {
    mousedown_node = null;
    mouseup_node = null;
    mousedown_link = null;
  }

  // update force layout (called automatically each iteration)
  function tick() {
    // draw directed edges with proper padding from node centers
    path.attr('d', function(d) {
      var deltaX = d.target.x - d.source.x,
          deltaY = d.target.y - d.source.y,
          dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
          normX = deltaX / dist,
          normY = deltaY / dist,
          sourcePadding = d.left ? 17 : 12,
          targetPadding = d.right ? 17 : 12,
          sourceX = d.source.x + (sourcePadding * normX),
          sourceY = d.source.y + (sourcePadding * normY),
          targetX = d.target.x - (targetPadding * normX),
          targetY = d.target.y - (targetPadding * normY);
      return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
    });

    circle.attr('transform', function(d) {
      return 'translate(' + d.x + ',' + d.y + ')';
    });
  }

  // update graph (called when needed)
  function restart() {
    // path (link) group
    path = path.data(links);

    // update existing links
    path.classed('selected', function(d) { return d === selected_link; })
      .style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
      .style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; });

    // add new links
    path.enter().append('svg:path')
      .attr('class', 'link')
      .classed('selected', function(d) { return d === selected_link; })
      .style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
      .style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; })
      .on('mousedown', function(d) {
        if(d3.event.ctrlKey) return;

        // select link
        mousedown_link = d;
        if(mousedown_link === selected_link) selected_link = null;
        else selected_link = mousedown_link;
        selected_node = null;
        restart();
      });

    // remove old links
    path.exit().remove();

    // circle (node) group
    // NB: the function arg is crucial here! nodes are known by id, not by index!
    circle = circle.data(nodes, function(d) { return d.id; });

    // update existing nodes (reflexive & selected visual states)
    circle.selectAll('circle')
      .style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
      .classed('reflexive', function(d) { return d.reflexive; });

    // add new nodes
    var g = circle.enter().append('svg:g');

    g.append('svg:circle')
      .attr('class', 'node')
      .attr('r', 12)
      .style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
      .style('stroke', function(d) { return d3.rgb(colors(d.id)).darker().toString(); })
      .classed('reflexive', function(d) { return d.reflexive; })
      .on('mouseover', function(d) {
        if(!mousedown_node || d === mousedown_node) return;
        // enlarge target node
        d3.select(this).attr('transform', 'scale(1.1)');
      })
      .on('mouseout', function(d) {
        if(!mousedown_node || d === mousedown_node) return;
        // unenlarge target node
        d3.select(this).attr('transform', '');
      })
      .on('mousedown', function(d) {
        if(d3.event.ctrlKey) return;

        // select node
        mousedown_node = d;
        if(mousedown_node === selected_node) selected_node = null;
        else selected_node = mousedown_node;
        selected_link = null;

        // reposition drag line
        drag_line
          .style('marker-end', 'url(#end-arrow)')
          .classed('hidden', false)
          .attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + mousedown_node.x + ',' + mousedown_node.y);

        restart();
      })
      .on('mouseup', function(d) {
        if(!mousedown_node) return;

        // needed by FF
        drag_line
          .classed('hidden', true)
          .style('marker-end', '');

        // check for drag-to-self
        mouseup_node = d;
        if(mouseup_node === mousedown_node) { resetMouseVars(); return; }

        // unenlarge target node
        d3.select(this).attr('transform', '');

        // add link to graph (update if exists)
        // NB: links are strictly source < target; arrows separately specified by booleans
        var source, target, direction;
        if(mousedown_node.id < mouseup_node.id) {
          source = mousedown_node;
          target = mouseup_node;
          direction = 'right';
        } else {
          source = mouseup_node;
          target = mousedown_node;
          direction = 'left';
        }

        var link;
        link = links.filter(function(l) {
          return (l.source === source && l.target === target);
        })[0];

        if(link) {
          link[direction] = true;
        } else {
          link = {source: source, target: target, left: false, right: false};
          link[direction] = true;
          links.push(link);
        }

        // select new link
        selected_link = link;
        selected_node = null;
        restart();
      });

    // show node IDs
    g.append('svg:text')
        .attr('x', 0)
        .attr('y', 4)
        .attr('class', 'id')
        .text(function(d) { return d.title; });

    // remove old nodes
    circle.exit().remove();

    // set the graph in motion
    force.start();
  }

  function mousedown() {
    // prevent I-bar on drag
    //d3.event.preventDefault();

    // because :active only works in WebKit?
    svg.classed('active', true);

    if(d3.event.ctrlKey || mousedown_node || mousedown_link) return;

    // insert new node at point
    var point = d3.mouse(this),
        node = {id: ++lastNodeId, reflexive: false};
    node.x = point[0];
    node.y = point[1];
    nodes.push(node);

    restart();
  }

  function mousemove() {
    if(!mousedown_node) return;

    // update drag line
    drag_line.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + d3.mouse(this)[0] + ',' + d3.mouse(this)[1]);

    restart();
  }

  function mouseup() {
    if(mousedown_node) {
      // hide drag line
      drag_line
        .classed('hidden', true)
        .style('marker-end', '');
    }

    // because :active only works in WebKit?
    svg.classed('active', false);

    // clear mouse event vars
    resetMouseVars();
  }

  function spliceLinksForNode(node) {
    var toSplice = links.filter(function(l) {
      return (l.source === node || l.target === node);
    });
    toSplice.map(function(l) {
      links.splice(links.indexOf(l), 1);
    });
  }

  // only respond once per keydown
  var lastKeyDown = -1;

  function keydown() {
    d3.event.preventDefault();

    if(lastKeyDown !== -1) return;
    lastKeyDown = d3.event.keyCode;

    // ctrl
    if(d3.event.keyCode === 17) {
      circle.call(force.drag);
      svg.classed('ctrl', true);
    }

    if(!selected_node && !selected_link) return;
    switch(d3.event.keyCode) {
      case 8: // backspace
      case 46: // delete
        if(selected_node) {
          nodes.splice(nodes.indexOf(selected_node), 1);
          spliceLinksForNode(selected_node);
        } else if(selected_link) {
          links.splice(links.indexOf(selected_link), 1);
        }
        selected_link = null;
        selected_node = null;
        restart();
        break;
      case 66: // B
        if(selected_link) {
          // set link direction to both left and right
          selected_link.left = true;
          selected_link.right = true;
        }
        restart();
        break;
      case 76: // L
        if(selected_link) {
          // set link direction to left only
          selected_link.left = true;
          selected_link.right = false;
        }
        restart();
        break;
      case 82: // R
        if(selected_node) {
          // toggle node reflexivity
          selected_node.reflexive = !selected_node.reflexive;
        } else if(selected_link) {
          // set link direction to right only
          selected_link.left = false;
          selected_link.right = true;
        }
        restart();
        break;
    }
  }

  function keyup() {
    lastKeyDown = -1;

    // ctrl
    if(d3.event.keyCode === 17) {
      circle
        .on('mousedown.drag', null)
        .on('touchstart.drag', null);
      svg.classed('ctrl', false);
    }
  }

  // app starts here
  svg.on('mousedown', mousedown)
    .on('mousemove', mousemove)
    .on('mouseup', mouseup);
  d3.select(window)
    .on('keydown', keydown)
    .on('keyup', keyup);
  restart();
})();
<|endoftext|>


<commit_before>SL = sugarLab;

var SCREEN_SIZE = new SL.Vec2(800, 576);

function logPlay() {
    _gaq.push(['_trackEvent', 'Button', 'Play']);
}

function start() {
    var startLoad = new Date;

    window.app = new SL.Game({canvas: document.getElementById('GameCanvas')});
    spritesheet = new PIXI.SpriteSheetLoader('res/atlas.json');

    spritesheet.load();

    app.assetCollection = new SL.AssetCollection('res/assets.json', app, function () {
        _gaq.push(['_trackEvent', 'Game', 'Load', '', (new Date - startLoad) / 1000]);
        $('.canvas-container').append(domjs.build(templates.modal));

        var loadingScene = new SL.Scene('loading', [], function () {
        }, app);

        loadingScene.addEntity(new SL.Loader({
            assetCollection: app.assetCollection,
            screenSize: SCREEN_SIZE,
            loadCallback: function () { app.transitionScene('menu'); },
            barColor: 'blue',
            textColor: 'green'
        }));

        var menuScene = new SL.Scene('menu', [], function () {
            var modal = $('.modal');

            modal.append(domjs.build(templates.menu));

            $('.menu .button').on('click', function () {
                modal.empty();
                modal.off();
                app.transitionScene($(this).attr('id'));
            });

            var curMOUSE;
            modal.mousemove(function(){
                curMOUSE = new SL.Vec2(event.clientX - this.offsetLeft, event.clientY - this.offsetTop);
            });

            var title = new PIXI.Sprite(app.assetCollection.getTexture('title'));
            app.currentScene.addEntity({
                sprite: title,
                update: function () {}
            });
            title.anchor = {x: 0.5, y: 0.5};
            title.position = new SL.Vec2(389, 140);
            var eyes = new PIXI.Sprite(app.assetCollection.getTexture('titleEyes')),
                eyeBase = new SL.Vec2(389, 140);

            eyes.anchor = {x: 0.5, y: 0.5};
            eyes.position = eyeBase;

            var idleClock;
            var valX, valY;
            var preValX, preValY;

            app.currentScene.addEntity({
                sprite: eyes,
                update: function () {
                    if (curMOUSE) {
                        valX = curMOUSE.x;
                        valY = curMOUSE.y;
                        if(valX != preValX){
                            var direction = title.position.angleBetween(curMOUSE);
                            eyes.position = eyeBase.getTranslatedAlongRotation(-3, direction);
                            idleClock=0;
                        } else {
                            if(Math.floor(idleClock) > 1){
                                eyes.position = eyeBase;
                            } else{
                                idleClock += 1/60;
                            }
                        }
                        preValX = valX;
                        preValY = valY;
                    }
                }
            });
            MapGeneration(512);
        }, app);

        var gameScene = new SL.Scene('game', [], function () {
            var modal = $('.modal');
            modal.empty();
            modal.off();
            modal.hide();

            testSprite = new PIXI.Sprite.fromFrame('tree.PNG');

            app.currentScene.addEntity({
                type: 'background',
                sprite: new PIXI.Sprite(app.assetCollection.getTexture('background')),
                update: function () {
                }
            });
            AnimationManager();
            MapGeneration(2048);
        }, app);

        app.addScene(loadingScene);
        app.addScene(menuScene);
        app.addScene(gameScene);
        app.transitionScene('loading');

        app.start();
    });
}

function MapGeneration(m) {
    var worldSize = m;
    var bgIndex = new PIXI.DisplayObjectContainer();

    var mapContainer = new PIXI.DisplayObjectContainer();
    var texture = new PIXI.RenderTexture();
    texture.render(mapContainer);

    function onloadGenerate(x,oX,oY){
        var label = x.split('.');
        var methodLabel = 'load'+label[0];
        methodLabel = new PIXI.Sprite.fromFrame(x);
        methodLabel.scale = {x: 2, y: 2};
        app.currentScene.addEntity({
          type: label[0],
          sprite: methodLabel,
          update: function () {}
        });
        // var assign obj and add it to the scene and //
        methodLabel.position = new SL.Vec2(oX, oY);
    }
//DRAW//
//INVOKE WHILE < World Map Size//
    var jsonObj;
    xhr = new XMLHttpRequest();
    xhr.onload = function () {
        jsonObj = JSON.parse(this.responseText);
        var keys = [];
        var tileKeys = [];
        var oX=0;
        var oY=0;
        for(var key in jsonObj.frames){
          keys.push(key);
        }
        for(var i=0;i<keys.length;i++){
            if(keys[i] == 'alert.PNG' || keys[i] == 'clickGround1.PNG' || keys[i] == 'clickGround2.PNG' || keys[i] == 'clickGround3.PNG' || keys[i] == 'puppyWalk1.PNG' || keys[i] == 'puppyWalk2.PNG' || keys[i] == 'puppyWalk3.PNG'){
            }else{
                tileKeys.push(keys[i]);
            }
        }
        var counterY = 0;
        var counterX = 0;
        var TileTrackerX = 0;
        while(!((512/64)%8) && oY < 512){
            while(!((512/64)%8) && oX < 512){
                var selector = Math.floor(Math.random() * tileKeys.length);
                bgIndex.addChild(tileKeys[selector]);

                onloadGenerate(tileKeys[selector],oX,oY);
                if(tileKeys[selector] == 'tree.PNG' || tileKeys[selector] == 'dirt.PNG'){
                    oX += 64;
                    TileTrackerX += 128;
                } else {
                    for(var i=0;i<1;i++){
                        var r = Math.floor(Math.random() * tileKeys.length);
                        var yO = oY;
                        while (tileKeys[r] == 'tree.PNG' || tileKeys[r] == 'dirt.PNG') {
                            r = Math.floor(Math.random() * tileKeys.length);
                        }
                        if(tileKeys[r] == 'tree.PNG' || tileKeys[r] == 'dirt.PNG'){
                        } else {
                            yO = oY + 64;
                            bgIndex.addChild(tileKeys[r]);
                            onloadGenerate(tileKeys[r],oX,yO);
                            yO = oY;
                        }
                        TileTrackerX += 64;
                        counterX += 1;
                    }
                    oX = oX;
                }
                if(TileTrackerX>512){
                    console.log('Encounterd incompatible set Size @ Row: '+(counterY+1) +', '+TileTrackerX);
                }
                oX+=64;
            }
            counterX = 0;
            TileTrackerX = 0;
            counterY += 1;
            oY+=128;
            oX=0;
        }
    };
    xhr.open('get', 'res/atlas.json', true);
    xhr.send();
}

function AnimationManager() {
    var indexManagement = new PIXI.DisplayObjectContainer();
    function onloadGenerate(x,oX,oY){
        var label = x.split('.');
        var methodLabel = 'load'+label[0];
        methodLabel = new PIXI.Sprite.fromFrame(x);
        methodLabel.scale = {x: 2, y: 2};
        methodLabel.position = new SL.Vec2(oX, oY);
        app.currentScene.addEntity({
          type: label[0],
          sprite: methodLabel,
          update: function () {}
        });
    }
    //DRAW//
    var jsonObj;
    xhr = new XMLHttpRequest();
    xhr.onload = function () {
        jsonObj = JSON.parse(this.responseText);
        var keys = [];
        var clickKeys = [];
        var indicatorKeys = [];
        var playerKeys = [];
        for(var key in jsonObj.frames){
          keys.push(key);
        }
        for(var i=0;i<keys.length;i++){
            if(keys[i] == 'clickGround1.PNG' || keys[i] == 'clickGround2.PNG' || keys[i] == 'clickGround3.PNG'){
                clickKeys.push(keys[i]);
            }else if(keys[i] == 'alert.PNG'){
                indicatorKeys.push(keys[i]);
            }else if(keys[i] == 'puppyWalk1.PNG' || keys[i] == 'puppyWalk2.PNG' || keys[i] == 'puppyWalk3.PNG'){
                playerKeys.push(keys[i]);
            }
        }
        onloadGenerate(playerKeys[0],250,250);
    };
    xhr.open('get', 'res/atlas.json', true);
    xhr.send();
}

function GameCursor() {
    var indexManagement = new PIXI.DisplayObjectContainer();

}<commit_msg>Removing animation manager, map generator, adding tile and tilegroup classes, map implementation, added tile configurations.<commit_after>SL = sugarLab;

var SCREEN_SIZE = new SL.Vec2(576, 576);

function logPlay() {
    _gaq.push(['_trackEvent', 'Button', 'Play']);
}

function start() {
    var startLoad = new Date;

    window.app = new SL.Game({canvas: document.getElementById('GameCanvas')});
    spritesheet = new PIXI.SpriteSheetLoader('res/atlas.json');

    spritesheet.load();

    app.assetCollection = new SL.AssetCollection('res/assets.json', app, function () {
        _gaq.push(['_trackEvent', 'Game', 'Load', '', (new Date - startLoad) / 1000]);
        $('.canvas-container').append(domjs.build(templates.modal));

        var loadingScene = new SL.Scene('loading', [], function () {
        }, app);

        loadingScene.addEntity(new SL.Loader({
            assetCollection: app.assetCollection,
            screenSize: SCREEN_SIZE,
            loadCallback: function () { app.transitionScene('menu'); },
            barColor: 'blue',
            textColor: 'green'
        }));

        var menuScene = new SL.Scene('menu', [], function () {
            var modal = $('.modal');

            modal.append(domjs.build(templates.menu));

            $('.menu .button').on('click', function () {
                modal.empty();
                modal.off();
                app.transitionScene($(this).attr('id'));
            });

            var curMOUSE;
            modal.mousemove(function(){
                curMOUSE = new SL.Vec2(event.clientX - this.offsetLeft, event.clientY - this.offsetTop);
            });

            var title = new PIXI.Sprite(app.assetCollection.getTexture('title'));
            app.currentScene.addEntity({
                sprite: title,
                update: function () {}
            });
            title.anchor = {x: 0.5, y: 0.5};
            title.position = new SL.Vec2(389, 140);
            var eyes = new PIXI.Sprite(app.assetCollection.getTexture('titleEyes')),
                eyeBase = new SL.Vec2(389, 140);

            eyes.anchor = {x: 0.5, y: 0.5};
            eyes.position = eyeBase;

            var idleClock;
            var valX, valY;
            var preValX, preValY;

            app.currentScene.addEntity({
                sprite: eyes,
                update: function () {
                    if (curMOUSE) {
                        valX = curMOUSE.x;
                        valY = curMOUSE.y;
                        if(valX != preValX){
                            var direction = title.position.angleBetween(curMOUSE);
                            eyes.position = eyeBase.getTranslatedAlongRotation(-3, direction);
                            idleClock=0;
                        } else {
                            if(Math.floor(idleClock) > 1){
                                eyes.position = eyeBase;
                            } else{
                                idleClock += 1/60;
                            }
                        }
                        preValX = valX;
                        preValY = valY;
                    }
                }
            });
        }, app);

        var gameScene = new SL.Scene('game', [], function () {
            var modal = $('.modal'),
                map;
            modal.empty();
            modal.off();
            modal.hide();

            testSprite = new PIXI.Sprite.fromFrame('tree.PNG');

            app.currentScene.addEntity({
                type: 'background',
                sprite: new PIXI.Sprite(app.assetCollection.getTexture('background')),
                update: function () {
                }
            });

            map = new Map();
        }, app);

        app.addScene(loadingScene);
        app.addScene(menuScene);
        app.addScene(gameScene);
        app.transitionScene('loading');

        app.start();
    });
}

function Tile (config) {
    var me = this;

    me.tag = 'tile';
    me.type = config.type;
    me.sprite = new PIXI.Sprite.fromFrame(me.type + '.png');
    me.state = 'idle';
    me.rect = new SL.Rect(config.location, new Vec2(me.sprite.width, me.sprite.height));

    me.update = function () {

    }
}

function TileGroup (location) {
    var me = this,
        tileConfigs = app.assetCollection.assets.tiles,
        largeTileWeight = 0,
        smallTileWeight = 0,
        totalWeight = 0,
        smallTiles = [],
        chances = [],
        largeTiles = [],
        runningChance = 0,
        largeTileChance, tileType, tileSeed;

    me.tag = 'tileGroup';
    me.rect = new SL.Rect(location, new SL.Vec2(576, 128));
    me.tiles = [];

    for (var i = 0; i < tileConfigs.length; i++) {
        if (tileConfigs[i].size === 1) {
            smallTileWeight += tileConfigs[i].rate;
            smallTiles.push(tileConfigs[i]);
        } else {
            largeTileWeight += tileConfigs[i].rate;
            largeTiles.push(tileConfigs[i]);
        }
        totalWeight += tileConfigs[i].rate;
    }
    for (i = 0; i < smallTiles.length; i++) {
        runningChance = 0;
        for (var i2 = 0; i2 <= i; i2++) {
            runningChance += smallTiles[i2].rate / totalWeight;
        }
        chances.push(runningChance / totalWeight);
    }
    largeTileChance = largeTileWeight / totalWeight;

    for (i = 0; i < 2; i++) {
        if (Math.random() < largeTileChance) {
            tileType = Math.random() < largeTiles[0].weight / largeTileWeight ? largeTiles[0].type : largeTiles[1].type;
            me.tiles.push(new Tile({
                type: tileType,
                location: me.rect.location.getTranslated(new SL.Vec2(128 * i, 0))
            }));
        } else {
            for (var i2 = 0; i2 < 4; i2++) {
                tileSeed = Math.random();
                switch (true) {
                    case tileSeed >= 0 && tileSeed < chances[0]:
                        tiles.push(new Tile({
                            type: smallTiles[0].type,
                            location: me.rect.location.getTranslated(new SL.Vec2((i * 128) + ((i2 % 2) * 64), Math.floor(i2 / 2) * 64))
                        }));
                        break;
                    case tileSeed >= chances[0] && tileSeed <= chances[1]:
                        tiles.push(new Tile({
                            type: smallTiles[1].type,
                            location: me.rect.location.getTranslated(new SL.Vec2((i * 128) + ((i2 % 2) * 64), Math.floor(i2 / 2) * 64))
                        }));
                        break;
                    case tileSeed >= chances[1] && tileSeed <= chances[2]:
                        tiles.push(new Tile({
                            type: smallTiles[2].type,
                            location: me.rect.location.getTranslated(new SL.Vec2((i * 128) + ((i2 % 2) * 64), Math.floor(i2 / 2) * 64))
                        }));
                        break;
                    case tileSeed >= chances[2] && tileSeed <= chances[3]:
                        tiles.push(new Tile({
                            type: smallTiles[3].type,
                            location: me.rect.location.getTranslated(new SL.Vec2((i * 128) + ((i2 % 2) * 64), Math.floor(i2 / 2) * 64))
                        }));
                        break;
                    case tileSeed >= chances[3] && tileSeed <= chances[4]:
                        tiles.push(new Tile({
                            type: smallTiles[4].type,
                            location: me.rect.location.getTranslated(new SL.Vec2((i * 128) + ((i2 % 2) * 64), Math.floor(i2 / 2) * 64))
                        }));
                        break;
                }
            }
        }
    }

    for (i = 0; i < me.tiles.length; i++) {
        app.currentScene.addEntity(me.tiles[i]);
    }

    me.update = function () {};
}

function Map () {
    var me = this;

    me.groups = 100;
    me.tileGroups = [];

    for (var i = 0; i < me.groups; i++) {
        me.tileGroups.push(new TileGroup(new SL.Vec2(0, i * 128)));
        app.currentScene.addEntity(me.tileGroups[i]);
    };
}
<|endoftext|>


<commit_before>/*jslint sloppy: true */
/*global enyo, console*/

enyo.kind({
    name: "PackageListItem",
    classes: "group-item-wrapper",
    components: [
        {
            classes: "group-item",
            layoutKind: "FittableColumnsLayout",
            components: [
                {
                    name: "package",
                    content: "",
                    fit: true
                },
            ],
        }],
    handlers: {
        onmousedown: "pressed",
        ondragstart: "released",
        onmouseup: "released"
    },
    pressed: function () {
        this.addClass("onyx-selected");
    },
    released: function () {
        this.removeClass("onyx-selected");
    }
});

enyo.kind({
    name: "LicenseTextItem",
    classes: "group-item-wrapper",
    components: [
        {
            classes: "group-item",
            layoutKind: "FittableColumnsLayout",
            components: [
                {
                    name: "licenseText",
                    tag: "pre",
                    content: "",
                    fit: true
                },
            ],
        }],
});

enyo.kind({
	name: "Licenses",
	kind: "FittableRows",
	fit: true,
	currentRequest: false,
	published: {
		updateResults: null
	},
	components: [
		 /* Top toolbar */
		{kind: "onyx.Toolbar", style: "line-height: 28px;", content: "Licenses"},
		 /* License Panels */
        {
            name: "licensePanels",
            kind: "Panels",
            //arrangerKind: "HFlipArranger",
            fit: true,
            draggable: false,
            components: [
            	/* Package List */
                {
                    kind: "enyo.FittableRows",
                    classes: "content-wrapper",
                    components: [
                        {
                            name: "PackageList",
                            kind: "onyx.Groupbox",
                            layoutKind: "FittableRowsLayout",
                            classes: "content-aligner",
                            style: "max-width: 1024px;",
                            fit: true,
                            components: [
                                {
                                    kind: "onyx.GroupboxHeader",
                                    content: "Choose a Package"
                                },
                                {
                                    classes: "networks-scroll",
                                    kind: "Scroller",
                                    touch: true,
                                    horizontal: "hidden",
                                    fit: true,
                                    components: [{
                                            name: "packageRepeater",
                                            kind: "Repeater",
                                            count: 0,
                                            onSetupItem: "setupPackageRow",
                                            components: [
                                                {
                                                    kind: "PackageListItem",
                                                    ontap: "packageSelected"
                                                }
                                            ]
                                    }]
                                },

        				]}
        		]},
        		/* Loading Message */
        		{name: "spinner", style: "padding: 10px; font-weight: bold; text-align:center;", components: [
					{ tag: "br"},
					{content: "Getting Licenses for Package..."},
					{ tag: "br"},
					{kind: "onyx.Spinner", classes: "onyx-light"}
				]},
        		/*License Text Panel */
        		{
                    kind: "enyo.FittableRows",
                    classes: "content-wrapper",
                    components: [
                        {
                            name: "LicenseList",
                            kind: "onyx.Groupbox",
                            layoutKind: "FittableRowsLayout",
                            classes: "content-aligner",
                            style: "max-width: 1024px;",
                            fit: true,
                            components: [
                                {
                                    kind: "onyx.GroupboxHeader",
                                    content: "Licenses"
                                },
                                {
                                    classes: "networks-scroll",
                                    kind: "Scroller",
                                    touch: true,
                                    //horizontal: "hidden",
                                    fit: true,
                                    components: [{
                                            name: "licenseRepeater",
                                            kind: "Repeater",
                                            count: 0,
                                            onSetupItem: "setupLicenseRow",
                                            components: [
                                                {
                                                    kind: "LicenseTextItem",
                                                }
                                            ]
                                    }]
                                },

        				]}
        		]},

    	]},
        /* Bottom toolbar */
        {
            name: "footer",
            kind: "onyx.Toolbar",
            layoutKind: "FittableColumnsLayout",
            components: [
                {
                    name: "Grabber",
                    kind: "onyx.Grabber"
                }, // this is hacky
                {
                    fit: true
                },
                {
					kind: "onyx.Button",
					name: "btnBack",
					classes: "center",
					content: "Back",
					ontap: "doBack",
					showing: false
				}
            ]
        },
		{
			name: "listPackagesService",
			kind: "enyo.PalmService",
			service: "palm://org.webosports.service.licenses",
			method: "listPackages",
			subscribe: false,
			onComplete: "gotPackageList"
		},
		{
			name: "listLicenseService",
			kind: "enyo.PalmService",
			service: "palm://org.webosports.service.licenses",
			method: "listLicensesForPackage",
			subscribe: false,
			onComplete: "gotLicensesForPackage"
		},
		{
			name: "getLicenseService",
			kind: "enyo.PalmService",
			service: "palm://org.webosports.service.licenses",
			method: "getLicenseTextForPackage",
			subscribe: false,
			onComplete: "gotLicenseText"
		}
	],

	create: function (inSender, inEvent) {
        this.inherited(arguments);
        // load the packages in the background, not in the main load thread
        enyo.asyncMethod(this, this.bindSafely("loadPackages"));
	},

	reflow: function (inSender) {
        this.inherited(arguments);
        if (enyo.Panels.isScreenNarrow())
            this.$.Grabber.applyStyle("visibility", "hidden");
        else
            this.$.Grabber.applyStyle("visibility", "visible");
    },

    loadPackages: function(){
    	if (!window.PalmSystem) {
    		// Setup some mock data
    		this.packageList = ["Package 1", "Package 2", "Package 3", "Package 4", "Package 5", "Package 6", "Package 7", "Package 8", "Package 9", "Package 10"];
    		this.$.packageRepeater.setCount(this.packageList.length);
    	} else {
			this.$.listPackagesService.send({});
    	}

    },

    gotPackageList: function (inSender, inEvent) {
		var result = inEvent.data.packages;
		if(result) {
			this.packageList = result;
			this.$.packageRepeater.setCount(this.packageList.length);
		}
    },

    gotLicensesForPackage: function(inSender, inEvent){
    	var result = inEvent.data.licenses;
    	if(result) {
			this.licenseList = result;

			var numOfLicense = this.licenseList.length;
			for(var i=0; i<numOfLicense; i++){
				this.log("Calling getLicenseService for package:" + this.selectedPackage + " license:" +  this.licenseList[i]);
				this.$.getLicenseService.send({"package": this.selectedPackage, "license": this.licenseList[i]});
			}

		}
    },

    gotLicenseText: function(inSender, inEvent) {
		var result = inEvent.data.license;
		if(result) {
			this.licenseTextList.push(String.fromCharCode.apply(null, new Uint16Array(result)));

			// we have got all the license, refresh the list
			if(this.licenseTextList.length == this.licenseList.length) {

				//this.log(this.licenseTextList);

				this.$.licenseRepeater.setCount(this.licenseTextList.length);
				this.showLicense();
			}

		}
    },

    showPackage: function() {
    	this.$.licensePanels.setIndex(0);
    	this.$.btnBack.hide();
    	this.$.footer.reflow();
    },

    showLoading: function() {
    	this.$.licensePanels.setIndex(1);
    	this.$.btnBack.show();
    	this.$.footer.reflow();
    },

    showLicense: function() {
    	this.$.licensePanels.setIndex(2);
    	this.$.btnBack.show();
    	this.$.footer.reflow();
    },

	setupPackageRow: function (inSender, inEvent) {
        inEvent.item.$.packageListItem.$.package.setContent(this.packageList[inEvent.index]);
	},

	packageSelected: function (inSender, inEvent) {

		this.selectedPackage = this.packageList[inEvent.index];

		this.log("Getting License for Package: " + this.selectedPackage);

		this.licenseTextList = [];

		if (!window.PalmSystem) {
    		// Setup some mock data
    		this.licenseTextList = ["License Text", "License Text"];
    		this.$.licenseRepeater.setCount(this.licenseTextList.length);
    		this.showLicense();
    	} else {
			this.$.listLicenseService.send({"package": this.selectedPackage});
			this.showLoading();
    	}

	},

	setupLicenseRow: function (inSender, inEvent) {
        inEvent.item.$.licenseTextItem.$.licenseText.setContent(this.licenseTextList[inEvent.index]);
	},

	doBack: function (inSender, inEvent) {
		this.showPackage();
	}

});
<commit_msg>Load large license files in batches, fixed scrolling issues<commit_after>/*jslint sloppy: true */
/*global enyo, console*/

enyo.kind({
    name: "PackageListItem",
    classes: "group-item-wrapper",
    components: [
        {
            classes: "group-item",
            layoutKind: "FittableColumnsLayout",
            components: [
                {
                    name: "package",
                    content: "",
                    fit: true
                },
            ],
        }],
    handlers: {
        onmousedown: "pressed",
        ondragstart: "released",
        onmouseup: "released"
    },
    pressed: function () {
        this.addClass("onyx-selected");
    },
    released: function () {
        this.removeClass("onyx-selected");
    }
});

enyo.kind({
    name: "LicenseTextItem",
    classes: "group-item-wrapper",
    components: [
        {
            classes: "group-item",
            layoutKind: "FittableColumnsLayout",
            components: [
                {
                    name: "licenseText",
                    tag: "pre",
                    content: "",
                    fit: true
                },
            ],
        }],
});

enyo.kind({
	name: "Licenses",
	kind: "FittableRows",
	fit: true,
	currentRequest: false,
	published: {
		updateResults: null
	},
	components: [
		 /* Top toolbar */
		{kind: "onyx.Toolbar", style: "line-height: 28px;", content: "Licenses"},
		 /* License Panels */
        {
            name: "licensePanels",
            kind: "Panels",
            //arrangerKind: "HFlipArranger",
            fit: true,
            draggable: false,
            components: [
            	/* Package List */
                {
                    kind: "enyo.FittableRows",
                    classes: "content-wrapper",
                    components: [
                        {
                            name: "PackageList",
                            kind: "onyx.Groupbox",
                            layoutKind: "FittableRowsLayout",
                            classes: "content-aligner",
                            style: "max-width: 1024px;",
                            fit: true,
                            components: [
                                {
                                    kind: "onyx.GroupboxHeader",
                                    content: "Choose a Package"
                                },
                                {
                                    name : "packageScroller",
                                    classes: "networks-scroll",
                                    kind: "Scroller",
                                    touch: true,
                                    horizontal: "hidden",
                                    fit: true,
                                    components: [{
                                            name: "packageRepeater",
                                            kind: "Repeater",
                                            count: 0,
                                            onSetupItem: "setupPackageRow",
                                            components: [
                                                {
                                                    kind: "PackageListItem",
                                                    ontap: "packageSelected"
                                                }
                                            ]
                                    }]
                                },

        				]}
        		]},
        		/* Loading Message */
        		{name: "spinner", style: "padding: 10px; font-weight: bold; text-align:center;", components: [
					{ tag: "br"},
					{content: "Getting Licenses for Package..."},
					{ tag: "br"},
					{kind: "onyx.Spinner", classes: "onyx-light"}
				]},
        		/*License Text Panel */
        		{
                    kind: "enyo.FittableRows",
                    classes: "content-wrapper",
                    components: [
                        {
                            name: "LicenseList",
                            kind: "onyx.Groupbox",
                            layoutKind: "FittableRowsLayout",
                            classes: "content-aligner",
                            style: "max-width: 1024px;",
                            fit: true,
                            components: [
                                {
                                    kind: "onyx.GroupboxHeader",
                                    content: "Licenses"
                                },
                                {
                                    name : "licenseScroller",
                                    classes: "networks-scroll",
                                    kind: "Scroller",
                                    touch: true,
                                    //horizontal: "hidden",
                                    fit: true,
                                    components: [{
                                            name: "licenseRepeater",
                                            kind: "Repeater",
                                            count: 0,
                                            onSetupItem: "setupLicenseRow",
                                            components: [
                                                {
                                                    kind: "LicenseTextItem",
                                                }
                                            ]
                                    }]
                                },

        				]}
        		]},

    	]},
        /* Bottom toolbar */
        {
            name: "footer",
            kind: "onyx.Toolbar",
            layoutKind: "FittableColumnsLayout",
            components: [
                {
                    name: "Grabber",
                    kind: "onyx.Grabber"
                }, // this is hacky
                {
                    fit: true
                },
                {
					kind: "onyx.Button",
					name: "btnBack",
					classes: "center",
					content: "Back",
					ontap: "doBack",
					showing: false
				}
            ]
        },
		{
			name: "listPackagesService",
			kind: "enyo.PalmService",
			service: "palm://org.webosports.service.licenses",
			method: "listPackages",
			subscribe: false,
			onComplete: "gotPackageList"
		},
		{
			name: "listLicenseService",
			kind: "enyo.PalmService",
			service: "palm://org.webosports.service.licenses",
			method: "listLicensesForPackage",
			subscribe: false,
			onComplete: "gotLicensesForPackage"
		},
		{
			name: "getLicenseService",
			kind: "enyo.PalmService",
			service: "palm://org.webosports.service.licenses",
			method: "getLicenseTextForPackage",
			subscribe: false,
			onComplete: "gotLicenseText"
		}
	],

	create: function (inSender, inEvent) {
        this.inherited(arguments);
        // load the packages in the background, not in the main load thread
        enyo.asyncMethod(this, this.bindSafely("loadPackages"));
	},

	reflow: function (inSender) {
        this.inherited(arguments);
        if (enyo.Panels.isScreenNarrow())
            this.$.Grabber.applyStyle("visibility", "hidden");
        else
            this.$.Grabber.applyStyle("visibility", "visible");
    },

    loadPackages: function(){
    	if (!window.PalmSystem) {
    		// Setup some mock data
    		this.packageList = ["Package 1", "Package 2", "Package 3", "Package 4", "Package 5", "Package 6", "Package 7", "Package 8", "Package 9", "Package 10"];
    		this.$.packageRepeater.setCount(this.packageList.length);
    	} else {
			this.$.listPackagesService.send({});
    	}

    },

    gotPackageList: function (inSender, inEvent) {
		var result = inEvent.data.packages;
		if(result) {
			this.packageList = result;
			this.$.packageRepeater.setCount(this.packageList.length);
		}
    },

    gotLicensesForPackage: function(inSender, inEvent){
    	var result = inEvent.data.licenses;
    	if(result) {
			this.licenseList = result;

			var numOfLicense = this.licenseList.length;
			for(var i=0; i<numOfLicense; i++){
				this.log("Calling getLicenseService for package:" + this.selectedPackage + " license:" +  this.licenseList[i]);
				this.$.getLicenseService.send({"package": this.selectedPackage, "license": this.licenseList[i]});
			}

		}
    },

    gotLicenseText: function(inSender, inEvent) {
		var result = inEvent.data.license;
		if(result) {

			this.licenseTextList.push(this.decodeString(result));

			// we have got all the license, refresh the list
			if(this.licenseTextList.length == this.licenseList.length) {

				this.log("Number of Licenses: " + this.licenseTextList.length);

                this.showLicense();
				this.$.licenseRepeater.setCount(this.licenseTextList.length);

			}

		}
    },

    // On some browsers If you try to decode an array largre than 2^16
    // it throws an error about Max stack depth.  So chunk it to batches
    decodeString: function(buf){
        var str = "";
        var b = new Uint16Array(buf);
        var n = b.length;
        var CHUNK_SIZE = Math.pow(2, 16);
        var offset, len;
        for (offset = 0; offset < n; offset += CHUNK_SIZE) {
            len = Math.min(CHUNK_SIZE, n-offset);
            str += String.fromCharCode.apply(null, b.subarray(offset, offset+len));
        }
        return str;
    },

    showPackage: function() {
    	this.$.licensePanels.setIndex(0);
    	this.$.btnBack.hide();
    	this.$.footer.reflow();
    },

    showLoading: function() {
    	this.$.licensePanels.setIndex(1);
    	this.$.btnBack.show();
    	this.$.footer.reflow();
    },

    showLicense: function() {
    	this.$.licensePanels.setIndex(2);
        this.$.licenseScroller.stabilize();
        this.$.licenseScroller.scrollToTop();
    	this.$.btnBack.show();
    	this.$.footer.reflow();
    },

	setupPackageRow: function (inSender, inEvent) {
        inEvent.item.$.packageListItem.$.package.setContent(this.packageList[inEvent.index]);
        return true;
	},

	packageSelected: function (inSender, inEvent) {

		this.selectedPackage = this.packageList[inEvent.index];

		this.log("Getting License for Package: " + this.selectedPackage);

		this.licenseTextList = [];

		if (!window.PalmSystem) {
    		// Setup some mock data
    		this.licenseTextList = ["License Text", "License Text"];
    		this.$.licenseRepeater.setCount(this.licenseTextList.length);
    		this.showLicense();
    	} else {
			this.$.listLicenseService.send({"package": this.selectedPackage});
			this.showLoading();
    	}

	},

	setupLicenseRow: function (inSender, inEvent) {
        //this.log("setupLicenseRow: " + inEvent.index);
        inEvent.item.$.licenseTextItem.$.licenseText.setContent(this.licenseTextList[inEvent.index]);
        return true;
	},

	doBack: function (inSender, inEvent) {
		this.showPackage();
	}

});
<|endoftext|>