<commit_before>var GameState = function(game) {
        this.MAX_CATS = 3; // number of cats
        this.MAX_YARN = 3;
        this.catsLeft = 5;
};

GameState.prototype.preload = function() {
    this.game.load.image('tile', 'assets/env/tile.png');
    this.game.load.image('room', 'assets/env/room.png');
    this.game.load.spritesheet('heart', 'assets/env/heart-sprite.png');

    // Load items assets
    this.game.load.image('yarn', 'assets/items/yarn.png');

    // Load cat assets
    this.game.load.spritesheet('garfield', 'assets/cats/garfield-sprite.png', 100, 100);
    this.game.load.spritesheet('marie', 'assets/cats/marie-sprite.png', 100, 100);
    this.game.load.spritesheet('mimi', 'assets/cats/mimi-sprite.png', 100, 100);
    this.game.load.spritesheet('whitey', 'assets/cats/whitey-sprite.png', 100, 100);

    // Audio!
    this.game.load.audio('theme', 'assets/audio/theme.ogg');
    this.game.load.audio('meow', 'assets/audio/meow.ogg');

    // Background!
    this.game.load.image('background', 'assets/background.jpg');
};

GameState.prototype.create = function() {
    // Set stage background
    this.game.add.image(0, 0, 'background');

    // Make transparent sprite for detecting mouse click
    clickablebg = this.game.add.sprite(0, 0);
    clickablebg.scale.setTo(game.width/clickablebg.width, game.height/clickablebg.height);
    clickablebg.inputEnabled = true;
    clickablebg.input.priorityID = 0;
    clickablebg.events.onInputDown.add(this.placeObstacle, this, 0, this);

    // Create a group to hold the cat
    this.catGroup = this.game.add.group();
    this.obstacleGroup = this.game.add.group();

    this.theme = this.game.add.audio('theme');
    meow = this.game.add.audio('meow');
    // Loop theme forever
    this.theme.loopFull(0.5);

    this.catsLeftText = this.game.add.text(this.game.width - 270, 20, "Cats left: " + this.catsLeft);

    // Setup pause button
    var pause_btn = this.input.keyboard.addKey(27);
    pause_btn.onDown.add(this.togglePause, this);

    // Add ammo tracker
    var tile = this.game.add.image(10, 10, 'tile');
    tile.scale.setTo(2, 1);

    var icon = this.game.add.image(20, 20, 'yarn');
    icon.scale.setTo(.8, .8);

    ammocount = 3
    ammotext = this.game.add.text(90, 30, this.ammocount);
};

GameState.prototype.update = function() {
    ammotext.text = ammocount;

    // If there are fewer than MAX_CATS, launch a new one
    if (this.catGroup.countLiving() < this.MAX_CATS) {
        // Set the launch point to a random location past the right edge
        // of the stage
        var newcat = this.launchCat(this.game.width - 100,
			this.game.rnd.integerInRange(50, this.game.height-50));

        // Move cat to the center of the left edge of the stage
        this.game.physics.arcade.moveToXY(newcat, 0, this.game.width / 2);
    }

    this.game.physics.arcade.collide(this.catGroup, this.obstacleGroup);
};

GameState.prototype.togglePause = function() {
   if (game.physics.arcade.isPaused) {
      this.text.destroy();
      game.physics.arcade.isPaused = false;
   } else {
      this.text = game.add.text(game.world.centerX, game.world.centerY, "Paused", {
        font: "65px Arial",
        fill: "#ffffff",
        align: "center"
      });
      game.physics.arcade.isPaused = true;
   }
};

GameState.prototype.launchCat = function(x, y) {
    // Get the first dead cat from the catGroup :'(
    var cat = this.catGroup.getFirstDead();

    // If there aren't any available, create a new one
    if (cat === null) {
        cat = new Cat(this.game);
        this.catGroup.add(cat);
    } else {
        this.catsLeft--;
        this.catsLeftText.setText("Cats Left: " + this.catsLeft);

        if (this.catsLeft == 0) {
            // End game
            this.togglePause();
        }
    }

    // Revive the cat (set it's alive property to true)
    // You can also define a onRevived event handler in your cat objects
    // to do stuff when they are revived.
    cat.revive();

    // Move the cat to the given coordinates
    cat.x = x;
    cat.y = y;

    return cat;
};

var Cat = function (game, x, y) {
    var type = ['garfield',
            'marie',
            'mimi',
            'whitey'][game.rnd.integerInRange(0,3)];

    Phaser.Sprite.call(this, game, x, y, type);
    this.health = [30,70,100][game.rnd.integerInRange(0,2)];
    this.animations.add('left', [0, 1], 2, true, true);

    game.physics.enable(this, Phaser.Physics.ARCADE);

    this.body.onCollide = new Phaser.Signal();
    this.body.onCollide.add(function(me, other) {
      // When a cat collides with an obstacle, that obstacle should lose one
      // hit point and the cat should lose one hit point.
      //
      // This can probably be done with sprite health things. If the health
      // gets to zero, either the obstacle or the cat is kill.
      //
      // If the cat is alive after a collision it should try to move again. If
      // the obstacle is alive again, it'll collide again. This repeats until
      // one or both dies.
      //
      // If the cat survives and obstacle dies, the cat should begin moving
      // back to the target.
      meow.play();
      me.damage(35);
      other.damage(50);

      if (me.alive) {
         var timer = game.time.create(false);
         timer.add(500, function(cat) {
            this.physics.arcade.moveToXY(cat, 0, this.width / 2);
         }, game, me);
         timer.start();
      }
    });
}

Cat.prototype = Object.create(Phaser.Sprite.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.update = function() {
    this.animations.play('left');
};

/*
    Obstacles code
*/

GameState.prototype.placeObstacle = function (sprite, pointer) {
    var x = pointer.x;
    var y = pointer.y;
    // Get the first dead cat from the catGroup :'(
    var obstacle = this.obstacleGroup.getFirstDead();

    // If there aren't any available, create a new one
    if (obstacle === null) {
      if (this.obstacleGroup.length < this.MAX_YARN) {
         obstacle = new Obstacle(this.game);
         this.obstacleGroup.add(obstacle);
      } else {
         return;
      }
    }

    // Revive the cat (set it's alive property to true)
    // You can also define a onRevived event handler in your cat objects
    // to do stuff when they are revived.
    obstacle.revive();

    // Move the cat to the given coordinates
    obstacle.x = x;
    obstacle.y = y;

    // Accounting
    ammocount -= 1;

    return obstacle;
}

var Obstacle = function (game, x, y) {
    Phaser.Sprite.call(this, game, x, y, 'yarn');
    this.game.physics.enable(this, Phaser.Physics.ARCADE);
    this.body.immovable = true;
    this.events.onKilled.add(function() {
        ammocount += 1;
    });
    this.inputEnabled = true;
    this.input.priorityID = 1;
    this.events.onInputDown.add(function (elem) {
        elem.kill();
    }, this);
    this.anchor = new Phaser.Point(.5,.5);
}

Obstacle.prototype = Object.create(Phaser.Sprite.prototype);
Obstacle.prototype.constructor = Obstacle;

var game = new Phaser.Game(1024, 768, Phaser.AUTO, 'game');
game.state.add('game', GameState, true);
<commit_msg>Adjust constants to make more challenge<commit_after>var GameState = function(game) {
        this.MAX_CATS = 5; // number of cats
        this.MAX_YARN = 2;
        this.catsLeft = 15;
};

GameState.prototype.preload = function() {
    this.game.load.image('tile', 'assets/env/tile.png');
    this.game.load.image('room', 'assets/env/room.png');
    this.game.load.spritesheet('heart', 'assets/env/heart-sprite.png');

    // Load items assets
    this.game.load.image('yarn', 'assets/items/yarn.png');

    // Load cat assets
    this.game.load.spritesheet('garfield', 'assets/cats/garfield-sprite.png', 100, 100);
    this.game.load.spritesheet('marie', 'assets/cats/marie-sprite.png', 100, 100);
    this.game.load.spritesheet('mimi', 'assets/cats/mimi-sprite.png', 100, 100);
    this.game.load.spritesheet('whitey', 'assets/cats/whitey-sprite.png', 100, 100);

    // Audio!
    this.game.load.audio('theme', 'assets/audio/theme.ogg');
    this.game.load.audio('meow', 'assets/audio/meow.ogg');

    // Background!
    this.game.load.image('background', 'assets/background.jpg');
};

GameState.prototype.create = function() {
    // Set stage background
    this.game.add.image(0, 0, 'background');

    // Make transparent sprite for detecting mouse click
    clickablebg = this.game.add.sprite(0, 0);
    clickablebg.scale.setTo(game.width/clickablebg.width, game.height/clickablebg.height);
    clickablebg.inputEnabled = true;
    clickablebg.input.priorityID = 0;
    clickablebg.events.onInputDown.add(this.placeObstacle, this, 0, this);

    // Create a group to hold the cat
    this.catGroup = this.game.add.group();
    this.obstacleGroup = this.game.add.group();

    this.theme = this.game.add.audio('theme');
    meow = this.game.add.audio('meow');
    // Loop theme forever
    this.theme.loopFull(0.5);

    this.catsLeftText = this.game.add.text(this.game.width - 270, 20, "Cats left: " + this.catsLeft);

    // Setup pause button
    var pause_btn = this.input.keyboard.addKey(27);
    pause_btn.onDown.add(this.togglePause, this);

    // Add ammo tracker
    var tile = this.game.add.image(10, 10, 'tile');
    tile.scale.setTo(2, 1);

    var icon = this.game.add.image(20, 20, 'yarn');
    icon.scale.setTo(.8, .8);

    ammocount = 3
    ammotext = this.game.add.text(90, 30, this.ammocount);
};

GameState.prototype.update = function() {
    ammotext.text = ammocount;

    // If there are fewer than MAX_CATS, launch a new one
    if (this.catGroup.countLiving() < this.MAX_CATS) {
        // Set the launch point to a random location past the right edge
        // of the stage
        var newcat = this.launchCat(this.game.width - 100,
			this.game.rnd.integerInRange(50, this.game.height-50));

        // Move cat to the center of the left edge of the stage
        this.game.physics.arcade.moveToXY(newcat, 0, this.game.width / 2);
    }

    this.game.physics.arcade.collide(this.catGroup, this.obstacleGroup);
};

GameState.prototype.togglePause = function() {
   if (game.physics.arcade.isPaused) {
      this.text.destroy();
      game.physics.arcade.isPaused = false;
   } else {
      this.text = game.add.text(game.world.centerX, game.world.centerY, "Paused", {
        font: "65px Arial",
        fill: "#ffffff",
        align: "center"
      });
      game.physics.arcade.isPaused = true;
   }
};

GameState.prototype.launchCat = function(x, y) {
    // Get the first dead cat from the catGroup :'(
    var cat = this.catGroup.getFirstDead();

    // If there aren't any available, create a new one
    if (cat === null) {
        cat = new Cat(this.game);
        this.catGroup.add(cat);
    } else {
        this.catsLeft--;
        this.catsLeftText.setText("Cats Left: " + this.catsLeft);

        if (this.catsLeft == 0) {
            // End game
            this.togglePause();
        }
    }

    // Revive the cat (set it's alive property to true)
    // You can also define a onRevived event handler in your cat objects
    // to do stuff when they are revived.
    cat.revive();

    // Move the cat to the given coordinates
    cat.x = x;
    cat.y = y;

    return cat;
};

var Cat = function (game, x, y) {
    var type = ['garfield',
            'marie',
            'mimi',
            'whitey'][game.rnd.integerInRange(0,3)];

    Phaser.Sprite.call(this, game, x, y, type);
    this.health = [30,70,100][game.rnd.integerInRange(0,2)];
    this.animations.add('left', [0, 1], 2, true, true);

    game.physics.enable(this, Phaser.Physics.ARCADE);

    this.body.onCollide = new Phaser.Signal();
    this.body.onCollide.add(function(me, other) {
      // When a cat collides with an obstacle, that obstacle should lose one
      // hit point and the cat should lose one hit point.
      //
      // This can probably be done with sprite health things. If the health
      // gets to zero, either the obstacle or the cat is kill.
      //
      // If the cat is alive after a collision it should try to move again. If
      // the obstacle is alive again, it'll collide again. This repeats until
      // one or both dies.
      //
      // If the cat survives and obstacle dies, the cat should begin moving
      // back to the target.
      meow.play();
      me.damage(35);
      other.damage(50);

      if (me.alive) {
         var timer = game.time.create(false);
         timer.add(500, function(cat) {
            this.physics.arcade.moveToXY(cat, 0, this.width / 2);
         }, game, me);
         timer.start();
      }
    });
}

Cat.prototype = Object.create(Phaser.Sprite.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.update = function() {
    this.animations.play('left');
};

/*
    Obstacles code
*/

GameState.prototype.placeObstacle = function (sprite, pointer) {
    var x = pointer.x;
    var y = pointer.y;
    // Get the first dead cat from the catGroup :'(
    var obstacle = this.obstacleGroup.getFirstDead();

    // If there aren't any available, create a new one
    if (obstacle === null) {
      if (this.obstacleGroup.length < this.MAX_YARN) {
         obstacle = new Obstacle(this.game);
         this.obstacleGroup.add(obstacle);
      } else {
         return;
      }
    }

    // Revive the cat (set it's alive property to true)
    // You can also define a onRevived event handler in your cat objects
    // to do stuff when they are revived.
    obstacle.revive();

    // Move the cat to the given coordinates
    obstacle.x = x;
    obstacle.y = y;

    // Accounting
    ammocount -= 1;

    return obstacle;
}

var Obstacle = function (game, x, y) {
    Phaser.Sprite.call(this, game, x, y, 'yarn');
    this.game.physics.enable(this, Phaser.Physics.ARCADE);
    this.body.immovable = true;
    this.events.onKilled.add(function() {
        ammocount += 1;
    });
    this.inputEnabled = true;
    this.input.priorityID = 1;
    this.events.onInputDown.add(function (elem) {
        elem.kill();
    }, this);
    this.anchor = new Phaser.Point(.5,.5);
}

Obstacle.prototype = Object.create(Phaser.Sprite.prototype);
Obstacle.prototype.constructor = Obstacle;

var game = new Phaser.Game(1024, 768, Phaser.AUTO, 'game');
game.state.add('game', GameState, true);
<|endoftext|>


<commit_before>/**
 * semantic-merge
 *
 * @author     Robert Rossmann <rr.rossmann@me.com>
 * @copyright  2015 Robert Rossmann
 * @license    http://choosealicense.com/licenses/bsd-3-clause  BSD-3-Clause License
 */

'use strict'

var Merger = require('../lib/merger')
  , parent
  , source
  , target
  , merge

// Prepare a base merger instance and reset parent, source and target objects
beforeEach(function () {
  parent = { parentProp: 'I am in parent', 'test-prop': 'test' }

  source = Object.create(parent)
  source.a = 'string'
  source.b = 5
  source[3] = 'numeric key'
  source.c = null   // typeof null === 'object', an ideal test case for recursive object merge
  source.nested = { nestedProp: 'I am in a nested object' }
  source.arr = [ 'a', 'b', 'c' ]

  target = {}

  merge = new Merger(source)
})

describe('Basics', function () {

  it('should be a function', function () {
    Merger.should.be.Function()
  })

  it('should return a new instance when called without \'new\'', function () {
    Merger.call(null, source).should.be.instanceof(Merger)
  })

  it('should throw when source is null or not typeof \'object\'', function () {
    // Invalid source in constructor
    void function () {
      new Merger('a string').into({})
    }.should.throw(TypeError)
    // Invalid source in .and() call
    void function () {
      merge.and('a string').into({})
    }.should.throw(TypeError)
    // null is also typeof object, unfortunately...
    void function () {
      merge.and(null).into({})
    }.should.throw(TypeError)
  })

  it('should throw when target is null or not typeof \'object\'', function () {
    // No target given
    void function () {
      new Merger({}).into()
    }.should.throw(TypeError)
    // Target is null (which is typeof object...)
    void function () {
      new Merger({}).into(null)
    }.should.throw(TypeError)
  })
})

describe('Merging', function () {

  it('should merge two objects', function () {
    merge.into(target)

    // Deliberately not using `should.have.keys` because the presence or absence of other keys is
    // tested in a separate test case
    target.should.have.properties('a', 'b', 3)
  })

  it('should return the target object', function () {
    merge.into(target).should.be.exactly(target)
  })

  it('should only copy enumerable properties', function () {
    Object.defineProperty(source, 'hiddenProp', { value: 'I shall be ignored' })

    merge.into(target)

    source.should.have.property('hiddenProp')   // Sanity check
    target.should.not.have.property('hiddenProp')
  })

  it('should only copy own properties', function () {
    merge.into(target)

    source.should.have.property('parentProp')   // Sanity check
    target.should.not.have.property('parentProp')
  })

  it('should work with a null property', function () {
    source.nullProp = null
    merge.into(target)

    target.should.have.property('nullProp', null)
  })

  it('should work with an undefined property', function () {
    source.undefProp = undefined
    merge.into(target)

    target.should.have.property('undefProp', undefined)
  })

  it('should work with arrays as source', function () {
    merge = new Merger([ 'a', 'b', 'c' ])
    merge.into(target)
    target.should.have.keys(0, 1, 2)
  })

  it('should work with arrays as target', function () {
    merge = new Merger([ 'a', 'b', 'c' ])
    target = []
    merge.into(target)

    target.should.have.keys(0, 1, 2)
  })
})

describe('Merging multiple sources', function () {

  it('should support merging multiple source objects', function () {
    merge.and({ anotherProp: 'from another source' }).into(target)

    target.should.have.property('anotherProp')
  })

  it('should merge last specified object as first, thus giving it lowest priority', function () {
    merge.and({ nested: { nestedProp: 'I should not override the source' } }).into(target)

    target.nested.should.have.property('nestedProp', 'I am in a nested object')
  })
})

describe('Merging - recursion', function () {

  it('should perform only shallow merge by default', function () {
    merge.into(target)

    target.nested.should.be.exactly(source.nested)
  })

  it('should perform recursive merge when enabled', function () {
    merge.recursively.into(target)

    target.nested.should.not.equal(source.nested, 'The object instances must not be identical')
    target.nested.should.have.keys(Object.keys(source.nested))
  })

  it('should not create new objects if the target already exists during recursion', function () {
    var nested = { nestedProp: 'I shall be overwritten' }
    target.nested = nested

    merge.recursively.into(target)

    target.nested.should.equal(nested, 'The object instances must be identical')
  })

  it('should merge into the same object type when target object does not exist', function () {
    merge.recursively.into(target)

    target.nested.should.be.Object()
    target.arr.should.be.Array()
  })

  it('should preserve properties already present on nested objects during recursion', function () {
    var nested = { anotherProp: 'I shall remain' }
    target.nested = nested

    merge.recursively.into(target)

    target.nested.should.have.property('anotherProp', 'I shall remain')
  })

  xit('should properly handle cyclic recursions', function () {
    source.source = source    // Create a backreference on itself

    merge.recursively.into(target)

    target.source.should.be.exactly(target)
  })
})

describe('Merging - exclusions', function () {

  it('should ignore excluded properties', function () {
    merge.excluding('a').into(target)

    target.should.not.have.property('a')
  })

  it('should allow excluded properties to be specified as array', function () {
    merge.excluding(['a']).into(target)

    target.should.not.have.property('a')
  })

  it('should ignore excluded properties on nested objects when merging recursively', function () {
    merge.excluding('nestedProp').recursively.into(target)

    target.nested.should.not.have.property('nestedProp')
  })
})

describe('Merging - arrays', function () {

  it('should merge arrays by value, not by key', function () {
    merge.and({ arr: [ 1, 2, 3 ] }).recursively.into(target)

    target.arr.should.have.length(6)
    target.arr.should.containDeep([ 'a', 'b', 'c', 1, 2, 3 ])
  })

  it('should skip primitives already present in source array', function () {
    merge.and({ arr: [ 1, 'a' ] }).recursively.into(target)

    target.arr.should.have.length(4)
    target.arr.should.containDeep([ 'a', 'b', 'c', 1 ])
  })

  it('should skip objects already present in source array', function () {
    var obj = { test: 'prop' }
    source.arr.push(obj)
    merge.and({ arr: [obj] }).recursively.into(target)

    target.arr.should.have.length(4)
    target.arr.should.containDeep([ 'a', 'b', 'c', obj ])
  })
})
<commit_msg>No need to use .call() in this test<commit_after>/**
 * semantic-merge
 *
 * @author     Robert Rossmann <rr.rossmann@me.com>
 * @copyright  2015 Robert Rossmann
 * @license    http://choosealicense.com/licenses/bsd-3-clause  BSD-3-Clause License
 */

'use strict'

var Merger = require('../lib/merger')
  , parent
  , source
  , target
  , merge

// Prepare a base merger instance and reset parent, source and target objects
beforeEach(function () {
  parent = { parentProp: 'I am in parent', 'test-prop': 'test' }

  source = Object.create(parent)
  source.a = 'string'
  source.b = 5
  source[3] = 'numeric key'
  source.c = null   // typeof null === 'object', an ideal test case for recursive object merge
  source.nested = { nestedProp: 'I am in a nested object' }
  source.arr = [ 'a', 'b', 'c' ]

  target = {}

  merge = new Merger(source)
})

describe('Basics', function () {

  it('should be a function', function () {
    Merger.should.be.Function()
  })

  it('should return a new instance when called without \'new\'', function () {
    Merger(source).should.be.instanceof(Merger)   // eslint-disable-line new-cap
  })

  it('should throw when source is null or not typeof \'object\'', function () {
    // Invalid source in constructor
    void function () {
      new Merger('a string').into({})
    }.should.throw(TypeError)
    // Invalid source in .and() call
    void function () {
      merge.and('a string').into({})
    }.should.throw(TypeError)
    // null is also typeof object, unfortunately...
    void function () {
      merge.and(null).into({})
    }.should.throw(TypeError)
  })

  it('should throw when target is null or not typeof \'object\'', function () {
    // No target given
    void function () {
      new Merger({}).into()
    }.should.throw(TypeError)
    // Target is null (which is typeof object...)
    void function () {
      new Merger({}).into(null)
    }.should.throw(TypeError)
  })
})

describe('Merging', function () {

  it('should merge two objects', function () {
    merge.into(target)

    // Deliberately not using `should.have.keys` because the presence or absence of other keys is
    // tested in a separate test case
    target.should.have.properties('a', 'b', 3)
  })

  it('should return the target object', function () {
    merge.into(target).should.be.exactly(target)
  })

  it('should only copy enumerable properties', function () {
    Object.defineProperty(source, 'hiddenProp', { value: 'I shall be ignored' })

    merge.into(target)

    source.should.have.property('hiddenProp')   // Sanity check
    target.should.not.have.property('hiddenProp')
  })

  it('should only copy own properties', function () {
    merge.into(target)

    source.should.have.property('parentProp')   // Sanity check
    target.should.not.have.property('parentProp')
  })

  it('should work with a null property', function () {
    source.nullProp = null
    merge.into(target)

    target.should.have.property('nullProp', null)
  })

  it('should work with an undefined property', function () {
    source.undefProp = undefined
    merge.into(target)

    target.should.have.property('undefProp', undefined)
  })

  it('should work with arrays as source', function () {
    merge = new Merger([ 'a', 'b', 'c' ])
    merge.into(target)
    target.should.have.keys(0, 1, 2)
  })

  it('should work with arrays as target', function () {
    merge = new Merger([ 'a', 'b', 'c' ])
    target = []
    merge.into(target)

    target.should.have.keys(0, 1, 2)
  })
})

describe('Merging multiple sources', function () {

  it('should support merging multiple source objects', function () {
    merge.and({ anotherProp: 'from another source' }).into(target)

    target.should.have.property('anotherProp')
  })

  it('should merge last specified object as first, thus giving it lowest priority', function () {
    merge.and({ nested: { nestedProp: 'I should not override the source' } }).into(target)

    target.nested.should.have.property('nestedProp', 'I am in a nested object')
  })
})

describe('Merging - recursion', function () {

  it('should perform only shallow merge by default', function () {
    merge.into(target)

    target.nested.should.be.exactly(source.nested)
  })

  it('should perform recursive merge when enabled', function () {
    merge.recursively.into(target)

    target.nested.should.not.equal(source.nested, 'The object instances must not be identical')
    target.nested.should.have.keys(Object.keys(source.nested))
  })

  it('should not create new objects if the target already exists during recursion', function () {
    var nested = { nestedProp: 'I shall be overwritten' }
    target.nested = nested

    merge.recursively.into(target)

    target.nested.should.equal(nested, 'The object instances must be identical')
  })

  it('should merge into the same object type when target object does not exist', function () {
    merge.recursively.into(target)

    target.nested.should.be.Object()
    target.arr.should.be.Array()
  })

  it('should preserve properties already present on nested objects during recursion', function () {
    var nested = { anotherProp: 'I shall remain' }
    target.nested = nested

    merge.recursively.into(target)

    target.nested.should.have.property('anotherProp', 'I shall remain')
  })

  xit('should properly handle cyclic recursions', function () {
    source.source = source    // Create a backreference on itself

    merge.recursively.into(target)

    target.source.should.be.exactly(target)
  })
})

describe('Merging - exclusions', function () {

  it('should ignore excluded properties', function () {
    merge.excluding('a').into(target)

    target.should.not.have.property('a')
  })

  it('should allow excluded properties to be specified as array', function () {
    merge.excluding(['a']).into(target)

    target.should.not.have.property('a')
  })

  it('should ignore excluded properties on nested objects when merging recursively', function () {
    merge.excluding('nestedProp').recursively.into(target)

    target.nested.should.not.have.property('nestedProp')
  })
})

describe('Merging - arrays', function () {

  it('should merge arrays by value, not by key', function () {
    merge.and({ arr: [ 1, 2, 3 ] }).recursively.into(target)

    target.arr.should.have.length(6)
    target.arr.should.containDeep([ 'a', 'b', 'c', 1, 2, 3 ])
  })

  it('should skip primitives already present in source array', function () {
    merge.and({ arr: [ 1, 'a' ] }).recursively.into(target)

    target.arr.should.have.length(4)
    target.arr.should.containDeep([ 'a', 'b', 'c', 1 ])
  })

  it('should skip objects already present in source array', function () {
    var obj = { test: 'prop' }
    source.arr.push(obj)
    merge.and({ arr: [obj] }).recursively.into(target)

    target.arr.should.have.length(4)
    target.arr.should.containDeep([ 'a', 'b', 'c', obj ])
  })
})
<|endoftext|>


<commit_before>var options,
    hoverZoomPlugins = hoverZoomPlugins || [],
    VK_CTRL = 1024,
    VK_SHIFT = 2048,
    actionKeys = ['actionKey', 'hideKey', 'openImageInWindowKey', 'openImageInTabKey', 'saveImageKey', 'fullZoomKey', 'prevImgKey', 'nextImgKey'];

function getMilliseconds(ctrl) {
    var value = parseFloat(ctrl.val());
    value = isNaN(value) ? 0 : value * 1000;
    ctrl.val(value / 1000);
    return value;
}

function keyCodeToString(key) {
    var s = '';
    if (key & VK_CTRL) {
        s += 'Ctrl+';
        key &= ~VK_CTRL;
    }
    if (key & VK_SHIFT) {
        s += 'Shift+';
        key &= ~VK_SHIFT;
    }
    if (key >= 65 && key < 91) {
        s += String.fromCharCode('A'.charCodeAt(0) + key - 65);
    } else if (key >= 112 && key < 124) {
        s += 'F' + (key - 111);
    }
    return s;
}

function initActionKeys() {
    actionKeys.forEach(function(key) {
        var id = key[0].toUpperCase() + key.substr(1);
        var title = chrome.i18n.getMessage("opt" + id + "Title");
        var description = chrome.i18n.getMessage("opt" + id + "Description");
        $('
' + title + '
' + description + '
	' +
            '<select id="sel' + id + '" class="actionKey"/>
').appendTo($('#tableActionKeys'));
        loadKeys($('#sel' + id));
    });
}

function loadKeys(sel) {
    $('<option value="0">None</option>').appendTo(sel);
    if (sel.attr('id') != 'selOpenImageInTabKey')
        $('<option value="16">Shift</option>').appendTo(sel);
    $('<option value="17">Ctrl</option>').appendTo(sel);
    if (navigator.appVersion.indexOf('Macintosh') > -1) {
        $('<option value="91">Command</option>').appendTo(sel);
    }
    for (var i = 65; i < 91; i++) {
        $('<option value="' + i + '">&#' + i + ';</option>').appendTo(sel);
    }
    for (var i = 112; i < 124; i++) {
        $('<option value="' + i + '">F' + (i - 111) + '</option>').appendTo(sel);
    }
    $('<option value="33">Page Up</option>').appendTo(sel);
    $('<option value="34">Page Down</option>').appendTo(sel);
    $('<option value="35">End</option>').appendTo(sel);
    $('<option value="36">Home</option>').appendTo(sel);
    $('<option value="37">Left</option>').appendTo(sel);
    $('<option value="38">Up</option>').appendTo(sel);
    $('<option value="39">Right</option>').appendTo(sel);
    $('<option value="40">Down</option>').appendTo(sel);
    $('<option value="45">Insert</option>').appendTo(sel);
    $('<option value="46">Delete</option>').appendTo(sel);
}

// Saves options to localStorage.
// TODO: Migrate to https://developer.chrome.com/extensions/storage
function saveOptions() {
    options.extensionEnabled = $('#chkExtensionEnabled')[0].checked;
    options.zoomFactor = $('#txtZoomFactor')[0].value;
    options.zoomVideos = $('#chkZoomVideos')[0].checked;
    options.videoPositionStep = $('#txtVideoPositionStep')[0].value;
    options.muteVideos = $('#chkMuteVideos')[0].checked;
    options.videoTimestamp = $('#chkVideoTimestamp')[0].checked;
    options.videoVolume = $('#txtVideoVolume')[0].value / 100;
    options.mouseUnderlap = $('#chkMouseUnderlap')[0].checked;
    options.pageActionEnabled = $('#chkPageActionEnabled')[0].checked;
    options.showCaptions = $('#chkShowCaptions')[0].checked;
    options.showWhileLoading = $('#chkShowWhileLoading')[0].checked;
    options.showHighRes = $('#chkShowHighRes')[0].checked;
    options.galleriesMouseWheel = $('#chkGalleriesMouseWheel')[0].checked;
    options.disableMouseWheelForVideo = $('#chkDisableMouseWheelForVideo')[0].checked;
    options.displayDelay = getMilliseconds($('#txtDisplayDelay'));
    options.displayDelayVideo = getMilliseconds($('#txtDisplayDelayVideo'));
    options.fadeDuration = getMilliseconds($('#txtFadeDuration'));
    options.ambilightEnabled = $('#chkAmbilightEnabled')[0].checked;

    options.whiteListMode = $('#chkWhiteListMode')[0].checked;
    options.excludedSites = [];
    $('#selExcludedSites').find('span').each(function () {
        options.excludedSites.push($(this).text());
    });

    options.disabledPlugins = [];
    $('.chkPlugin').each(function () {
        var self = $(this);
        if (!self.is(':checked')) options.disabledPlugins.push(self.attr('id').substr('chkPlugin'.length));
    });

    actionKeys.forEach(function(key) {
        var id = key[0].toUpperCase() + key.substr(1);
        options[key] = parseInt($('#sel' + id).val());
    });

    options.updateNotifications = $('#chkUpdateNotifications')[0].checked;
    options.addToHistory = $('#chkAddToHistory')[0].checked;
    options.filterNSFW = $('#chkFilterNSFW')[0].checked;
    options.alwaysPreload = $('#chkAlwaysPreload')[0].checked;
    options.enableGalleries = $('#chkEnableGalleries')[0].checked;
    options.picturesOpacity = $('#txtPicturesOpacity')[0].value / 100;

    localStorage.options = JSON.stringify(options);
    sendOptions(options);
    restoreOptions();
    $('#messages').clearQueue().animate({opacity:1}, 500).delay(5000).animate({opacity:0}, 500);
    return false;
}

// Restores options from localStorage.
function restoreOptions() {
    options = loadOptions();

    $('#chkExtensionEnabled')[0].checked = options.extensionEnabled;
    $('#txtZoomFactor')[0].value = options.zoomFactor;
    $('#chkZoomVideos')[0].checked = options.zoomVideos;
    $('#txtVideoPositionStep')[0].value = options.videoPositionStep;
    $('#chkMuteVideos')[0].checked = options.muteVideos;
    $('#chkVideoTimestamp')[0].checked = options.videoTimestamp;
    $('#rngVideoVolume').val(options.videoVolume * 100);
    $('#txtVideoVolume').val(options.videoVolume * 100);
    $('#chkMouseUnderlap')[0].checked = options.mouseUnderlap;
    $('#chkPageActionEnabled')[0].checked = options.pageActionEnabled;
    $('#chkShowCaptions')[0].checked = options.showCaptions;
    $('#chkShowWhileLoading')[0].checked = options.showWhileLoading;
    $('#chkShowHighRes')[0].checked = options.showHighRes;
    $('#chkGalleriesMouseWheel')[0].checked = options.galleriesMouseWheel;
    $('#chkDisableMouseWheelForVideo')[0].checked = options.disableMouseWheelForVideo;
    $('#txtDisplayDelay').val((options.displayDelay || 0) / 1000);
    $('#txtDisplayDelayVideo').val((options.displayDelayVideo || 0) / 1000);
    $('#txtFadeDuration').val((options.fadeDuration || 0) / 1000);
    $('#chkAmbilightEnabled')[0].checked = options.ambilightEnabled;

    $('#chkWhiteListMode')[0].checked = options.whiteListMode;
    $('#selExcludedSites').empty();
    for (var i = 0; i < options.excludedSites.length; i++) {
        appendExcludedSite(options.excludedSites[i]);
    }

    actionKeys.forEach(function(key) {
        var id = key[0].toUpperCase() + key.substr(1);
        $('#sel' + id).val(options[key]);
    });

    $('#chkUpdateNotifications')[0].checked = options.updateNotifications;
    $('#chkAddToHistory')[0].checked = options.addToHistory;
    $('#chkFilterNSFW')[0].checked = options.filterNSFW;

    $('#chkAlwaysPreload')[0].checked = options.alwaysPreload;
    $('#chkEnableGalleries')[0].checked = options.enableGalleries;
    $('#txtPicturesOpacity').val(options.picturesOpacity * 100);

    $('input:checked').trigger('gumby.check');
    return false;
}

function btnAddExcludedSiteOnClick() {
    var field = $('#txtAddExcludedSite');
    var site = field.val().trim().toLowerCase().replace(/.*:\/\//, '');
    if (site) appendExcludedSite(site);
    field.val('').focus();
    return false;
}

function appendExcludedSite(site) {
    $('
 ' + site + '
').appendTo('#selExcludedSites').find('a').on('click', btnRemoveExcludedSiteOnClick);
}

function btnRemoveExcludedSiteOnClick() {
    $(this).parent().remove();
    return false;
}

function selKeyOnChange(event) {
    var currSel = $(event.target);
    if (currSel.val() != '0') {
        $('.actionKey').each(function () {
            if (!$(this).is(currSel) && $(this).val() == currSel.val()) {
                $(this).val('0').effect("highlight", {color:'red'}, 5000);
            }
        });
    }
}

function chkWhiteListModeOnChange() {
    $('#lblToggle').text(chrome.i18n.getMessage($('#chkWhiteListMode')[0].checked ? "optSectionSitesEnabled" : "optSectionSitesDisabled"));
}

function chkAddToHistoryModeOnChange() {
    if ($('#chkAddToHistory')[0].checked) {
        chrome.permissions.contains({permissions: ['history']}, function (granted) {
            if (!granted) {
                chrome.permissions.request({permissions: ['history']}, function (granted) {
                    if (!granted) {
                        $("#chkAddToHistory").trigger('gumby.uncheck');
                    }
                });
            }
        });
    }
}

function percentageOnChange() {
    var value = parseInt(this.value);
    if (isNaN(value)) value = 100;
    if (value < 1) value = 1;
    if (value > 100) value = 100;
    this.value = value;
}

function updateTxtVideoVolume() {
    $('#txtVideoVolume')[0].value = this.value;
}

function onMessage(message, sender, callback) {
    switch (message.action) {
        case 'optionsChanged':
            restoreOptions();
            break;
    }
}

function getPlugins(callback) {
    chrome.runtime.getPackageDirectoryEntry(function(root) {
        root.getDirectory("plugins", {create: false}, function(pluginsdir) {
            var reader = pluginsdir.createReader();
            var entries = [];
            var readEntries = function() {
                reader.readEntries(function(results) {
                    if (results.length) {
                        entries = entries.concat(results.map(function(de){return de.name;}));
                        readEntries();
                    } else {
                        callback(entries);
                    }
                });
            };
            readEntries();
        });
    });
}

function loadPlugins() {
    getPlugins(function(plugins) {
        plugins.forEach(function(plugin) {
            var script = document.createElement('script');
            script.type = 'text/javascript';
            script.src = '../plugins/' + plugin;
            document.body.appendChild(script);
        });
        window.setTimeout(populatePluginsTable, 500);
    });
}

function populatePluginsTable() {
    var plugins = $.unique(hoverZoomPlugins.map(function(plugin) {return plugin.name}));
    plugins.forEach(function(plugin) {
        var chkName = 'chkPlugin' + plugin.replace(/[^\w]/g, '').toLowerCase();
        $('
<input type="checkbox" id="' + chkName + '" class="chkPlugin">
' + plugin + '

').appendTo('#tblPlugins');
        $('#' + chkName)[0].checked = !options.disabledPlugins.includes(chkName.substr('chkPlugin'.length));
    });
    Gumby.initialize('checkbox');
}

$(function () {
    initActionKeys();
    i18n();
    chkWhiteListModeOnChange();
    $("#version").text(chrome.i18n.getMessage("optFooterVersionCopyright", chrome.app.getDetails().version));

    $('#btnSave').click(saveOptions);
    $('#btnReset').click(restoreOptions);
    $('#chkWhiteListMode').parent().on('gumby.onChange', chkWhiteListModeOnChange);
    $('#chkAddToHistory').parent().on('gumby.onChange', chkAddToHistoryModeOnChange);
    $('#txtZoomFactor').change(percentageOnChange);
    $('#txtPicturesOpacity').change(percentageOnChange);
    $('#rngVideoVolume').change(updateTxtVideoVolume);
    $('#txtVideoVolume').change(percentageOnChange);
    $('#txtVideoPositionStep').change(percentageOnChange);
    $('.actionKey').change(selKeyOnChange);
    $('#btnAddExcludedSite').click(btnAddExcludedSiteOnClick);
    $('#btnRemoveExcludedSite').click(btnRemoveExcludedSiteOnClick);
    $('#aShowUpdateNotification').click(showUpdateNotification);

    restoreOptions();
    loadPlugins();

    chrome.runtime.onMessage.addListener(onMessage);
});
<commit_msg>Fix range control to update while dragging<commit_after>var options,
    hoverZoomPlugins = hoverZoomPlugins || [],
    VK_CTRL = 1024,
    VK_SHIFT = 2048,
    actionKeys = ['actionKey', 'hideKey', 'openImageInWindowKey', 'openImageInTabKey', 'saveImageKey', 'fullZoomKey', 'prevImgKey', 'nextImgKey'];

function getMilliseconds(ctrl) {
    var value = parseFloat(ctrl.val());
    value = isNaN(value) ? 0 : value * 1000;
    ctrl.val(value / 1000);
    return value;
}

function keyCodeToString(key) {
    var s = '';
    if (key & VK_CTRL) {
        s += 'Ctrl+';
        key &= ~VK_CTRL;
    }
    if (key & VK_SHIFT) {
        s += 'Shift+';
        key &= ~VK_SHIFT;
    }
    if (key >= 65 && key < 91) {
        s += String.fromCharCode('A'.charCodeAt(0) + key - 65);
    } else if (key >= 112 && key < 124) {
        s += 'F' + (key - 111);
    }
    return s;
}

function initActionKeys() {
    actionKeys.forEach(function(key) {
        var id = key[0].toUpperCase() + key.substr(1);
        var title = chrome.i18n.getMessage("opt" + id + "Title");
        var description = chrome.i18n.getMessage("opt" + id + "Description");
        $('
' + title + '
' + description + '
	' +
            '<select id="sel' + id + '" class="actionKey"/>
').appendTo($('#tableActionKeys'));
        loadKeys($('#sel' + id));
    });
}

function loadKeys(sel) {
    $('<option value="0">None</option>').appendTo(sel);
    if (sel.attr('id') != 'selOpenImageInTabKey')
        $('<option value="16">Shift</option>').appendTo(sel);
    $('<option value="17">Ctrl</option>').appendTo(sel);
    if (navigator.appVersion.indexOf('Macintosh') > -1) {
        $('<option value="91">Command</option>').appendTo(sel);
    }
    for (var i = 65; i < 91; i++) {
        $('<option value="' + i + '">&#' + i + ';</option>').appendTo(sel);
    }
    for (var i = 112; i < 124; i++) {
        $('<option value="' + i + '">F' + (i - 111) + '</option>').appendTo(sel);
    }
    $('<option value="33">Page Up</option>').appendTo(sel);
    $('<option value="34">Page Down</option>').appendTo(sel);
    $('<option value="35">End</option>').appendTo(sel);
    $('<option value="36">Home</option>').appendTo(sel);
    $('<option value="37">Left</option>').appendTo(sel);
    $('<option value="38">Up</option>').appendTo(sel);
    $('<option value="39">Right</option>').appendTo(sel);
    $('<option value="40">Down</option>').appendTo(sel);
    $('<option value="45">Insert</option>').appendTo(sel);
    $('<option value="46">Delete</option>').appendTo(sel);
}

// Saves options to localStorage.
// TODO: Migrate to https://developer.chrome.com/extensions/storage
function saveOptions() {
    options.extensionEnabled = $('#chkExtensionEnabled')[0].checked;
    options.zoomFactor = $('#txtZoomFactor')[0].value;
    options.zoomVideos = $('#chkZoomVideos')[0].checked;
    options.videoPositionStep = $('#txtVideoPositionStep')[0].value;
    options.muteVideos = $('#chkMuteVideos')[0].checked;
    options.videoTimestamp = $('#chkVideoTimestamp')[0].checked;
    options.videoVolume = $('#txtVideoVolume')[0].value / 100;
    options.mouseUnderlap = $('#chkMouseUnderlap')[0].checked;
    options.pageActionEnabled = $('#chkPageActionEnabled')[0].checked;
    options.showCaptions = $('#chkShowCaptions')[0].checked;
    options.showWhileLoading = $('#chkShowWhileLoading')[0].checked;
    options.showHighRes = $('#chkShowHighRes')[0].checked;
    options.galleriesMouseWheel = $('#chkGalleriesMouseWheel')[0].checked;
    options.disableMouseWheelForVideo = $('#chkDisableMouseWheelForVideo')[0].checked;
    options.displayDelay = getMilliseconds($('#txtDisplayDelay'));
    options.displayDelayVideo = getMilliseconds($('#txtDisplayDelayVideo'));
    options.fadeDuration = getMilliseconds($('#txtFadeDuration'));
    options.ambilightEnabled = $('#chkAmbilightEnabled')[0].checked;

    options.whiteListMode = $('#chkWhiteListMode')[0].checked;
    options.excludedSites = [];
    $('#selExcludedSites').find('span').each(function () {
        options.excludedSites.push($(this).text());
    });

    options.disabledPlugins = [];
    $('.chkPlugin').each(function () {
        var self = $(this);
        if (!self.is(':checked')) options.disabledPlugins.push(self.attr('id').substr('chkPlugin'.length));
    });

    actionKeys.forEach(function(key) {
        var id = key[0].toUpperCase() + key.substr(1);
        options[key] = parseInt($('#sel' + id).val());
    });

    options.updateNotifications = $('#chkUpdateNotifications')[0].checked;
    options.addToHistory = $('#chkAddToHistory')[0].checked;
    options.filterNSFW = $('#chkFilterNSFW')[0].checked;
    options.alwaysPreload = $('#chkAlwaysPreload')[0].checked;
    options.enableGalleries = $('#chkEnableGalleries')[0].checked;
    options.picturesOpacity = $('#txtPicturesOpacity')[0].value / 100;

    localStorage.options = JSON.stringify(options);
    sendOptions(options);
    restoreOptions();
    $('#messages').clearQueue().animate({opacity:1}, 500).delay(5000).animate({opacity:0}, 500);
    return false;
}

// Restores options from localStorage.
function restoreOptions() {
    options = loadOptions();

    $('#chkExtensionEnabled')[0].checked = options.extensionEnabled;
    $('#txtZoomFactor')[0].value = options.zoomFactor;
    $('#chkZoomVideos')[0].checked = options.zoomVideos;
    $('#txtVideoPositionStep')[0].value = options.videoPositionStep;
    $('#chkMuteVideos')[0].checked = options.muteVideos;
    $('#chkVideoTimestamp')[0].checked = options.videoTimestamp;
    $('#rngVideoVolume').val(options.videoVolume * 100);
    $('#txtVideoVolume').val(options.videoVolume * 100);
    $('#chkMouseUnderlap')[0].checked = options.mouseUnderlap;
    $('#chkPageActionEnabled')[0].checked = options.pageActionEnabled;
    $('#chkShowCaptions')[0].checked = options.showCaptions;
    $('#chkShowWhileLoading')[0].checked = options.showWhileLoading;
    $('#chkShowHighRes')[0].checked = options.showHighRes;
    $('#chkGalleriesMouseWheel')[0].checked = options.galleriesMouseWheel;
    $('#chkDisableMouseWheelForVideo')[0].checked = options.disableMouseWheelForVideo;
    $('#txtDisplayDelay').val((options.displayDelay || 0) / 1000);
    $('#txtDisplayDelayVideo').val((options.displayDelayVideo || 0) / 1000);
    $('#txtFadeDuration').val((options.fadeDuration || 0) / 1000);
    $('#chkAmbilightEnabled')[0].checked = options.ambilightEnabled;

    $('#chkWhiteListMode')[0].checked = options.whiteListMode;
    $('#selExcludedSites').empty();
    for (var i = 0; i < options.excludedSites.length; i++) {
        appendExcludedSite(options.excludedSites[i]);
    }

    actionKeys.forEach(function(key) {
        var id = key[0].toUpperCase() + key.substr(1);
        $('#sel' + id).val(options[key]);
    });

    $('#chkUpdateNotifications')[0].checked = options.updateNotifications;
    $('#chkAddToHistory')[0].checked = options.addToHistory;
    $('#chkFilterNSFW')[0].checked = options.filterNSFW;

    $('#chkAlwaysPreload')[0].checked = options.alwaysPreload;
    $('#chkEnableGalleries')[0].checked = options.enableGalleries;
    $('#txtPicturesOpacity').val(options.picturesOpacity * 100);

    $('input:checked').trigger('gumby.check');
    return false;
}

function btnAddExcludedSiteOnClick() {
    var field = $('#txtAddExcludedSite');
    var site = field.val().trim().toLowerCase().replace(/.*:\/\//, '');
    if (site) appendExcludedSite(site);
    field.val('').focus();
    return false;
}

function appendExcludedSite(site) {
    $('
 ' + site + '
').appendTo('#selExcludedSites').find('a').on('click', btnRemoveExcludedSiteOnClick);
}

function btnRemoveExcludedSiteOnClick() {
    $(this).parent().remove();
    return false;
}

function selKeyOnChange(event) {
    var currSel = $(event.target);
    if (currSel.val() != '0') {
        $('.actionKey').each(function () {
            if (!$(this).is(currSel) && $(this).val() == currSel.val()) {
                $(this).val('0').effect("highlight", {color:'red'}, 5000);
            }
        });
    }
}

function chkWhiteListModeOnChange() {
    $('#lblToggle').text(chrome.i18n.getMessage($('#chkWhiteListMode')[0].checked ? "optSectionSitesEnabled" : "optSectionSitesDisabled"));
}

function chkAddToHistoryModeOnChange() {
    if ($('#chkAddToHistory')[0].checked) {
        chrome.permissions.contains({permissions: ['history']}, function (granted) {
            if (!granted) {
                chrome.permissions.request({permissions: ['history']}, function (granted) {
                    if (!granted) {
                        $("#chkAddToHistory").trigger('gumby.uncheck');
                    }
                });
            }
        });
    }
}

function percentageOnChange() {
    var value = parseInt(this.value);
    if (isNaN(value)) value = 100;
    if (value < 1) value = 1;
    if (value > 100) value = 100;
    this.value = value;
}

function updateTxtVideoVolume() {
    $('#txtVideoVolume')[0].value = this.value;
}

function onMessage(message, sender, callback) {
    switch (message.action) {
        case 'optionsChanged':
            restoreOptions();
            break;
    }
}

function getPlugins(callback) {
    chrome.runtime.getPackageDirectoryEntry(function(root) {
        root.getDirectory("plugins", {create: false}, function(pluginsdir) {
            var reader = pluginsdir.createReader();
            var entries = [];
            var readEntries = function() {
                reader.readEntries(function(results) {
                    if (results.length) {
                        entries = entries.concat(results.map(function(de){return de.name;}));
                        readEntries();
                    } else {
                        callback(entries);
                    }
                });
            };
            readEntries();
        });
    });
}

function loadPlugins() {
    getPlugins(function(plugins) {
        plugins.forEach(function(plugin) {
            var script = document.createElement('script');
            script.type = 'text/javascript';
            script.src = '../plugins/' + plugin;
            document.body.appendChild(script);
        });
        window.setTimeout(populatePluginsTable, 500);
    });
}

function populatePluginsTable() {
    var plugins = $.unique(hoverZoomPlugins.map(function(plugin) {return plugin.name}));
    plugins.forEach(function(plugin) {
        var chkName = 'chkPlugin' + plugin.replace(/[^\w]/g, '').toLowerCase();
        $('
<input type="checkbox" id="' + chkName + '" class="chkPlugin">
' + plugin + '

').appendTo('#tblPlugins');
        $('#' + chkName)[0].checked = !options.disabledPlugins.includes(chkName.substr('chkPlugin'.length));
    });
    Gumby.initialize('checkbox');
}

$(function () {
    initActionKeys();
    i18n();
    chkWhiteListModeOnChange();
    $("#version").text(chrome.i18n.getMessage("optFooterVersionCopyright", chrome.app.getDetails().version));

    $('#btnSave').click(saveOptions);
    $('#btnReset').click(restoreOptions);
    $('#chkWhiteListMode').parent().on('gumby.onChange', chkWhiteListModeOnChange);
    $('#chkAddToHistory').parent().on('gumby.onChange', chkAddToHistoryModeOnChange);
    $('#txtZoomFactor').change(percentageOnChange);
    $('#txtPicturesOpacity').change(percentageOnChange);
    $('#rngVideoVolume').on('input change', updateTxtVideoVolume);
    $('#txtVideoVolume').change(percentageOnChange);
    $('#txtVideoPositionStep').change(percentageOnChange);
    $('.actionKey').change(selKeyOnChange);
    $('#btnAddExcludedSite').click(btnAddExcludedSiteOnClick);
    $('#btnRemoveExcludedSite').click(btnRemoveExcludedSiteOnClick);
    $('#aShowUpdateNotification').click(showUpdateNotification);

    restoreOptions();
    loadPlugins();

    chrome.runtime.onMessage.addListener(onMessage);
});
<|endoftext|>


<commit_before>// Saves options (synced)
function saveOptions() {
  // get base URL
  event.preventDefault();
  var jira = document.getElementById('jira-url').value;
  var defaultOption = document.getElementById('default-option').value;
  chrome.storage.sync.set({
    jiraURL: jira,
    defaultOption: defaultOption
  }, function() {
    // Notify user
    var status = document.getElementById('status');
    status.textContent = 'Options were saved!';
    // remove after 500ms
    window.setTimeout(window.close, 500);
  });
}

// Restore the JIRA base url
function restoreOptions() {
  // fallback to empty string
  chrome.storage.sync.get({
    jiraURL: '',
    defaultOption: 'current tab'
  }, function(options) {
    // set value
    document.getElementById('jira-url').value = options.jiraURL;
    document.getElementById('default-option').value = options.defaultOption;
  });
}

// Load options on DOMContentLoad
document.addEventListener('DOMContentLoaded', restoreOptions);
// Save options when button is clicked
window.addEventListener('load', function(evt) {
  document.getElementById('options').addEventListener('submit', saveOptions);
});
<commit_msg>added URL validation<commit_after>// Saves options (synced)
function saveOptions() {
  // get base URL
  event.preventDefault();
  var status = document.getElementById('status');
  var urlPattern = new RegExp("(http|https)://[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?");
  var jira = document.getElementById('jira-url').value;
  if (!urlPattern.test(jira)) {
    status.textContent = 'Please specify a valid URL!';
  } else  {
    var defaultOption = document.getElementById('default-option').value;
    chrome.storage.sync.set({
      jiraURL: jira,
      defaultOption: defaultOption
    }, function() {
      // Notify user
      status.textContent = 'Options were saved!';
      // remove after 500ms
      window.setTimeout(window.close, 500);
    });
  }
}

// Restore the JIRA base url
function restoreOptions() {
  // fallback to empty string
  chrome.storage.sync.get({
    jiraURL: '',
    defaultOption: 'current tab'
  }, function(options) {
    // set value
    document.getElementById('jira-url').value = options.jiraURL;
    document.getElementById('default-option').value = options.defaultOption;
  });
}

// Load options on DOMContentLoad
document.addEventListener('DOMContentLoaded', restoreOptions);
// Save options when button is clicked
window.addEventListener('load', function(evt) {
  document.getElementById('options').addEventListener('submit', saveOptions);
});
<|endoftext|>


<commit_before>/*global _, window, document, jQuery, Kinetic*/
var MAPJS = MAPJS || {};
MAPJS.KineticMediator = function (mapModel, stage) {
	'use strict';
	var layer = new Kinetic.Layer(),
		nodeByIdeaId = {},
		connectorByFromIdeaId_ToIdeaId = {},
		connectorKey = function (fromIdeaId, toIdeaId) {
			return fromIdeaId + '_' + toIdeaId;
		},
		moveStage = function (deltaX, deltaY) {
			if (stage) {
				if (deltaY !== 0) { stage.attrs.y += deltaY; }
				if (deltaX !== 0) { stage.attrs.x += deltaX; }
				stage.draw();
			}
		},
		getTargetShape = function (evt) {
			var is;
			if (evt.offsetX && evt.offsetY) {
				is = stage.getIntersection({x: evt.offsetX, y: evt.offsetY});
				return is && is.shape;
			}
			return false;
		};
	stage.add(layer);
	jQuery(stage.getContainer()).on('dblclick', function (evt) { stage.simulate('dblclick', evt); });
	stage.on('dbltap dblclick', function (evt) {
		if (!getTargetShape(evt)) {
			stage.transitionTo({
				x: 0.5 * stage.getWidth(),
				y: 0.5 * stage.getHeight(),
				scale: {
					x: 1,
					y: 1
				},
				duration: 0.5,
				easing: 'ease-in-out'
			});
		}
	});
	mapModel.addEventListener('nodeCreated', function (n) {
		var node = new Kinetic.Idea({
			level: n.level,
			x: n.x,
			y: n.y,
			text: n.title,
			mmStyle: n.style,
			opacity: 0
		});
		/* in kinetic 4.3 cannot use click because click if fired on dragend */
		node.on('click tap', mapModel.selectNode.bind(mapModel, n.id));
		node.on('dragstart', function () {
			node.moveToTop();
			node.attrs.shadow.offset = {
				x: 8,
				y: 8
			};
		});
		node.on('dragmove', function () {
			mapModel.nodeDragMove(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
		});
		node.on('dragend', function () {
			node.attrs.shadow.offset = {
				x: 4,
				y: 4
			};
			mapModel.nodeDragEnd(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
		});
		node.on(':textChanged', function (event) {
			mapModel.updateTitle(n.id, event.text);
			mapModel.dispatchEvent('inputEnabledChanged', true);
		});
		node.on(':editing', function (event) {
			mapModel.dispatchEvent('inputEnabledChanged', false);
		});
		node.on(':nodeEditRequested', mapModel.editNode.bind(mapModel, 'mouse', false));

		mapModel.addEventListener('nodeEditRequested:' + n.id, node.editNode);
		nodeByIdeaId[n.id] = node;
		layer.add(node);
		node.transitionToAndDontStopCurrentTransitions({
			opacity: 1,
			duration: 0.4
		});
	});
	mapModel.addEventListener('nodeSelectionChanged', function (ideaId, isSelected) {
		var node = nodeByIdeaId[ideaId],
			scale = stage.getScale().x || 1,
			offset = 100,
			move = { x: 0, y: 0 };
		node.setIsSelected(isSelected);
		if (!isSelected) {
			return;
		}
		if (node.getAbsolutePosition().x + node.getWidth() * scale + offset > stage.getWidth()) {
			move.x = stage.getWidth() - (node.getAbsolutePosition().x + node.getWidth() * scale + offset);
		} else if (node.getAbsolutePosition().x < offset) {
			move.x  = offset - node.getAbsolutePosition().x;
		}
		if (node.getAbsolutePosition().y + node.getHeight() * scale + offset > stage.getHeight()) {
			move.y = stage.getHeight() - (node.getAbsolutePosition().y + node.getHeight() * scale + offset);
		} else if (node.getAbsolutePosition().y < offset) {
			move.y = offset - node.getAbsolutePosition().y;
		}
		stage.transitionTo({
			x: stage.attrs.x + move.x,
			y: stage.attrs.y + move.y,
			duration: 0.4,
			easing: 'ease-in-out'
		});
	});
	mapModel.addEventListener('nodeStyleChanged', function (n) {
		var node = nodeByIdeaId[n.id];
		node.setMMStyle(n.style);
	});
	mapModel.addEventListener('nodeDroppableChanged', function (ideaId, isDroppable) {
		var node = nodeByIdeaId[ideaId];
		node.setIsDroppable(isDroppable);
	});
	mapModel.addEventListener('nodeRemoved', function (n) {
		var node = nodeByIdeaId[n.id];
		delete nodeByIdeaId[n.id];
		node.transitionTo({
			opacity: 0.25,
			duration: 0.4,
			callback: node.remove.bind(node)
		});
		node.off('click dblclick tap dragstart dragmove dragend mouseover mouseout :textChanged :nodeEditRequested');
		mapModel.removeEventListener('nodeEditRequested:' + n.id, node.editNode);
	});
	mapModel.addEventListener('nodeMoved', function (n, reason) {
		var node = nodeByIdeaId[n.id];
		node.transitionTo({
			x: n.x,
			y: n.y,
			duration: 0.4,
			easing: reason === 'failed' ? 'bounce-ease-out' : 'ease-in-out'
		});
	});
	mapModel.addEventListener('nodeTitleChanged', function (n) {
		var node = nodeByIdeaId[n.id];
		node.setText(n.title);
		layer.draw();
	});
	mapModel.addEventListener('connectorCreated', function (n) {
		var connector = new Kinetic.Connector({
			shapeFrom: nodeByIdeaId[n.from],
			shapeTo: nodeByIdeaId[n.to],
			stroke: '#888',
			strokeWidth: 1,
			opacity: 0
		});
		connector.opacity = 0;
		connectorByFromIdeaId_ToIdeaId[connectorKey(n.from, n.to)] = connector;
		layer.add(connector);
		connector.moveToBottom();
		connector.transitionTo({
			opacity: 1,
			duration: 0.4
		});
	});
	mapModel.addEventListener('connectorRemoved', function (n) {
		var key = connectorKey(n.from, n.to),
			connector = connectorByFromIdeaId_ToIdeaId[key];
		delete connectorByFromIdeaId_ToIdeaId[key];
		connector.transitionTo({
			opacity: 0,
			duration: 0.1,
			callback: connector.remove.bind(connector)
		});
	});
	mapModel.addEventListener('mapScaleChanged', function (scaleMultiplier) {
		var scale = (stage.getScale().x || 1) * scaleMultiplier;
		stage.transitionTo({
			scale: {
				x: scale,
				y: scale
			},
			duration: 0.1
		});
	});
	mapModel.addEventListener('mapMoveRequested', function (deltaX, deltaY) {
		moveStage(deltaX, deltaY);
	});

	(function () {
		var keyboardEventHandlers = {
			13: mapModel.addSiblingIdea.bind(mapModel, 'keyboard'),
			8: mapModel.removeSubIdea.bind(mapModel, 'keyboard'),
			9: mapModel.addSubIdea.bind(mapModel, 'keyboard'),
			37: mapModel.selectNodeLeft.bind(mapModel, 'keyboard'),
			38: mapModel.selectNodeUp.bind(mapModel, 'keyboard'),
			39: mapModel.selectNodeRight.bind(mapModel, 'keyboard'),
			40: mapModel.selectNodeDown.bind(mapModel, 'keyboard'),
			46: mapModel.removeSubIdea.bind(mapModel, 'keyboard'),
			32: mapModel.editNode.bind(mapModel, 'keyboard'),
			191: mapModel.toggleCollapse.bind(mapModel, 'keyboard')
		}, shiftKeyboardEventHandlers = {
			9: mapModel.insertIntermediate.bind(mapModel, 'keyboard'),
			38: mapModel.toggleCollapse.bind(mapModel, 'keyboard'),
		}, metaKeyboardEventHandlers = {
			90: mapModel.undo.bind(mapModel, 'keyboard'),
			89: mapModel.redo.bind(mapModel, 'keyboard'),
			187: mapModel.scaleUp.bind(mapModel, 'keyboard'),
			189: mapModel.scaleDown.bind(mapModel, 'keyboard')
		},
			onKeydown = function (evt) {
				var eventHandler = ((evt.metaKey || evt.ctrlKey) ? metaKeyboardEventHandlers :
						(evt.shiftKey ? shiftKeyboardEventHandlers : keyboardEventHandlers))[evt.which];
				if (eventHandler) {
					eventHandler();
					evt.preventDefault();
				}
			},
			onScroll = function (event, delta, deltaX, deltaY) {
				moveStage(deltaX, deltaY);
				if (deltaX < 0) { /* stop the back button */
					event.preventDefault();
				}
				if (deltaY < 0) { /*stop scrolling down */
					event.preventDefault();
				}
			};
		jQuery(document).keydown(onKeydown);
		jQuery(window).mousewheel(onScroll);
		mapModel.addEventListener('inputEnabledChanged', function (isInputEnabled) {
			jQuery(document)[isInputEnabled ? 'bind' : 'unbind']('keydown', onKeydown);
			jQuery(window)[isInputEnabled ? 'mousewheel' : 'unmousewheel'](onScroll);
		});

	}());
};
MAPJS.KineticMediator.dimensionProvider = _.memoize(function (title) {
	'use strict';
	var text = new Kinetic.Idea({
		text: title
	});
	return {
		width: text.getWidth(),
		height: text.getHeight()
	};
});
MAPJS.KineticMediator.layoutCalculator = function (idea) {
	'use strict';
	return MAPJS.calculateLayout(idea, MAPJS.KineticMediator.dimensionProvider);
};
<commit_msg>cmd+0 to reset stage<commit_after>/*global _, window, document, jQuery, Kinetic*/
var MAPJS = MAPJS || {};
MAPJS.KineticMediator = function (mapModel, stage) {
	'use strict';
	var layer = new Kinetic.Layer(),
		nodeByIdeaId = {},
		connectorByFromIdeaId_ToIdeaId = {},
		connectorKey = function (fromIdeaId, toIdeaId) {
			return fromIdeaId + '_' + toIdeaId;
		},
		moveStage = function (deltaX, deltaY) {
			if (stage) {
				if (deltaY !== 0) { stage.attrs.y += deltaY; }
				if (deltaX !== 0) { stage.attrs.x += deltaX; }
				stage.draw();
			}
		},
		getTargetShape = function (evt) {
			var is;
			if (evt.offsetX && evt.offsetY) {
				is = stage.getIntersection({x: evt.offsetX, y: evt.offsetY});
				return is && is.shape;
			}
			return false;
		},
		resetStage = function (evt) {
			stage.transitionTo({
				x: 0.5 * stage.getWidth(),
				y: 0.5 * stage.getHeight(),
				scale: {
					x: 1,
					y: 1
				},
				duration: 0.5,
				easing: 'ease-in-out'
			});
		};
	stage.add(layer);
	jQuery(stage.getContainer()).on('dblclick', function (evt) { stage.simulate('dblclick', evt); });
	stage.on('dbltap dblclick', function (evt) {
		if (!getTargetShape(evt)) {
			resetStage();
		}
	});
	mapModel.addEventListener('nodeCreated', function (n) {
		var node = new Kinetic.Idea({
			level: n.level,
			x: n.x,
			y: n.y,
			text: n.title,
			mmStyle: n.style,
			opacity: 0
		});
		/* in kinetic 4.3 cannot use click because click if fired on dragend */
		node.on('click tap', mapModel.selectNode.bind(mapModel, n.id));
		node.on('dragstart', function () {
			node.moveToTop();
			node.attrs.shadow.offset = {
				x: 8,
				y: 8
			};
		});
		node.on('dragmove', function () {
			mapModel.nodeDragMove(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
		});
		node.on('dragend', function () {
			node.attrs.shadow.offset = {
				x: 4,
				y: 4
			};
			mapModel.nodeDragEnd(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
		});
		node.on(':textChanged', function (event) {
			mapModel.updateTitle(n.id, event.text);
			mapModel.dispatchEvent('inputEnabledChanged', true);
		});
		node.on(':editing', function (event) {
			mapModel.dispatchEvent('inputEnabledChanged', false);
		});
		node.on(':nodeEditRequested', mapModel.editNode.bind(mapModel, 'mouse', false));

		mapModel.addEventListener('nodeEditRequested:' + n.id, node.editNode);
		nodeByIdeaId[n.id] = node;
		layer.add(node);
		node.transitionToAndDontStopCurrentTransitions({
			opacity: 1,
			duration: 0.4
		});
	});
	mapModel.addEventListener('nodeSelectionChanged', function (ideaId, isSelected) {
		var node = nodeByIdeaId[ideaId],
			scale = stage.getScale().x || 1,
			offset = 100,
			move = { x: 0, y: 0 };
		node.setIsSelected(isSelected);
		if (!isSelected) {
			return;
		}
		if (node.getAbsolutePosition().x + node.getWidth() * scale + offset > stage.getWidth()) {
			move.x = stage.getWidth() - (node.getAbsolutePosition().x + node.getWidth() * scale + offset);
		} else if (node.getAbsolutePosition().x < offset) {
			move.x  = offset - node.getAbsolutePosition().x;
		}
		if (node.getAbsolutePosition().y + node.getHeight() * scale + offset > stage.getHeight()) {
			move.y = stage.getHeight() - (node.getAbsolutePosition().y + node.getHeight() * scale + offset);
		} else if (node.getAbsolutePosition().y < offset) {
			move.y = offset - node.getAbsolutePosition().y;
		}
		stage.transitionTo({
			x: stage.attrs.x + move.x,
			y: stage.attrs.y + move.y,
			duration: 0.4,
			easing: 'ease-in-out'
		});
	});
	mapModel.addEventListener('nodeStyleChanged', function (n) {
		var node = nodeByIdeaId[n.id];
		node.setMMStyle(n.style);
	});
	mapModel.addEventListener('nodeDroppableChanged', function (ideaId, isDroppable) {
		var node = nodeByIdeaId[ideaId];
		node.setIsDroppable(isDroppable);
	});
	mapModel.addEventListener('nodeRemoved', function (n) {
		var node = nodeByIdeaId[n.id];
		delete nodeByIdeaId[n.id];
		node.transitionTo({
			opacity: 0.25,
			duration: 0.4,
			callback: node.remove.bind(node)
		});
		node.off('click dblclick tap dragstart dragmove dragend mouseover mouseout :textChanged :nodeEditRequested');
		mapModel.removeEventListener('nodeEditRequested:' + n.id, node.editNode);
	});
	mapModel.addEventListener('nodeMoved', function (n, reason) {
		var node = nodeByIdeaId[n.id];
		node.transitionTo({
			x: n.x,
			y: n.y,
			duration: 0.4,
			easing: reason === 'failed' ? 'bounce-ease-out' : 'ease-in-out'
		});
	});
	mapModel.addEventListener('nodeTitleChanged', function (n) {
		var node = nodeByIdeaId[n.id];
		node.setText(n.title);
		layer.draw();
	});
	mapModel.addEventListener('connectorCreated', function (n) {
		var connector = new Kinetic.Connector({
			shapeFrom: nodeByIdeaId[n.from],
			shapeTo: nodeByIdeaId[n.to],
			stroke: '#888',
			strokeWidth: 1,
			opacity: 0
		});
		connector.opacity = 0;
		connectorByFromIdeaId_ToIdeaId[connectorKey(n.from, n.to)] = connector;
		layer.add(connector);
		connector.moveToBottom();
		connector.transitionTo({
			opacity: 1,
			duration: 0.4
		});
	});
	mapModel.addEventListener('connectorRemoved', function (n) {
		var key = connectorKey(n.from, n.to),
			connector = connectorByFromIdeaId_ToIdeaId[key];
		delete connectorByFromIdeaId_ToIdeaId[key];
		connector.transitionTo({
			opacity: 0,
			duration: 0.1,
			callback: connector.remove.bind(connector)
		});
	});
	mapModel.addEventListener('mapScaleChanged', function (scaleMultiplier) {
		var scale = (stage.getScale().x || 1) * scaleMultiplier;
		stage.transitionTo({
			scale: {
				x: scale,
				y: scale
			},
			duration: 0.1
		});
	});
	mapModel.addEventListener('mapMoveRequested', function (deltaX, deltaY) {
		moveStage(deltaX, deltaY);
	});

	(function () {
		var keyboardEventHandlers = {
			13: mapModel.addSiblingIdea.bind(mapModel, 'keyboard'),
			8: mapModel.removeSubIdea.bind(mapModel, 'keyboard'),
			9: mapModel.addSubIdea.bind(mapModel, 'keyboard'),
			37: mapModel.selectNodeLeft.bind(mapModel, 'keyboard'),
			38: mapModel.selectNodeUp.bind(mapModel, 'keyboard'),
			39: mapModel.selectNodeRight.bind(mapModel, 'keyboard'),
			40: mapModel.selectNodeDown.bind(mapModel, 'keyboard'),
			46: mapModel.removeSubIdea.bind(mapModel, 'keyboard'),
			32: mapModel.editNode.bind(mapModel, 'keyboard'),
			191: mapModel.toggleCollapse.bind(mapModel, 'keyboard')
		}, shiftKeyboardEventHandlers = {
			9: mapModel.insertIntermediate.bind(mapModel, 'keyboard'),
			38: mapModel.toggleCollapse.bind(mapModel, 'keyboard'),
		}, metaKeyboardEventHandlers = {
			48: resetStage,
			90: mapModel.undo.bind(mapModel, 'keyboard'),
			89: mapModel.redo.bind(mapModel, 'keyboard'),
			187: mapModel.scaleUp.bind(mapModel, 'keyboard'),
			189: mapModel.scaleDown.bind(mapModel, 'keyboard')
		},
			onKeydown = function (evt) {
				var eventHandler = ((evt.metaKey || evt.ctrlKey) ? metaKeyboardEventHandlers :
						(evt.shiftKey ? shiftKeyboardEventHandlers : keyboardEventHandlers))[evt.which];
				if (eventHandler) {
					eventHandler();
					evt.preventDefault();
				}
			},
			onScroll = function (event, delta, deltaX, deltaY) {
				moveStage(deltaX, deltaY);
				if (deltaX < 0) { /* stop the back button */
					event.preventDefault();
				}
				if (deltaY < 0) { /*stop scrolling down */
					event.preventDefault();
				}
			};
		jQuery(document).keydown(onKeydown);
		jQuery(window).mousewheel(onScroll);
		mapModel.addEventListener('inputEnabledChanged', function (isInputEnabled) {
			jQuery(document)[isInputEnabled ? 'bind' : 'unbind']('keydown', onKeydown);
			jQuery(window)[isInputEnabled ? 'mousewheel' : 'unmousewheel'](onScroll);
		});

	}());
};
MAPJS.KineticMediator.dimensionProvider = _.memoize(function (title) {
	'use strict';
	var text = new Kinetic.Idea({
		text: title
	});
	return {
		width: text.getWidth(),
		height: text.getHeight()
	};
});
MAPJS.KineticMediator.layoutCalculator = function (idea) {
	'use strict';
	return MAPJS.calculateLayout(idea, MAPJS.KineticMediator.dimensionProvider);
};
<|endoftext|>


<commit_before>/* Base Pattern
 */

define([
  'jquery',
  'pat-registry',
  'mockup-parser',
  "pat-logger"
], function($, Registry, mockupParser, logger) {
  'use strict';

  var initMockup = function initMockup($el, options, trigger) {
    var name = this.prototype.name;
    var log = logger.getLogger("pat." + name);
    var pattern = $el.data('pattern-' + name);
    if (pattern === undefined && Registry.patterns[name]) {
      try {
          pattern = new Registry.patterns[name]($el, mockupParser.getOptions($el, name, options));
      } catch (e) {
          log.error('Failed while initializing "' + name + '" pattern.');
      }
      $el.data('pattern-' + name, pattern);
    }
    return pattern;
  };

  // Base Pattern
  var Base = function($el, options) {
    this.$el = $el;
    this.options = $.extend(true, {}, this.defaults || {}, options || {});
    this.init();
    this.emit('init');
  };

  Base.prototype = {
    is_mockup_pattern: true,
    constructor: Base,
    on: function(eventName, eventCallback) {
      this.$el.on(eventName + '.' + this.name + '.patterns', eventCallback);
    },

    emit: function(eventName, args) {
      // args should be a list
      if (args === undefined) {
        args = [];
      }
      this.$el.trigger(eventName + '.' + this.name + '.patterns', args);
    }
  };

  Base.extend = function(NewPattern) {
    var Base = this, Constructor;
    if (NewPattern && NewPattern.hasOwnProperty('constructor')) {
      Constructor = NewPattern.constructor;
    } else {
      Constructor = function() { Base.apply(this, arguments); };  // TODO: arguments from where
    }
    var Surrogate = function() { this.constructor = Constructor; };
    Surrogate.prototype = Base.prototype;
    Constructor.prototype = new Surrogate();
    Constructor.extend = Base.extend;
    Constructor.jquery_plugin = true;
    Constructor.trigger = NewPattern.trigger;
    Constructor.init = initMockup;
    $.extend(true, Constructor.prototype, NewPattern);
    Constructor.__super__ = Base.prototype;  // TODO: needed?
    Registry.register(Constructor, NewPattern.name);
    return Constructor;
  };

  return Base;
});
<commit_msg>Provide comments and rename variables in the "extend" method<commit_after>/* Base Pattern
 */

define([
  'jquery',
  'pat-registry',
  'mockup-parser',
  "pat-logger"
], function($, Registry, mockupParser, logger) {
  'use strict';

  var initMockup = function initMockup($el, options, trigger) {
    var name = this.prototype.name;
    var log = logger.getLogger("pat." + name);
    var pattern = $el.data('pattern-' + name);
    if (pattern === undefined && Registry.patterns[name]) {
      try {
          pattern = new Registry.patterns[name]($el, mockupParser.getOptions($el, name, options));
      } catch (e) {
          log.error('Failed while initializing "' + name + '" pattern.');
      }
      $el.data('pattern-' + name, pattern);
    }
    return pattern;
  };

  // Base Pattern
  var Base = function($el, options) {
    this.$el = $el;
    this.options = $.extend(true, {}, this.defaults || {}, options || {});
    this.init();
    this.emit('init');
  };

  Base.prototype = {
    constructor: Base,
    on: function(eventName, eventCallback) {
      this.$el.on(eventName + '.' + this.name + '.patterns', eventCallback);
    },
    emit: function(eventName, args) {
      // args should be a list
      if (args === undefined) {
        args = [];
      }
      this.$el.trigger(eventName + '.' + this.name + '.patterns', args);
    }
  };

  Base.extend = function(patternProps) {
    /* Helper function to correctly set up the prototype chain for new patterns.
     */
    var parent = this;
    var child;

    // Check that the required configuration properties are given.
    if (!patternProps) {
      throw new Error("Pattern configuration properties required when calling Base.extend");
    }
    if (!patternProps.name) {
      throw new Error("A Mockup pattern must have a name attribute");
    }
    if (!patternProps.trigger) {
      throw new Error("The Mockup pattern '"+patternProps.name+"' must have a trigger attribute");
    }

    // The constructor function for the new subclass is either defined by you
    // (the "constructor" property in your `extend` definition), or defaulted
    // by us to simply call the parent's constructor.
    if (patternProps.hasOwnProperty('constructor')) {
      child = patternProps.constructor;
    } else {
      child = function() { parent.apply(this, arguments); };
    }

    // Allow patterns to be extended indefinitely
    child.extend = Base.extend;

    // Static properties required by the Patternslib registry
    child.init = initMockup;
    child.jquery_plugin = true;
    child.trigger = patternProps.trigger;

    // Set the prototype chain to inherit from `parent`, without calling
    // `parent`'s constructor function.
    var Surrogate = function() { this.constructor = child; };
    Surrogate.prototype = parent.prototype;
    child.prototype = new Surrogate();

    // Add pattern's configuration properties (instance properties) to the subclass,
    $.extend(true, child.prototype, patternProps);

    // Set a convenience property in case the parent's prototype is needed
    // later.
    child.__super__ = parent.prototype;

    // Register the pattern in the Patternslib registry.
    Registry.register(child, patternProps.name);
    return child;
  };

  return Base;
});
<|endoftext|>


<commit_before>var seaport = require('../');
var test = require('tap').test;

test('resubscribe if connection dies', function(t) {
    var serverPort = Math.floor(Math.random() * 5e4 + 1e4),
        server = seaport.createServer(),
        client = seaport.connect('localhost:' + serverPort, {reconnect: 10}),
        allocatedPort = 1234;

    t.plan(6);

    var eventNames = ['allocate', 'assume', 'free'];

    eventNames.forEach(function(eventName) {
        client.subscribe(eventName, function() {
            t.ok(true, eventName + ' emitted');
        });
    });

    function emit() {
        eventNames.forEach(function(eventName) {
            server.emit(eventName, {});
        });
    }

    server.listen(serverPort);

    // Wait for client to connect
    setTimeout(emit, 200)

    // Wait for first tests to finish
    setTimeout(function() {
        client.up.conn.stream.end();
    }, 400);

    // Run second test
    setTimeout(emit, 1600);

    t.on('end', function() {
        server.close();
        client.close();
        t.ok(true, 'closed server');
    });
});
<commit_msg>failing test when subscription is delayed<commit_after>var seaport = require('../');
var test = require('tap').test;

test('resubscribe if connection dies', function(t) {
    var serverPort = Math.floor(Math.random() * 5e4 + 1e4),
        server = seaport.createServer(),
        client = seaport.connect('localhost:' + serverPort, {reconnect: 10}),
        allocatedPort = 1234;

    t.plan(6);

    var eventNames = ['allocate', 'assume', 'free'];

    eventNames.forEach(function(eventName) {
        client.subscribe(eventName, function() {
            t.ok(true, eventName + ' emitted');
        });
    });

    function emit() {
        eventNames.forEach(function(eventName) {
            server.emit(eventName, {});
        });
    }

    server.listen(serverPort);

    // Wait for client to connect
    setTimeout(emit, 200)

    // Wait for first tests to finish
    setTimeout(function() {
        client.up.conn.stream.end();
    }, 400);

    // Run second test
    setTimeout(emit, 1600);

    t.on('end', function() {
        server.close();
        client.close();
        t.ok(true, 'closed server');
    });
});

test('resubscribe with delayed subscription', function(t) {
    var serverPort = Math.floor(Math.random() * 5e4 + 1e4),
        server = seaport.createServer(),
        client = seaport.connect('localhost:' + serverPort, {reconnect: 10}),
        allocatedPort = 1234;

    t.plan(6);

    var eventNames = ['allocate', 'assume', 'free'];

    setTimeout(function () {
        eventNames.forEach(function(eventName) {
            client.subscribe(eventName, function() {
                t.ok(true, eventName + ' emitted');
            });
        });
    }, 100);

    function emit() {
        eventNames.forEach(function(eventName) {
            server.emit(eventName, {});
        });
    }

    server.listen(serverPort);

    // Wait for client to connect
    setTimeout(emit, 200)

    // Wait for first tests to finish
    setTimeout(function() {
        client.up.conn.stream.end();
    }, 400);

    // Run second test
    setTimeout(emit, 1600);

    t.on('end', function() {
        server.close();
        client.close();
        t.ok(true, 'closed server');
    });
});
<|endoftext|>


<commit_before>
$(document).ready( function(){

     $.ajax({
         url:'http://plot-generator.herokuapp.com',
         type:'GET',
         success: function(data){
               $('code#plotgen').html(data["text"]);
         }
     });

     $('button#plotgen').on('click', function () {
        var Status = $(this).val();
        $('code#plotgen').html("Loading...")
          $.ajax({
              url:'http://plot-generator.herokuapp.com',
              type:'GET',
              success: function(data){
                    $('code#plotgen').html(data["text"]);
              }
          });
    });

})

<commit_msg>Error callback<commit_after>
$(document).ready( function(){

     $.ajax({
         url:'http://plot-generator.herokuapp.com',
         type:'GET',
         success: function(data){
               $('code#plotgen').html(data["text"]);
         }
     });

     $('button#plotgen').on('click', function () {
        var Status = $(this).val();
        $('code#plotgen').html("Loading...")
          $.ajax({
              url:'http://plot-generator.herokuapp.com',
              type:'GET',
              success: function(data){
                    $('code#plotgen').html(data["text"]);
              },
              error: function(data){
                    $('code#plotgen').html("Failed to fetch plot.");
              }
          });
    });

})

<|endoftext|>