<commit_before>define(['knockout', 'knockout-collection', 'knockout-mapping', 'lodash', 'cms/modules/ui', 'cms/modules/dropbox-chooser', 'cms/modules/dispatcher', 'amplify', 'bluebird'], function(ko, KnockoutCollection, koMapping, lodash, ui, Dropbox, dispatcher, amplify) {
   var Promise = require("bluebird");

   var FileManager = {};

   FileManager.Sync = function() {
     var that = this;

     that.concurrentFiles = ko.observable(2); // the number of files per request
     that.concurrentConnections = ko.observable(4); // the number of requests simultanously

     this.removeFile = function(item) {
       that.batch.push(ko.unwrap(item.key));
     };

     this.beginRemoveBatch = function() {
       that.batch = [];
     };

     this.commitRemoveBatch = function(processing) {
       var d = $.Deferred();
       if (that.batch.length) {
         processing(true);
         dispatcher.send('DELETE', '/cms/media', { keys: that.batch }, 'json')
          .done(function(response) {
             processing(false);
             d.resolve(response);
           })
          .fail(function(err, response) {
             processing(false);
             amplify.publish('cms.ajax.error', response);
             d.reject(err, response);
          });
       }

       return d.promise();
     };

     this.uploadFromDropbox = function(ci, files, processing, progress, afterwards) {

       var path = ci.path();

       processing(true);
       progress(0);

       Promise.map(_.chunk(files, that.concurrentFiles()), function(chunkOfFiles) {
         var sendPromise = dispatcher.sendPromised('POST', '/cms/media/dropbox', { dropboxFiles: chunkOfFiles, path: path }, 'json');

         sendPromise.reflect().then(function(inspection) {
          if (inspection.isFulfilled()) {
            progress(progress()+chunkOfFiles.length);
          }
         });

         return sendPromise;

       }, {concurrency: that.concurrentConnections()}).then(function() {
         return dispatcher.sendPromised('GET', '/cms/media', undefined, 'json');
       })
       .then(function(response) {
         processing(false);
         return afterwards(response);
       })
       .catch(function(fault) {
         processing(false);

         if (fault.response) {
           amplify.publish('cms.ajax.error', fault.response);
         } else {
           throw fault;
         }
       });
     }
   };

   FileManager.Item = function(data, parentItem) {
     var that = this;

     that.isDragging = ko.observable(false);
     that.type = ko.observable('file');
     that.unsynced = ko.observable(false);
     that.items = ko.observableArray([]);

     var mapping = {
       items: {
         create: function(options) {
           return new FileManager.Item(options.data, options.parent);
         },
         arrayChanged: function(event, item) {
           if (event === 'deleted') {
             amplify.publish('fileManager.deleted', item);
           }
         },
         key: function(data) {
           return ko.unwrap(data.key);
         }
       }
     };

     koMapping.fromJS(data, mapping, that);

     this.label = ko.computed(function() {
       return that.name();
     });

     this.isRoot = ko.computed(function() {
       return that.type() === 'ROOT';
     });

     this.isFile = ko.computed(function() {
       return that.type() === 'file';
     });

     this.isImage = ko.computed(function() {
       if (!that.isFile()) return false;

       var mime = ko.unwrap(that.mimeType);

       return mime && mime.indexOf('image/') === 0;
     });

     this.isDirectory = ko.computed(function() {
       return that.type() === 'directory' || that.isRoot();
     });

     this.parentItem = parentItem;

     this.hasItems = ko.computed(function() {
       return that.items && that.items().length > 0;
     });

     this.addDirectory = function(item) {
       that.items.push(item);
     };

     this.addFile = function(item) {
       that.items.push(item);
     };

     // returns the path as string seperated with / starting with /
     this.path = ko.computed(function() {
       var pathItem = that, path = '';

       while (pathItem && !pathItem.isRoot()) {
         path = pathItem.name()+'/'+path;

         pathItem = pathItem.parentItem;
       }

       if (!that.isDirectory()) {
         path = path.substr(0, path.length-1);
       }

       path = '/'+path;

       return path;
     });

     this.key = ko.computed(function() {
       return that.path().substr(1);
     });
   };

   FileManager.Manager = function(data) {
     var that = this;

     this.sync = new FileManager.Sync();
     this.breadcrumbs = ko.observableArray([]);
     this.selection =  ko.observableArray([]); // selection on one page
     this.chosenFiles = new KnockoutCollection([], {key: 'path'});
     this.processing = ko.observable(false);
     this.filesProgress = ko.observable(0);
     this.filesTotal = ko.observable(0);
     this.isInChoosingMode = ko.observable(true);

     var mapping = {
       root: {
         create: function(options) {
           return new FileManager.Item(options.data, null);
         },
         key: function(data) {
           return ko.unwrap(data.path);
         }
       }
     };

     koMapping.fromJS(data, mapping, that);

     this.currentItem = ko.observable();

     this.setCurrentItem = function(item) {
       that.currentItem(item);

       that.selection.removeAll();

       var bcItem = item, breadcrumbs = [];
       while (!bcItem.isRoot()) {
         breadcrumbs.unshift(bcItem);

         bcItem = bcItem.parentItem;
       }

       breadcrumbs.unshift(that.root);

       that.breadcrumbs(breadcrumbs);
     };

     this.setCurrentItem(that.root);

     this.clickItem = function(item) {
       if (item.isDirectory()) {
         that.setCurrentItem(item);
       } else {
         alert('download: '+item.name());
       }
     };

     this.hasItems = ko.computed(function() {
       var ci = that.currentItem();

       return ci && ci.hasItems();
     });

     this.sortedItems = ko.computed(function() {
       var order = 'asc';

       return _.orderBy(
           that.currentItem().items(),
           // first level: directories before files
           function(item) {
             return item.isDirectory() ? 0 : 1;
           },
           // second level: alphabetically by name
           function(item) {
             return item.name();
           },
           ['asc', order]
         );
     });

     this.removeItem = function(item) {
       if (!item.unsynced()) {
         if (item.isDirectory()) {
           ko.utils.arrayForEach(item.items(), function(childItem) {
             that.removeItem(childItem);
           });
         } else {
           that.sync.removeFile(item);
           that.removeFromChosen(item);

           if (that.currentItem() === item) {
             that.setCurrentItem(item.parentItem);
           }
         }
       }
     };

     this.removeItems = function() {
       var selection = that.selection().slice();

       if (selection.length) {
         that.sync.beginRemoveBatch();

         ko.utils.arrayForEach(selection, function(item) {
           that.removeItem(item);
         });

         that.sync.commitRemoveBatch(that.processing)
           .done(function(response) {
             that.refreshData(response.body);
           });
       }
     };

     this.moveItems = function() {
       var selection = that.selection();

       if (selection.length) {
         console.log(selection);
       }
     };

     this.newFolder = function() {
       ui.prompt("Wie soll der neue Ordner heißen?").done(function(name) {
         if (name != "") {
           name = name.replace(/ä/g, 'ae').replace(/ö/g,'oe').replace(/ü/g, 'ue').replace(/ß/g, 'ss');

           var item;
           that.currentItem().addDirectory(
             item = new FileManager.Item({
               name: name,
               type: 'directory',
               items: [],
               unsynced: true
             }, that.currentItem())
           );

           that.setCurrentItem(item);
         }
       });
     };

     this.hasSelection = ko.computed(function () {
       return that.selection().length > 0;
     });

     this.hasChosenFiles = ko.computed(function () {
       return that.chosenFiles.toArray().length > 0;
     });

     this.trackSelection = ko.computed(function() {
       ko.utils.arrayForEach(that.selection(), function(item) {
         if (item.isFile()) {
           that.chosenFiles.add(item);
         }
       });
     });

     this.selectAll = function() {
       var ci = that.currentItem();

       if (ci) {
         ko.utils.arrayForEach(ci.items(), function(item) {
           if (item.isFile()) {
             that.selection.push(item);
           }
         });
       }
     };

     this.removeFromChosen = function(item) {
       that.selection.remove(item);
       that.chosenFiles.remove(item);
     };

     this.addFilesFromDropbox = function() {
       Dropbox.choose({
         success: function(files) {
           var ci = that.currentItem();

           that.filesTotal(files.length);
           that.sync.uploadFromDropbox(ci, files, that.processing, that.filesProgress, function(response) {
             that.refreshData(response.body);
           });
          },
          linkType: "direct",
          multiselect: true,

          // file types, such as "video" or "images" in the list. For more information,
          // see File types below. By default, all extensions are allowed.
          extensions: []
       });
     };

     this.refreshData = function(data) {
       koMapping.fromJS(data, mapping, that);
     };

     amplify.subscribe('fileManager.deleted', function(item) {
       var ci = that.currentItem();
       if (ci && item === ci) {
         that.setCurrentItem(ci.parentItem);
       }
     });

     this.confirmChosenFiles = function () {
       var chosen = that.chosenFiles.toArray();
       if (chosen.length && that.options && that.options.success) {
         that.options.success.call(that, chosen);
       }
     };

     this.reset = function(options) {
       that.options = options;
       that.selection.removeAll();
       that.chosenFiles.removeAll();
     }
   };

   return FileManager;
});<commit_msg>fix path name in several ways<commit_after>define(['knockout', 'knockout-collection', 'knockout-mapping', 'lodash', 'cms/modules/ui', 'cms/modules/dropbox-chooser', 'cms/modules/dispatcher', 'amplify', 'bluebird'], function(ko, KnockoutCollection, koMapping, lodash, ui, Dropbox, dispatcher, amplify) {
   var Promise = require("bluebird");

   var FileManager = {};

   FileManager.Sync = function() {
     var that = this;

     that.concurrentFiles = ko.observable(2); // the number of files per request
     that.concurrentConnections = ko.observable(4); // the number of requests simultanously

     this.removeFile = function(item) {
       that.batch.push(ko.unwrap(item.key));
     };

     this.beginRemoveBatch = function() {
       that.batch = [];
     };

     this.commitRemoveBatch = function(processing) {
       var d = $.Deferred();
       if (that.batch.length) {
         processing(true);
         dispatcher.send('DELETE', '/cms/media', { keys: that.batch }, 'json')
          .done(function(response) {
             processing(false);
             d.resolve(response);
           })
          .fail(function(err, response) {
             processing(false);
             amplify.publish('cms.ajax.error', response);
             d.reject(err, response);
          });
       }

       return d.promise();
     };

     this.uploadFromDropbox = function(ci, files, processing, progress, afterwards) {

       var path = ci.path();

       processing(true);
       progress(0);

       Promise.map(_.chunk(files, that.concurrentFiles()), function(chunkOfFiles) {
         var sendPromise = dispatcher.sendPromised('POST', '/cms/media/dropbox', { dropboxFiles: chunkOfFiles, path: path }, 'json');

         sendPromise.reflect().then(function(inspection) {
          if (inspection.isFulfilled()) {
            progress(progress()+chunkOfFiles.length);
          }
         });

         return sendPromise;

       }, {concurrency: that.concurrentConnections()}).then(function() {
         return dispatcher.sendPromised('GET', '/cms/media', undefined, 'json');
       })
       .then(function(response) {
         processing(false);
         return afterwards(response);
       })
       .catch(function(fault) {
         processing(false);

         if (fault.response) {
           amplify.publish('cms.ajax.error', fault.response);
         } else {
           throw fault;
         }
       });
     }
   };

   FileManager.Item = function(data, parentItem) {
     var that = this;

     that.isDragging = ko.observable(false);
     that.type = ko.observable('file');
     that.unsynced = ko.observable(false);
     that.items = ko.observableArray([]);

     var mapping = {
       items: {
         create: function(options) {
           return new FileManager.Item(options.data, options.parent);
         },
         arrayChanged: function(event, item) {
           if (event === 'deleted') {
             amplify.publish('fileManager.deleted', item);
           }
         },
         key: function(data) {
           return ko.unwrap(data.key);
         }
       }
     };

     koMapping.fromJS(data, mapping, that);

     this.label = ko.computed(function() {
       return that.name();
     });

     this.isRoot = ko.computed(function() {
       return that.type() === 'ROOT';
     });

     this.isFile = ko.computed(function() {
       return that.type() === 'file';
     });

     this.isImage = ko.computed(function() {
       if (!that.isFile()) return false;

       var mime = ko.unwrap(that.mimeType);

       return mime && mime.indexOf('image/') === 0;
     });

     this.isDirectory = ko.computed(function() {
       return that.type() === 'directory' || that.isRoot();
     });

     this.parentItem = parentItem;

     this.hasItems = ko.computed(function() {
       return that.items && that.items().length > 0;
     });

     this.addDirectory = function(item) {
       that.items.push(item);
     };

     this.addFile = function(item) {
       that.items.push(item);
     };

     // returns the path as string seperated with / starting with /
     this.path = ko.computed(function() {
       var pathItem = that, path = '';

       while (pathItem && !pathItem.isRoot()) {
         path = pathItem.name()+'/'+path;

         pathItem = pathItem.parentItem;
       }

       if (!that.isDirectory()) {
         path = path.substr(0, path.length-1);
       }

       path = '/'+path;

       return path;
     });

     this.key = ko.computed(function() {
       return that.path().substr(1);
     });
   };

   FileManager.Manager = function(data) {
     var that = this;

     this.sync = new FileManager.Sync();
     this.breadcrumbs = ko.observableArray([]);
     this.selection =  ko.observableArray([]); // selection on one page
     this.chosenFiles = new KnockoutCollection([], {key: 'path'});
     this.processing = ko.observable(false);
     this.filesProgress = ko.observable(0);
     this.filesTotal = ko.observable(0);
     this.isInChoosingMode = ko.observable(true);

     var mapping = {
       root: {
         create: function(options) {
           return new FileManager.Item(options.data, null);
         },
         key: function(data) {
           return ko.unwrap(data.path);
         }
       }
     };

     koMapping.fromJS(data, mapping, that);

     this.currentItem = ko.observable();

     this.setCurrentItem = function(item) {
       that.currentItem(item);

       that.selection.removeAll();

       var bcItem = item, breadcrumbs = [];
       while (!bcItem.isRoot()) {
         breadcrumbs.unshift(bcItem);

         bcItem = bcItem.parentItem;
       }

       breadcrumbs.unshift(that.root);

       that.breadcrumbs(breadcrumbs);
     };

     this.setCurrentItem(that.root);

     this.clickItem = function(item) {
       if (item.isDirectory()) {
         that.setCurrentItem(item);
       } else {
         alert('download: '+item.name());
       }
     };

     this.hasItems = ko.computed(function() {
       var ci = that.currentItem();

       return ci && ci.hasItems();
     });

     this.sortedItems = ko.computed(function() {
       var order = 'asc';

       return _.orderBy(
           that.currentItem().items(),
           // first level: directories before files
           function(item) {
             return item.isDirectory() ? 0 : 1;
           },
           // second level: alphabetically by name
           function(item) {
             return item.name();
           },
           ['asc', order]
         );
     });

     this.removeItem = function(item) {
       if (!item.unsynced()) {
         if (item.isDirectory()) {
           ko.utils.arrayForEach(item.items(), function(childItem) {
             that.removeItem(childItem);
           });
         } else {
           that.sync.removeFile(item);
           that.removeFromChosen(item);

           if (that.currentItem() === item) {
             that.setCurrentItem(item.parentItem);
           }
         }
       }
     };

     this.removeItems = function() {
       var selection = that.selection().slice();

       if (selection.length) {
         that.sync.beginRemoveBatch();

         ko.utils.arrayForEach(selection, function(item) {
           that.removeItem(item);
         });

         that.sync.commitRemoveBatch(that.processing)
           .done(function(response) {
             that.refreshData(response.body);
           });
       }
     };

     this.moveItems = function() {
       var selection = that.selection();

       if (selection.length) {
         console.log(selection);
       }
     };

     this.newFolder = function() {
       ui.prompt("Wie soll der neue Ordner heißen?").done(function(name) {
         if (name != "") {
           name = name.replace(/ä/g, 'ae').replace(/ö/g,'oe').replace(/ü/g, 'ue').replace(/ß/g, 'ss');
           name = name.replace(/[,/\\]/g, '_');
           name = _.deburr(name);
           name = _.trim(name);

           var item;
           that.currentItem().addDirectory(
             item = new FileManager.Item({
               name: name,
               type: 'directory',
               items: [],
               unsynced: true
             }, that.currentItem())
           );

           that.setCurrentItem(item);
         }
       });
     };

     this.hasSelection = ko.computed(function () {
       return that.selection().length > 0;
     });

     this.hasChosenFiles = ko.computed(function () {
       return that.chosenFiles.toArray().length > 0;
     });

     this.trackSelection = ko.computed(function() {
       ko.utils.arrayForEach(that.selection(), function(item) {
         if (item.isFile()) {
           that.chosenFiles.add(item);
         }
       });
     });

     this.selectAll = function() {
       var ci = that.currentItem();

       if (ci) {
         ko.utils.arrayForEach(ci.items(), function(item) {
           if (item.isFile()) {
             that.selection.push(item);
           }
         });
       }
     };

     this.removeFromChosen = function(item) {
       that.selection.remove(item);
       that.chosenFiles.remove(item);
     };

     this.addFilesFromDropbox = function() {
       Dropbox.choose({
         success: function(files) {
           var ci = that.currentItem();

           that.filesTotal(files.length);
           that.sync.uploadFromDropbox(ci, files, that.processing, that.filesProgress, function(response) {
             that.refreshData(response.body);
           });
          },
          linkType: "direct",
          multiselect: true,

          // file types, such as "video" or "images" in the list. For more information,
          // see File types below. By default, all extensions are allowed.
          extensions: []
       });
     };

     this.refreshData = function(data) {
       koMapping.fromJS(data, mapping, that);
     };

     amplify.subscribe('fileManager.deleted', function(item) {
       var ci = that.currentItem();
       if (ci && item === ci) {
         that.setCurrentItem(ci.parentItem);
       }
     });

     this.confirmChosenFiles = function () {
       var chosen = that.chosenFiles.toArray();
       if (chosen.length && that.options && that.options.success) {
         that.options.success.call(that, chosen);
       }
     };

     this.reset = function(options) {
       that.options = options;
       that.selection.removeAll();
       that.chosenFiles.removeAll();
     }
   };

   return FileManager;
});<|endoftext|>


<commit_before><commit_msg>Update cities for ProMods 2.46<commit_after><|endoftext|>


<commit_before>var level1 = {};

level1.create = function () {

  this.game.global.life = 3;

  var background = this.game.add.sprite(this.game.world.centerX - 10, this.game.world.centerY, 'background');
  background.anchor.setTo(0.5, 0.5);
  background.scale.setTo(3.4, 3.4);

  this.scoreLabel = this.game.add.text(50, 50, 'score: 0',
		{ font: '22px Arial', fill: '#ffffff' });

  // Add player
  this.player = this.game.add.sprite(this.game.width/2, this.game.world.centerY + 100, 'player');
  this.player.anchor.setTo(0.5, 0.5);
  this.player.scale.setTo(0.5,0.5);
  this.game.physics.arcade.enable(this.player);
  this.player.body.updateBounds(this.player.scale.x, this.player.scale.y);

  this.life = this.game.add.sprite(this.game.width - 150, 40, 'life');
  this.life2 = this.game.add.sprite(this.game.width - 110, 40, 'life');
  this.life3 = this.game.add.sprite(this.game.width - 70, 40, 'life');

  this.emitter = this.game.add.emitter(0, 0, 15);
  this.emitter.makeParticles('pixel');
  this.emitter.setYSpeed(-150, 150);
  this.emitter.setXSpeed(-150, 150);
  this.emitter.setScale(2, 0, 2, 0, 800);
  this.emitter.gravity = 0;

  this.enemies = this.game.add.group();
  this.enemies.createMultiple(10, 'enemy');
  //set the scale for each group children instead of the whole group
  for (i = 0; i <this.enemies.length ; i++) {
	  this.enemies.children[i].scale.setTo(0.5,0.5);
  }
  this.enemies.enableBody = true;
  this.game.physics.arcade.enable(this.enemies);

  this.nextEnemy = 0;

  // Display the coin
  this.coin = this.game.add.sprite(60, 240, 'coin');

  // Set the anchor point to its center
  this.coin.anchor.setTo(0.5, 0.5);
  this.coin.scale.setTo(0.3, 0.3);
  // Add Arcade physics to the coin
  this.game.physics.arcade.enable(this.coin);

  this.coinSound = this.game.add.audio('coin');
  this.laserSound = this.game.add.audio('laser');

  // Create group laser
  this.lasers = this.game.add.group();

  this.lasers.createMultiple(20, 'laser');
  //set the scale for each group children instead of the whole group
  for (i = 0; i <this.lasers.length ; i++) {
	  	this.lasers.children[i].scale.setTo(0.5,0.5);
  }
  this.lasers.callAll('events.onOutOfBounds.add', 'events.onOutOfBounds', this.resetLaser);
  this.lasers.callAll('anchor.setTo', 'anchor', 0.5, 1.0);
  this.lasers.setAll('checkWorldBounds', true);
  this.game.physics.arcade.enable(this.lasers);
  this.lasers.enableBody = true;

  // add Spacebar key
  this.keys = [Phaser.KeyCode.SPACEBAR];
  this.phaserKeys = this.game.input.keyboard.addKeys(this.keys);
  this.game.input.keyboard.addKeyCapture(this.keys);

  this.wasd = {
  	up: this.game.input.keyboard.addKey(Phaser.Keyboard.W),
  	down: this.game.input.keyboard.addKey(Phaser.Keyboard.S),
  	left: this.game.input.keyboard.addKey(Phaser.Keyboard.A),
  	right: this.game.input.keyboard.addKey(Phaser.Keyboard.D)
  };

  cursors = this.game.input.keyboard.createCursorKeys();
},

level1.update = function () {

	this.game.physics.arcade.overlap(this.player, this.enemies, this.playerDie,
		null, this);
	this.game.physics.arcade.overlap(this.lasers, this.enemies, this.enemyDie,
		null, this);
	this.game.physics.arcade.overlap(this.player, this.coin, this.takeCoin,
	null, this);

	if (this.nextEnemy < this.game.time.now) {
		// Define our variables
		var start = 4000, end = 1000, score = 100;
		// Formula to decrease the delay between enemies over time
		// At first it's 4000ms, then slowly goes to 1000ms
		var delay = Math.max(
		start - (start - end) * this.game.global.score / score, end);
		// Create a new enemy and update the 'nextEnemy' time
		this.addEnemy();
		this.nextEnemy = this.game.time.now + delay;
	}

	// Player movement
    if ( (cursors.up.isDown || this.wasd.up.isDown)  && this.player.body.y > 200)
    {
        this.player.body.velocity.y = -400;
    }
    else if ( (cursors.down.isDown || this.wasd.down.isDown) && this.player.body.y < 500)
    {
        this.player.body.velocity.y = 400;
    }
    else if (cursors.left.isDown || this.wasd.left.isDown)
    {
        this.player.body.velocity.x = -400;
        this.player.body.velocity.y = 0;
        this.player.angle = -10;
    }
    else if (cursors.right.isDown || this.wasd.right.isDown)
    {
        this.player.body.velocity.x = 400;
        this.player.body.velocity.y = 0;
        this.player.angle = 10;
    }
    else
    {
		this.player.body.velocity.x = 0;
		this.player.body.velocity.y = 0;
		this.player.angle = 0;
	}

	this.game.world.wrap(this.player, 0, true);

	// Fire laser event
	for (var index in this.phaserKeys) {
		var key = this.phaserKeys[index];
		if (key.justDown) {
			this.fireLaser();
		}
	}
},

level1.startLevel2 = function () {
	this.game.state.start('level2');
},

level1.resetLaser = function (laser) {
	laser.kill();
},

level1.fireLaser = function () {
	var laser = this.lasers.getFirstExists(false);
	if (laser) {
		this.laserSound.play();
		if(this.player.angle == 10){
			laser.reset(this.player.x + 20, this.player.y - 20);
		}
		else if (this.player.angle == -10){
			laser.reset(this.player.x - 20, this.player.y - 20);
		}
		else{
			laser.reset(this.player.x, this.player.y - 20);
		}
		laser.body.velocity.y = -500;
	}
},

level1.takeCoin = function(player, coin) {
	this.game.global.score += 5;
	this.scoreLabel.text = 'score: ' + this.game.global.score;

	this.coinSound.play();
	this.coin.scale.setTo(0, 0);
	this.game.add.tween(this.coin.scale).to({x: 0.4, y: 0.4}, 300).start();

	this.game.add.tween(this.player.scale).to({x: 0.7, y: 0.7}, 100)
	.yoyo(true).start();

	this.updateCoinPosition();
},

level1.updateCoinPosition = function() {
	var coinPosition = [
		{x: 140, y: 210}, {x: 360, y: 210},
		{x: 60, y: 340}, {x: 440, y: 340},
		{x: 130, y: 400}, {x: 370, y: 400}
	];
	for (var i = 0; i < coinPosition.length; i++) {
		if (coinPosition[i].x == this.coin.x) {
			coinPosition.splice(i, 1);
		}
	}
	var newPosition = this.game.rnd.pick(coinPosition);
	this.coin.reset(newPosition.x, newPosition.y);
},
level1.addEnemy = function() {
	var enemy = this.enemies.getFirstDead();
	if (!enemy) {
		return;
	}
	enemy.anchor.setTo(0.5, 1);
	enemy.reset(this.game.rnd.pick([100, 200, 300, 400, 500, 600, 700]), 0);
	enemy.body.gravity.y = 500;
	enemy.body.velocity.x = this.game.rnd.pick([0, 50, 100, 200]) * this.game.rnd.pick([-1, 1]);
	enemy.checkWorldBounds = true;
	enemy.outOfBoundsKill = true;
},
level1.playerDie = function() {

	this.emitter.x = this.player.x;
	this.emitter.y = this.player.y;
	this.emitter.start(true, 800, null, 15);

	this.player.kill();

	this.game.global.life -= 1;

	switch(this.game.global.life) {
	    case 2:
	    	this.life3.kill();
	        this.game.time.events.add(1000, this.resetPlayer, this);
	        break;
	    case 1:
	    	this.life2.kill();
	        this.game.time.events.add(1000, this.resetPlayer, this);
	        break;
	    case 0:
	    	this.life.kill();
			this.game.global.score = 0;
	        this.game.time.events.add(1000, this.startMenu, this);
	        break;
	}

},
level1.enemyDie = function(laser, enemy) {
	enemy.kill();

	this.game.global.score += 10;
	this.scoreLabel.text = 'score: ' + this.game.global.score;

	this.emitter.x = enemy.x;
	this.emitter.y = enemy.y;
	this.emitter.start(true, 800, null, 15);

},
level1.resetPlayer = function() {
	this.player.reset(this.game.width/2, this.game.world.centerY + 100);
},
level1.startMenu = function() {
	this.game.state.start('mainTitle');
},

module.exports = level1;
<commit_msg>Changed the way enemies move in the game<commit_after>var level1 = {};

level1.create = function () {

  this.game.global.life = 3;

  var background = this.game.add.sprite(this.game.world.centerX - 10, this.game.world.centerY, 'background');
  background.anchor.setTo(0.5, 0.5);
  background.scale.setTo(3.4, 3.4);

  this.scoreLabel = this.game.add.text(50, 50, 'score: 0',
		{ font: '22px Arial', fill: '#ffffff' });

  // Add player
  this.player = this.game.add.sprite(this.game.width/2, this.game.world.centerY + 100, 'player');
  this.player.anchor.setTo(0.5, 0.5);
  this.player.scale.setTo(0.5,0.5);
  this.game.physics.arcade.enable(this.player);
  this.player.body.updateBounds(this.player.scale.x, this.player.scale.y);

  this.life = this.game.add.sprite(this.game.width - 150, 40, 'life');
  this.life2 = this.game.add.sprite(this.game.width - 110, 40, 'life');
  this.life3 = this.game.add.sprite(this.game.width - 70, 40, 'life');

  this.emitter = this.game.add.emitter(0, 0, 15);
  this.emitter.makeParticles('pixel');
  this.emitter.setYSpeed(-150, 150);
  this.emitter.setXSpeed(-150, 150);
  this.emitter.setScale(2, 0, 2, 0, 800);
  this.emitter.gravity = 0;

  this.enemies = this.game.add.group();
  this.enemies.createMultiple(10, 'enemy');
  //set the scale for each group children instead of the whole group
  for (i = 0; i <this.enemies.length ; i++) {
	  this.enemies.children[i].scale.setTo(0.5,0.5);
  }
  this.enemies.enableBody = true;
  this.game.physics.arcade.enable(this.enemies);

  this.nextEnemy = 0;

  // Display the coin
  this.coin = this.game.add.sprite(60, 240, 'coin');

  // Set the anchor point to its center
  this.coin.anchor.setTo(0.5, 0.5);
  this.coin.scale.setTo(0.3, 0.3);
  // Add Arcade physics to the coin
  this.game.physics.arcade.enable(this.coin);

  this.coinSound = this.game.add.audio('coin');
  this.laserSound = this.game.add.audio('laser');

  // Create group laser
  this.lasers = this.game.add.group();

  this.lasers.createMultiple(20, 'laser');
  //set the scale for each group children instead of the whole group
  for (i = 0; i <this.lasers.length ; i++) {
	  	this.lasers.children[i].scale.setTo(0.5,0.5);
  }
  this.lasers.callAll('events.onOutOfBounds.add', 'events.onOutOfBounds', this.resetLaser);
  this.lasers.callAll('anchor.setTo', 'anchor', 0.5, 1.0);
  this.lasers.setAll('checkWorldBounds', true);
  this.game.physics.arcade.enable(this.lasers);
  this.lasers.enableBody = true;

  // add Spacebar key
  this.keys = [Phaser.KeyCode.SPACEBAR];
  this.phaserKeys = this.game.input.keyboard.addKeys(this.keys);
  this.game.input.keyboard.addKeyCapture(this.keys);

  this.wasd = {
  	up: this.game.input.keyboard.addKey(Phaser.Keyboard.W),
  	down: this.game.input.keyboard.addKey(Phaser.Keyboard.S),
  	left: this.game.input.keyboard.addKey(Phaser.Keyboard.A),
  	right: this.game.input.keyboard.addKey(Phaser.Keyboard.D)
  };

  cursors = this.game.input.keyboard.createCursorKeys();
},

level1.update = function () {

	this.game.physics.arcade.overlap(this.player, this.enemies, this.playerDie,
		null, this);
	this.game.physics.arcade.overlap(this.lasers, this.enemies, this.enemyDie,
		null, this);
	this.game.physics.arcade.overlap(this.player, this.coin, this.takeCoin,
	null, this);

	if (this.nextEnemy < this.game.time.now) {
		// Define our variables
		var start = 4000, end = 1000, score = 100;
		// Formula to decrease the delay between enemies over time
		// At first it's 4000ms, then slowly goes to 1000ms
		var delay = Math.max(
		start - (start - end) * this.game.global.score / score, end);
		// Create a new enemy and update the 'nextEnemy' time
		this.addEnemy();
		this.nextEnemy = this.game.time.now + delay;
	}

	// Player movement
    if ( (cursors.up.isDown || this.wasd.up.isDown)  && this.player.body.y > 200)
    {
        this.player.body.velocity.y = -400;
    }
    else if ( (cursors.down.isDown || this.wasd.down.isDown) && this.player.body.y < 500)
    {
        this.player.body.velocity.y = 400;
    }
    else if (cursors.left.isDown || this.wasd.left.isDown)
    {
        this.player.body.velocity.x = -400;
        this.player.body.velocity.y = 0;
        this.player.angle = -10;
    }
    else if (cursors.right.isDown || this.wasd.right.isDown)
    {
        this.player.body.velocity.x = 400;
        this.player.body.velocity.y = 0;
        this.player.angle = 10;
    }
    else
    {
		this.player.body.velocity.x = 0;
		this.player.body.velocity.y = 0;
		this.player.angle = 0;
	}

	this.game.world.wrap(this.player, 0, true);

	// Fire laser event
	for (var index in this.phaserKeys) {
		var key = this.phaserKeys[index];
		if (key.justDown) {
			this.fireLaser();
		}
	}
},

level1.startLevel2 = function () {
	this.game.state.start('level2');
},

level1.resetLaser = function (laser) {
	laser.kill();
},

level1.fireLaser = function () {
	var laser = this.lasers.getFirstExists(false);
	if (laser) {
		this.laserSound.play();
		if(this.player.angle == 10){
			laser.reset(this.player.x + 20, this.player.y - 20);
		}
		else if (this.player.angle == -10){
			laser.reset(this.player.x - 20, this.player.y - 20);
		}
		else{
			laser.reset(this.player.x, this.player.y - 20);
		}
		laser.body.velocity.y = -500;
	}
},

level1.takeCoin = function(player, coin) {
	this.game.global.score += 5;
	this.scoreLabel.text = 'score: ' + this.game.global.score;

	this.coinSound.play();
	this.coin.scale.setTo(0, 0);
	this.game.add.tween(this.coin.scale).to({x: 0.4, y: 0.4}, 300).start();

	this.game.add.tween(this.player.scale).to({x: 0.7, y: 0.7}, 100)
	.yoyo(true).start();

	this.updateCoinPosition();
},

level1.updateCoinPosition = function() {
	var coinPosition = [
		{x: 140, y: 210}, {x: 360, y: 210},
		{x: 60, y: 340}, {x: 440, y: 340},
		{x: 130, y: 400}, {x: 370, y: 400}
	];
	for (var i = 0; i < coinPosition.length; i++) {
		if (coinPosition[i].x == this.coin.x) {
			coinPosition.splice(i, 1);
		}
	}
	var newPosition = this.game.rnd.pick(coinPosition);
	this.coin.reset(newPosition.x, newPosition.y);
},
level1.addEnemy = function() {
	var enemy = this.enemies.getFirstDead();
	if (!enemy) {
		return;
	}
	enemy.anchor.setTo(0.5, 1);
	enemy.reset(this.game.rnd.pick([100, 200, 300, 400, 500, 600, 700]), 0);
	enemy.body.velocity.y = this.game.rnd.pick([100, 200]) * this.game.rnd.pick([-1, 1]);
	enemy.body.velocity.x = this.game.rnd.pick([0, 50]) * this.game.rnd.pick([-1, 1]);
	enemy.checkWorldBounds = true;
	enemy.outOfBoundsKill = true;
},
level1.playerDie = function() {

	this.emitter.x = this.player.x;
	this.emitter.y = this.player.y;
	this.emitter.start(true, 800, null, 15);

	this.player.kill();

	this.game.global.life -= 1;

	switch(this.game.global.life) {
	    case 2:
	    	this.life3.kill();
	        this.game.time.events.add(1000, this.resetPlayer, this);
	        break;
	    case 1:
	    	this.life2.kill();
	        this.game.time.events.add(1000, this.resetPlayer, this);
	        break;
	    case 0:
	    	this.life.kill();
			this.game.global.score = 0;
	        this.game.time.events.add(1000, this.startMenu, this);
	        break;
	}

},
level1.enemyDie = function(laser, enemy) {
	enemy.kill();

	this.game.global.score += 10;
	this.scoreLabel.text = 'score: ' + this.game.global.score;

	this.emitter.x = enemy.x;
	this.emitter.y = enemy.y;
	this.emitter.start(true, 800, null, 15);

},
level1.resetPlayer = function() {
	this.player.reset(this.game.width/2, this.game.world.centerY + 100);
},
level1.startMenu = function() {
	this.game.state.start('mainTitle');
},

module.exports = level1;
<|endoftext|>


<commit_before>(function($) {

	$.extend(mejs.MepDefaults, {
		muteText: 'Mute Toggle',
		hideVolumeOnTouchDevices: true,

		audioVolume: 'horizontal',
		videoVolume: 'vertical'
	});

	$.extend(MediaElementPlayer.prototype, {
		buildvolume: function(player, controls, layers, media) {

			// Android and iOS don't support volume controls
			if (mejs.MediaFeatures.hasTouch && this.options.hideVolumeOnTouchDevices)
				return;

			var t = this,
				mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume,
				mute = (mode == 'horizontal') ?

				// horizontal version
				$('
'+
					'<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '"></button>'+
				'
' +
				'
'+ // outer background
					'

'+ // line background
					'

'+ // current volume
					'

'+ // handle
				'
'
				)
					.appendTo(controls) :

				// vertical version
				$('
'+
					'<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '"></button>'+
					'
'+ // outer background
						'

'+ // line background
						'

'+ // current volume
						'

'+ // handle
					'
'+
				'
')
					.appendTo(controls),
			volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'),
			volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'),
			volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'),
			volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'),

			positionVolumeHandle = function(volume, secondTry) {

				if (!volumeSlider.is(':visible') && typeof secondTry === 'undefined') {
					volumeSlider.show();
					positionVolumeHandle(volume, true);
					volumeSlider.hide()
					return;
				}

				// correct to 0-1
				volume = Math.max(0,volume);
				volume = Math.min(volume,1);

				// ajust mute button style
				if (volume == 0) {
					mute.removeClass('mejs-mute').addClass('mejs-unmute');
				} else {
					mute.removeClass('mejs-unmute').addClass('mejs-mute');
				}

				// position slider
				if (mode == 'vertical') {
					var

						// height of the full size volume slider background
						totalHeight = volumeTotal.height(),

						// top/left of full size volume slider background
						totalPosition = volumeTotal.position(),

						// the new top position based on the current volume
						// 70% volume on 100px height == top:30px
						newTop = totalHeight - (totalHeight * volume);

					// handle
					volumeHandle.css(Math.round('top', totalPosition.top + newTop - (volumeHandle.height() / 2)));

					// show the current visibility
					volumeCurrent.height(Math.round(totalHeight - newTop));
					volumeCurrent.css('top', Math.round(totalPosition.top + newTop));
				} else {
					var

						// height of the full size volume slider background
						totalWidth = volumeTotal.width(),

						// top/left of full size volume slider background
						totalPosition = volumeTotal.position(),

						// the new left position based on the current volume
						newLeft = totalWidth * volume;

					// handle
					volumeHandle.css('left', Math.round(totalPosition.left + newLeft - (volumeHandle.width() / 2));

					// rezize the current part of the volume bar
					volumeCurrent.width(Math.round(newLeft));
				}
			},
			handleVolumeMove = function(e) {

				var volume = null,
					totalOffset = volumeTotal.offset();

				// calculate the new volume based on the moust position
				if (mode == 'vertical') {

					var
						railHeight = volumeTotal.height(),
						totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10),
						newY = e.pageY - totalOffset.top;

					volume = (railHeight - newY) / railHeight;

					// the controls just hide themselves (usually when mouse moves too far up)
					if (totalOffset.top == 0 || totalOffset.left == 0)
						return;

				} else {
					var
						railWidth = volumeTotal.width(),
						newX = e.pageX - totalOffset.left;

					volume = newX / railWidth;
				}

				// ensure the volume isn't outside 0-1
				volume = Math.max(0,volume);
				volume = Math.min(volume,1);

				// position the slider and handle
				positionVolumeHandle(volume);

				// set the media object (this will trigger the volumechanged event)
				if (volume == 0) {
					media.setMuted(true);
				} else {
					media.setMuted(false);
				}
				media.setVolume(volume);
			},
			mouseIsDown = false,
			mouseIsOver = false;

			// SLIDER

			mute
				.hover(function() {
					volumeSlider.show();
					mouseIsOver = true;
				}, function() {
					mouseIsOver = false;

					if (!mouseIsDown && mode == 'vertical')	{
						volumeSlider.hide();
					}
				});

			volumeSlider
				.bind('mouseover', function() {
					mouseIsOver = true;
				})
				.bind('mousedown', function (e) {
					handleVolumeMove(e);
					$(document)
						.bind('mousemove.vol', function(e) {
							handleVolumeMove(e);
						})
						.bind('mouseup.vol', function () {
							mouseIsDown = false;
							$(document).unbind('.vol');

							if (!mouseIsOver && mode == 'vertical') {
								volumeSlider.hide();
							}
						});
					mouseIsDown = true;

					return false;
				});

			// MUTE button
			mute.find('button').click(function() {
				media.setMuted( !media.muted );
			});

			// listen for volume change events from other sources
			media.addEventListener('volumechange', function(e) {
				if (!mouseIsDown) {
					if (media.muted) {
						positionVolumeHandle(0);
						mute.removeClass('mejs-mute').addClass('mejs-unmute');
					} else {
						positionVolumeHandle(media.volume);
						mute.removeClass('mejs-unmute').addClass('mejs-mute');
					}
				}
			}, false);

			if (t.container.is(':visible')) {
				// set initial volume
				positionVolumeHandle(player.options.startVolume);

				// shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements
				if (media.pluginType === 'native') {
					media.setVolume(player.options.startVolume);
				}
			}
		}
	});

})(mejs.$);
<commit_msg>Fixed probelms from commit<commit_after>(function($) {

	$.extend(mejs.MepDefaults, {
		muteText: 'Mute Toggle',
		hideVolumeOnTouchDevices: true,

		audioVolume: 'horizontal',
		videoVolume: 'vertical'
	});

	$.extend(MediaElementPlayer.prototype, {
		buildvolume: function(player, controls, layers, media) {

			// Android and iOS don't support volume controls
			if (mejs.MediaFeatures.hasTouch && this.options.hideVolumeOnTouchDevices)
				return;

			var t = this,
				mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume,
				mute = (mode == 'horizontal') ?

				// horizontal version
				$('
'+
					'<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '"></button>'+
				'
' +
				'
'+ // outer background
					'

'+ // line background
					'

'+ // current volume
					'

'+ // handle
				'
'
				)
					.appendTo(controls) :

				// vertical version
				$('
'+
					'<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '"></button>'+
					'
'+ // outer background
						'

'+ // line background
						'

'+ // current volume
						'

'+ // handle
					'
'+
				'
')
					.appendTo(controls),
			volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'),
			volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'),
			volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'),
			volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'),

			positionVolumeHandle = function(volume, secondTry) {

				if (!volumeSlider.is(':visible') && typeof secondTry == 'undefined') {
					volumeSlider.show();
					positionVolumeHandle(volume, true);
					volumeSlider.hide()
					return;
				}

				// correct to 0-1
				volume = Math.max(0,volume);
				volume = Math.min(volume,1);

				// ajust mute button style
				if (volume == 0) {
					mute.removeClass('mejs-mute').addClass('mejs-unmute');
				} else {
					mute.removeClass('mejs-unmute').addClass('mejs-mute');
				}

				// position slider
				if (mode == 'vertical') {
					var

						// height of the full size volume slider background
						totalHeight = volumeTotal.height(),

						// top/left of full size volume slider background
						totalPosition = volumeTotal.position(),

						// the new top position based on the current volume
						// 70% volume on 100px height == top:30px
						newTop = totalHeight - (totalHeight * volume);

					// handle
					volumeHandle.css('top', totalPosition.top + newTop - (volumeHandle.height() / 2));

					// show the current visibility
					volumeCurrent.height(totalHeight - newTop );
					volumeCurrent.css('top', totalPosition.top + newTop);
				} else {
					var

						// height of the full size volume slider background
						totalWidth = volumeTotal.width(),

						// top/left of full size volume slider background
						totalPosition = volumeTotal.position(),

						// the new left position based on the current volume
						newLeft = totalWidth * volume;

					// handle
					volumeHandle.css('left', totalPosition.left + newLeft - (volumeHandle.width() / 2));

					// rezize the current part of the volume bar
					volumeCurrent.width( newLeft );
				}
			},
			handleVolumeMove = function(e) {

				var volume = null,
					totalOffset = volumeTotal.offset();

				// calculate the new volume based on the moust position
				if (mode == 'vertical') {

					var
						railHeight = volumeTotal.height(),
						totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10),
						newY = e.pageY - totalOffset.top;

					volume = (railHeight - newY) / railHeight;

					// the controls just hide themselves (usually when mouse moves too far up)
					if (totalOffset.top == 0 || totalOffset.left == 0)
						return;

				} else {
					var
						railWidth = volumeTotal.width(),
						newX = e.pageX - totalOffset.left;

					volume = newX / railWidth;
				}

				// ensure the volume isn't outside 0-1
				volume = Math.max(0,volume);
				volume = Math.min(volume,1);

				// position the slider and handle
				positionVolumeHandle(volume);

				// set the media object (this will trigger the volumechanged event)
				if (volume == 0) {
					media.setMuted(true);
				} else {
					media.setMuted(false);
				}
				media.setVolume(volume);
			},
			mouseIsDown = false,
			mouseIsOver = false;

			// SLIDER

			mute
				.hover(function() {
					volumeSlider.show();
					mouseIsOver = true;
				}, function() {
					mouseIsOver = false;

					if (!mouseIsDown && mode == 'vertical')	{
						volumeSlider.hide();
					}
				});

			volumeSlider
				.bind('mouseover', function() {
					mouseIsOver = true;
				})
				.bind('mousedown', function (e) {
					handleVolumeMove(e);
					$(document)
						.bind('mousemove.vol', function(e) {
							handleVolumeMove(e);
						})
						.bind('mouseup.vol', function () {
							mouseIsDown = false;
							$(document).unbind('.vol');

							if (!mouseIsOver && mode == 'vertical') {
								volumeSlider.hide();
							}
						});
					mouseIsDown = true;

					return false;
				});

			// MUTE button
			mute.find('button').click(function() {
				media.setMuted( !media.muted );
			});

			// listen for volume change events from other sources
			media.addEventListener('volumechange', function(e) {
				if (!mouseIsDown) {
					if (media.muted) {
						positionVolumeHandle(0);
						mute.removeClass('mejs-mute').addClass('mejs-unmute');
					} else {
						positionVolumeHandle(media.volume);
						mute.removeClass('mejs-unmute').addClass('mejs-mute');
					}
				}
			}, false);

			if (t.container.is(':visible')) {
				// set initial volume
				positionVolumeHandle(player.options.startVolume);

				// shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements
				if (media.pluginType === 'native') {
					media.setVolume(player.options.startVolume);
				}
			}
		}
	});

})(mejs.$);<|endoftext|>


<commit_before>/* @flow */

// This is the server side of the puzzle syncing process. (The client side is in
// client/state.js.)
//
// We maintain a bucket (a ServerSyncer object) of socket connections for each
// puzzle. For each new connection, we wait for the "hello" packet. This packet
// tells us which puzzle the connection is for, and then we put it in that bucket
// (creating it if necessary).
//
// The ServerSyncer's job is actually pretty easy compared to the clients.
// It just has to receive updates from clients, save them, and send them back
// out. For each puzzle, the server maintains a sequence of operations leading up
// to the latest state. When the server receives a new operation from a client,
// it checks which version of the puzzle the operation is rooted at. It then
// transforms the new operation up so that it is rooted at the latest puzzle.
// It then saves this new operation and broadcasts it to all the connected
// clients.

// TODO clean up broken connections
// TODO check that received messages are well-formed
// TODO handle errors

import * as db from "./db";
import * as Ot from "../shared/ot";

import type {PuzzleState} from "../shared/types";

// Takes a single argument, the listener socket.
export function init(socket_listener: any) {
  // Initialize an empty list of buckets.
  const connection_buckets = {}

  socket_listener.on("connection", (socket) => {
    console.debug("New connection");

    // On each new connection, wait for the "hello" packet to be received.
    socket.on("hello", (data) => {
      // Now we know which puzzle the connection is for. Add the connection
      // to the bucket.
      if (!(data.puzzleID in connection_buckets)) {
        connection_buckets[data.puzzleID] = new ServerSyncer(data.puzzleID, () => {
          // This callback is called when the bucket is ready to delete itself.
          delete connection_buckets[data.puzzleID];
          console.debug(`Deleted bucket ${data.puzzleID}`);
        });
        console.debug(`Created bucket ${data.puzzleID}`);
      }
      console.debug(`Adding connection to bucket ${data.puzzleID}`);
      connection_buckets[data.puzzleID].addConnection(socket, data);
    });
  });
}

// Each ServerSyncer bucket needs to execute a bunch of things in series.
// This is an object to help with that - you push tasks, and they get
// executed in the order pushed (FIFO queue).
// Each task pushed is a function which takes a callback - call the callback
// when the task is complete.
class AsyncQueue {
  bottom: any = null;
  top: any = null;

  push(fn) {
    if (this.top === null) {
      this.top = this.bottom = {
        fn: fn,
        next: null,
      };
      this.call(fn);
    } else {
      if (!this.bottom) {
        throw new Error('invariant error: bottom should be non-null');
      }
      this.bottom.next = {
        fn: fn,
        next: null,
      };
      this.bottom = this.bottom.next;
    }
  }

  call(fn) {
    fn(() => {
      if (!this.top) {
        throw new Error('invariant error: top should be non-null');
      }

      this.top = this.top.next;
      if (this.top === null) {
        this.bottom = null;
      } else {
        this.call(this.top.fn);
      }
    });
  }
}

class ServerSyncer {
  latestStateID: number;
  latestState: PuzzleState;

  // list of objects:
  //   socket: socket.io object
  //   id: unique id, shared with the clients
  //   cursor: current cursor
  connections = [];

  // All important tasks are done through the queue.
  queue = new AsyncQueue();

  // Used to assign ids to the connected clients
  idCounter = 1;

  puzzleID: string;

  constructor(puzzleID, callbackOnClose) {
    this.puzzleID = puzzleID;

    // Start by loading in the latest puzzle state from memory.
    this.queue.push((callback) => {
      db.loadPuzzleLatestState(puzzleID, (puzzle) => {
        if (puzzle === null) {
          // puzzle does not exist
          for (const conn of this.connections) {
            conn.socket.disconnect();
          }
          callbackOnClose();
        } else {
          this.latestState = puzzle.state;
          this.latestStateID = puzzle.stateID;
          callback();
        }
      });
    });
  }

  addConnection(socket, data) {
    const conn = {
      socket: socket,
      id: this.idCounter,
      cursor: null,
    };
    this.idCounter += 1;

    // For the new connection, you need to
    //   - Add it to the connections list.
    //   - If it asks for the lastest state, just send it the latest state.
    //   - If it asks for all operations from a given state, fetch those
    //   operations and send them.
    // All atomically, of course (that's what the AsyncQueue is for...)
    // (For example, you don't want to add it to the connection list before
    // sending these operations - or else it might send updates in the wrong
    // order.)
    this.queue.push((callback) => {
      this.connections.push(conn);

      if (data.latest === "yes") {
        // NOTE: unused currently; clients always start with
        // a state.
        socket.emit("state", {
          stateID: this.latestStateID,
          puzzle: this.latestState,
        });
        callback();
      } else {
        db.getOpsToLatest(this.puzzleID, data.from, (ops) => {
          let i = data.from + 1
          for (const op of ops) {
            socket.emit("update", {
              stateID: i,
              opID: op.opID,
              op: op.op,
            });
            i++;
          }
          socket.emit("update_cursor", {
            cursor_updates: this.connections.map((conn) => {
              return {id: conn.id, cursor: conn.cursor};
            }),
          });
          callback();
        });
      }
    });

    // What to do when you receive an "update" packet from the client.
    socket.on("update", (update_data) => {
      console.debug(`Received update ${update_data.opID} rooted at ${update_data.rootID}`);

      this.queue.push((callback) => {
        this.doesOpExist(update_data.opID, (exists) => {
          if (exists) {
            // If the operation has already been received, ignore it.
            // We're done here.
            callback();
          } else {
            // Otherwise, it's a new update that we have to process.
            // First, load the puzzle state that the operation is rooted at.
            // Then, load the operations that lead from that state to the
            // latest state.
            db.loadPuzzleState(this.puzzleID, update_data.rootID, (rootState) => {
              db.getOpsToLatest(this.puzzleID, update_data.rootID, (ops) => {
                let newOp = update_data.op;
                // TODO check for rootState being null
                // $FlowFixMe
                let newState: PuzzleState = rootState;
                // Transform the new operation against the operations
                // that already exist.
                for (const op of ops) {
                  const [a1, _] = Ot.xform(newState, newOp, op.op);
                  newState = Ot.apply(newState, op.op);
                  newOp = a1;
                }
                // TODO check for null
                newState = Ot.apply(this.latestState, newOp);
                // Save the new (transformed) op and the new state.
                db.saveOp(this.puzzleID, update_data.opID, newOp, newState, () => {
                  conn.cursor = update_data.cursor;

                  // Tell all the connections about the new update.
                  this.broadcast("update", {
                    stateID: this.latestStateID + 1,
                    opID: update_data.opID,
                    op: newOp,
                    cursor_updates: [
                      {
                        user_id: conn.id,
                        cursor: conn.cursor,
                      }
                    ],
                  });
                  this.latestState = newState;
                  this.latestStateID++;
                  callback();
                });
              });
            });
          }
        });
      });
    });

    socket.on("cursor_update", (update_data) => {
      conn.cursor = update_data.cursor;
      this.broadcast_except(conn, "update_cursor", {
        cursor_updates: [
          {
            user_id: conn.id,
            cursor: conn.cursor,
          }
        ]
      });
    });
  }

  doesOpExist(opID, callback) {
    db.getOpSeq(this.puzzleID, opID, (op) => {
      callback(op !== null);
    });
  }

  broadcast(msg, data) {
    this.broadcast_except(null, msg, data);
  }

  broadcast_except(connExc, msg, data) {
    for (const conn of this.connections) {
      if (conn !== connExc) {
        conn.socket.emit(msg, data);
      }
    }
  }
}
<commit_msg>don't send a client's cursor info to itself<commit_after>/* @flow */

// This is the server side of the puzzle syncing process. (The client side is in
// client/state.js.)
//
// We maintain a bucket (a ServerSyncer object) of socket connections for each
// puzzle. For each new connection, we wait for the "hello" packet. This packet
// tells us which puzzle the connection is for, and then we put it in that bucket
// (creating it if necessary).
//
// The ServerSyncer's job is actually pretty easy compared to the clients.
// It just has to receive updates from clients, save them, and send them back
// out. For each puzzle, the server maintains a sequence of operations leading up
// to the latest state. When the server receives a new operation from a client,
// it checks which version of the puzzle the operation is rooted at. It then
// transforms the new operation up so that it is rooted at the latest puzzle.
// It then saves this new operation and broadcasts it to all the connected
// clients.

// TODO clean up broken connections
// TODO check that received messages are well-formed
// TODO handle errors

import * as db from "./db";
import * as Ot from "../shared/ot";

import type {PuzzleState} from "../shared/types";

// Takes a single argument, the listener socket.
export function init(socket_listener: any) {
  // Initialize an empty list of buckets.
  const connection_buckets = {}

  socket_listener.on("connection", (socket) => {
    console.debug("New connection");

    // On each new connection, wait for the "hello" packet to be received.
    socket.on("hello", (data) => {
      // Now we know which puzzle the connection is for. Add the connection
      // to the bucket.
      if (!(data.puzzleID in connection_buckets)) {
        connection_buckets[data.puzzleID] = new ServerSyncer(data.puzzleID, () => {
          // This callback is called when the bucket is ready to delete itself.
          delete connection_buckets[data.puzzleID];
          console.debug(`Deleted bucket ${data.puzzleID}`);
        });
        console.debug(`Created bucket ${data.puzzleID}`);
      }
      console.debug(`Adding connection to bucket ${data.puzzleID}`);
      connection_buckets[data.puzzleID].addConnection(socket, data);
    });
  });
}

// Each ServerSyncer bucket needs to execute a bunch of things in series.
// This is an object to help with that - you push tasks, and they get
// executed in the order pushed (FIFO queue).
// Each task pushed is a function which takes a callback - call the callback
// when the task is complete.
class AsyncQueue {
  bottom: any = null;
  top: any = null;

  push(fn) {
    if (this.top === null) {
      this.top = this.bottom = {
        fn: fn,
        next: null,
      };
      this.call(fn);
    } else {
      if (!this.bottom) {
        throw new Error('invariant error: bottom should be non-null');
      }
      this.bottom.next = {
        fn: fn,
        next: null,
      };
      this.bottom = this.bottom.next;
    }
  }

  call(fn) {
    fn(() => {
      if (!this.top) {
        throw new Error('invariant error: top should be non-null');
      }

      this.top = this.top.next;
      if (this.top === null) {
        this.bottom = null;
      } else {
        this.call(this.top.fn);
      }
    });
  }
}

class ServerSyncer {
  latestStateID: number;
  latestState: PuzzleState;

  // list of objects:
  //   socket: socket.io object
  //   id: unique id, shared with the clients
  //   cursor: current cursor
  connections = [];

  // All important tasks are done through the queue.
  queue = new AsyncQueue();

  // Used to assign ids to the connected clients
  idCounter = 1;

  puzzleID: string;

  constructor(puzzleID, callbackOnClose) {
    this.puzzleID = puzzleID;

    // Start by loading in the latest puzzle state from memory.
    this.queue.push((callback) => {
      db.loadPuzzleLatestState(puzzleID, (puzzle) => {
        if (puzzle === null) {
          // puzzle does not exist
          for (const conn of this.connections) {
            conn.socket.disconnect();
          }
          callbackOnClose();
        } else {
          this.latestState = puzzle.state;
          this.latestStateID = puzzle.stateID;
          callback();
        }
      });
    });
  }

  addConnection(socket, data) {
    const conn = {
      socket: socket,
      id: this.idCounter,
      cursor: null,
    };
    this.idCounter += 1;

    // For the new connection, you need to
    //   - Add it to the connections list.
    //   - If it asks for the lastest state, just send it the latest state.
    //   - If it asks for all operations from a given state, fetch those
    //   operations and send them.
    // All atomically, of course (that's what the AsyncQueue is for...)
    // (For example, you don't want to add it to the connection list before
    // sending these operations - or else it might send updates in the wrong
    // order.)
    this.queue.push((callback) => {
      this.connections.push(conn);

      if (data.latest === "yes") {
        // NOTE: unused currently; clients always start with
        // a state.
        socket.emit("state", {
          stateID: this.latestStateID,
          puzzle: this.latestState,
        });
        callback();
      } else {
        db.getOpsToLatest(this.puzzleID, data.from, (ops) => {
          let i = data.from + 1
          for (const op of ops) {
            socket.emit("update", {
              stateID: i,
              opID: op.opID,
              op: op.op,
            });
            i++;
          }
          socket.emit("update_cursor", {
            cursor_updates: this.connections.map((conn) => {
              return {id: conn.id, cursor: conn.cursor};
            }),
          });
          callback();
        });
      }
    });

    // What to do when you receive an "update" packet from the client.
    socket.on("update", (update_data) => {
      console.debug(`Received update ${update_data.opID} rooted at ${update_data.rootID}`);

      this.queue.push((callback) => {
        this.doesOpExist(update_data.opID, (exists) => {
          if (exists) {
            // If the operation has already been received, ignore it.
            // We're done here.
            callback();
          } else {
            // Otherwise, it's a new update that we have to process.
            // First, load the puzzle state that the operation is rooted at.
            // Then, load the operations that lead from that state to the
            // latest state.
            db.loadPuzzleState(this.puzzleID, update_data.rootID, (rootState) => {
              db.getOpsToLatest(this.puzzleID, update_data.rootID, (ops) => {
                let newOp = update_data.op;
                // TODO check for rootState being null
                // $FlowFixMe
                let newState: PuzzleState = rootState;
                // Transform the new operation against the operations
                // that already exist.
                for (const op of ops) {
                  const [a1, _] = Ot.xform(newState, newOp, op.op);
                  newState = Ot.apply(newState, op.op);
                  newOp = a1;
                }
                // TODO check for null
                newState = Ot.apply(this.latestState, newOp);
                // Save the new (transformed) op and the new state.
                db.saveOp(this.puzzleID, update_data.opID, newOp, newState, () => {
                  conn.cursor = update_data.cursor;

                  // Tell all the connections about the new update.
                  for (const connToSendTo of this.connections) {
                    connToSendTo.socket.emit("update", {
                      stateID: this.latestStateID + 1,
                      opID: update_data.opID,
                      op: newOp,
                      cursor_updates: [
                        {
                          user_id: conn.id,
                          // Don't send one's cursor to oneself.
                          cursor: connToSendTo === conn ? null : conn.cursor,
                        }
                      ],
                    });
                  }
                  this.latestState = newState;
                  this.latestStateID++;
                  callback();
                });
              });
            });
          }
        });
      });
    });

    socket.on("cursor_update", (update_data) => {
      conn.cursor = update_data.cursor;
      this.broadcast_except(conn, "update_cursor", {
        cursor_updates: [
          {
            user_id: conn.id,
            cursor: conn.cursor,
          }
        ]
      });
    });
  }

  doesOpExist(opID, callback) {
    db.getOpSeq(this.puzzleID, opID, (op) => {
      callback(op !== null);
    });
  }

  broadcast(msg, data) {
    this.broadcast_except(null, msg, data);
  }

  broadcast_except(connExc, msg, data) {
    for (const conn of this.connections) {
      if (conn !== connExc) {
        conn.socket.emit(msg, data);
      }
    }
  }
}
<|endoftext|>


<commit_before>Seed({
		name : "Mold.Lib.DomNode",
		dna : "class",
		include : [
			"Mold.Lib.DomParser",
			"Mold.Lib.Coder"
		]
	},
	function(type, name){

		var that = this;

		/*Errors*/
		var ERROR;

		/*node typs*/
		var nodeTypes = {
			ELEMENT_NODE : 1,
			ATTRIBUTE_NODE : 2,
			TEXT_NODE : 3,
			DOCUMENT_NODE : 9,
			DOCUMENT_FRAGMENT_NODE : 11
		}

		var _checkNodeType = function(type){
			return Mold.find(nodeTypes, function(value, name){
				if(type === value || type === name){
					return true;
				}
			})
			return false;
		}

		if(!_checkNodeType(type)){
			throw type + " is not a valid node-type"
		}

		/*standard properties*/

		this.nodeValue = "",
		this.nodeName = (name) ? name.toLowerCase() : false,
		this.nodeType = type,
		this.parentNode = false,
		this.nextSibling = false,
		this.previousSibling = false,
		this.firstChild = false,
		this.lastChild = false,
		this.parentElement = false,
		this.attributes = [],
		this.childNodes = [];

		var _parentNode = false;
		Object.defineProperty(this, 'parentNode', {
			get : function() {
				return _parentNode
			},
			set : function(nodeValue) {
				_parentNode = nodeValue;
				this.parentElement = nodeValue;
				return _parentNode;
			}
		});

		switch(type){
			case 3:
				this.nodeName = "#text";
				break;
			case 9:
				this.nodeName = "#document";
				break;

		}

		var _getInnerHTML = function(startNode){
			var markup = "";
			Mold.each(startNode.childNodes, function(node){
				if(node.nodeType === 1){
					markup += "<" + node.nodeName;
					if(node.attributes.length > 0 ){
						Mold.each(node.attributes, function(attrNode){
							markup += " " + attrNode.nodeName + "=\"" + attrNode.nodeValue + "\"";
						});
					}
					markup += ">";
					if(node.hasChildNodes()){
						markup += _getInnerHTML(node);
					}
					markup += "</" + node.nodeName + ">";
				}
				if(node.nodeType === 3){
					markup += node.nodeValue;
				}
			});
			return markup;
		}

		var _setInnerHTML = function(markup){
			var newDom = Mold.Lib.DomParser.parse(markup);
			that.childNodes = [];
			that.firstChild = newDom.firstChild;
			that.childNodes = newDom.childNodes;
			that.lastChild = newDom.lastChild;
		}

		Object.defineProperty(this, 'innerHTML', {
			get : function() {
				return _getInnerHTML(this);
			},
			set : function(markup) {
				return _setInnerHTML(markup);
			}
		});

		this.publics = {
			/*node properties*/

			cloneNode : function(withChildNodes){
				var cloneNode =  new Mold.Lib.DomNode(this.nodeType, this.nodeName);
				cloneNode.nodeValue = this.nodeValue;
				Mold.each(this.attributes, function(attribute){
					cloneNode.setAttributeNode(attribute.cloneNode());
				});
				if(withChildNodes){
					Mold.each(this.childNodes, function(child){
						cloneNode.appendChild(child.cloneNode(true));
					});
				}
				return cloneNode;
			},
			appendChild : function(child){
				if(!child){
					throw "Can not append child of undefiend!"
				}
				child.parentNode = this;
				this.childNodes.push(child);
				if(this.childNodes[this.childNodes.length - 2]){
					this.childNodes[this.childNodes.length - 2].nextSibling = child;
					child.previousSibling = this.childNodes[this.childNodes.length - 2];
				}
				if(this.childNodes.length === 1){

					this.firstChild = child;
				}
				this.lastChild = child;
			},
			insertBefore : function(newChild, referenz){
				var child,
					index = 0,
					len = this.childNodes.length;

				if(!referenz){
					this.appendChild(newChild);
					return true;
				}

				for(; index < len; index++){
					child =  this.childNodes[index];
					if(referenz === child){
						if(index === 0){
							that.firstChild = newChild;
						}else{
							that.childNodes[index - 1].nextSibling = newChild;
							newChild.previousSibling = that.childNodes[index - 1];
						}
						newChild.parentNode = that;
						newChild.nextSibling = that.childNodes[index];
						that.childNodes[index].previousSibling = newChild;
						that.childNodes.splice(index, 0, newChild);
						that.lastChild = that.childNodes[that.childNodes.length];
						break;
					}
				}
				return true;
			},
			removeChild : function(child){
				var i = 0,
					len = this.childNodes.length;

				if(!child){
					throw "Can not remove child of undefiend!"
				}

				for(; i < len; i++){
					if(this.childNodes[i] === child){
						if(i === 0){

							if(this.childNodes.length > 1){
								this.firstChild = this.childNodes[ i+1 ];
							}else{
								this.firstChild = false;
							}
						}
						if(this.childNodes[i - 1]){
							this.childNodes[i - 1].nextSibling = this.childNodes[i + 1];
						}
						if(this.childNodes[i + 1]){
							this.childNodes[i + 1].previousSibling = this.childNodes[i - 1];
						}else{
							if(this.childNodes[i - 1]){
								this.lastChild = this.childNodes[i - 1];
							}else{
								this.lastChild = false;
							}
						}
						this.childNodes.splice(i, 1);
						child.parentNode = false;
						return child;
					}
				}
				return false;
			},
			replaceChild : function(newChild, child){
				var i = 0,
					len = this.childNodes.length;

				for(; i < len; i++){
					if(this.childNodes[i] === child){
						if(i === 0){
							this.firstChild = child;
						}
						if(this.childNodes[i - 1]){
							this.childNodes[i - 1].nextSibling = newChild;
							newChild.previousSibling = this.childNodes[i - 1];
						}
						if(this.childNodes[i + 1]){
							this.childNodes[i + 1].previousSibling = newChild;
							newChild.nextSibling = this.childNodes[i + 1].previousSibling;
						}else{
							this.lastChild = newChild;
						}
						this.childNodes.splice(i, 1, newChild);

						child.parentNode = false;
						return true;
					}
				}
				return false;
			},
			hasChildNodes : function(){
				if(this.childNodes.length > 0){
					return true;
				}else{
					return false;
				}
			},

			getAttributeNode : function(name){
				return Mold.find(this.attributes, function(attribute){
					if(attribute.nodeName === name.toLowerCase()){
						return true;
					}
				})
			},
			getAttribute : function(name){
				var attributeNode = this.getAttributeNode(name);
				if(attributeNode){
					return attributeNode.nodeValue;
				}
				return false;
			},
			setAttributeNode : function(attribute){
				this.attributes.push(attribute);
			},
			setAttribute : function(name, value){
				var attribute = this.getAttributeNode(name);
				if(!attribute){
					attribute = new Mold.Lib.DomNode(2, name);
					this.setAttributeNode(attribute);
				}
				attribute.nodeValue = value;
			},
			removeAttributeNode : function(attribute){
				var i = 0,
					len = this.attributes.length;

				for(; i < len; i++){
					if(this.attributes[i] === attribute){
						this.attributes.splice(i, 1);
						attribute.parentNode = false;
						return true;
					}
				}
				return false;
			},
			removeAttribute : function(name){
				var attribute = this.getAttributeNode(name);
				if(attribute){
					this.removeAttributeNode(attribute);
				}
			}
		}
	}
)<commit_msg>Coder removed<commit_after>Seed({
		name : "Mold.Lib.DomNode",
		dna : "class",
		include : [
			"Mold.Lib.DomParser"
		]
	},
	function(type, name){

		var that = this;

		/*Errors*/
		var ERROR;

		/*node typs*/
		var nodeTypes = {
			ELEMENT_NODE : 1,
			ATTRIBUTE_NODE : 2,
			TEXT_NODE : 3,
			DOCUMENT_NODE : 9,
			DOCUMENT_FRAGMENT_NODE : 11
		}

		var _checkNodeType = function(type){
			return Mold.find(nodeTypes, function(value, name){
				if(type === value || type === name){
					return true;
				}
			})
			return false;
		}

		if(!_checkNodeType(type)){
			throw type + " is not a valid node-type"
		}

		/*standard properties*/

		this.nodeValue = "",
		this.nodeName = (name) ? name.toLowerCase() : false,
		this.nodeType = type,
		this.parentNode = false,
		this.nextSibling = false,
		this.previousSibling = false,
		this.firstChild = false,
		this.lastChild = false,
		this.parentElement = false,
		this.attributes = [],
		this.childNodes = [];

		var _parentNode = false;
		Object.defineProperty(this, 'parentNode', {
			get : function() {
				return _parentNode
			},
			set : function(nodeValue) {
				_parentNode = nodeValue;
				this.parentElement = nodeValue;
				return _parentNode;
			}
		});

		switch(type){
			case 3:
				this.nodeName = "#text";
				break;
			case 9:
				this.nodeName = "#document";
				break;

		}

		var _getInnerHTML = function(startNode){
			var markup = "";
			Mold.each(startNode.childNodes, function(node){
				if(node.nodeType === 1){
					markup += "<" + node.nodeName;
					if(node.attributes.length > 0 ){
						Mold.each(node.attributes, function(attrNode){
							markup += " " + attrNode.nodeName + "=\"" + attrNode.nodeValue + "\"";
						});
					}
					markup += ">";
					if(node.hasChildNodes()){
						markup += _getInnerHTML(node);
					}
					markup += "</" + node.nodeName + ">";
				}
				if(node.nodeType === 3){
					markup += node.nodeValue;
				}
			});
			return markup;
		}

		var _setInnerHTML = function(markup){
			var newDom = Mold.Lib.DomParser.parse(markup);
			that.childNodes = [];
			that.firstChild = newDom.firstChild;
			that.childNodes = newDom.childNodes;
			that.lastChild = newDom.lastChild;
		}

		Object.defineProperty(this, 'innerHTML', {
			get : function() {
				return _getInnerHTML(this);
			},
			set : function(markup) {
				return _setInnerHTML(markup);
			}
		});

		this.publics = {
			/*node properties*/

			cloneNode : function(withChildNodes){
				var cloneNode =  new Mold.Lib.DomNode(this.nodeType, this.nodeName);
				cloneNode.nodeValue = this.nodeValue;
				Mold.each(this.attributes, function(attribute){
					cloneNode.setAttributeNode(attribute.cloneNode());
				});
				if(withChildNodes){
					Mold.each(this.childNodes, function(child){
						cloneNode.appendChild(child.cloneNode(true));
					});
				}
				return cloneNode;
			},
			appendChild : function(child){
				if(!child){
					throw "Can not append child of undefiend!"
				}
				child.parentNode = this;
				this.childNodes.push(child);
				if(this.childNodes[this.childNodes.length - 2]){
					this.childNodes[this.childNodes.length - 2].nextSibling = child;
					child.previousSibling = this.childNodes[this.childNodes.length - 2];
				}
				if(this.childNodes.length === 1){

					this.firstChild = child;
				}
				this.lastChild = child;
			},
			insertBefore : function(newChild, referenz){
				var child,
					index = 0,
					len = this.childNodes.length;

				if(!referenz){
					this.appendChild(newChild);
					return true;
				}

				for(; index < len; index++){
					child =  this.childNodes[index];
					if(referenz === child){
						if(index === 0){
							that.firstChild = newChild;
						}else{
							that.childNodes[index - 1].nextSibling = newChild;
							newChild.previousSibling = that.childNodes[index - 1];
						}
						newChild.parentNode = that;
						newChild.nextSibling = that.childNodes[index];
						that.childNodes[index].previousSibling = newChild;
						that.childNodes.splice(index, 0, newChild);
						that.lastChild = that.childNodes[that.childNodes.length];
						break;
					}
				}
				return true;
			},
			removeChild : function(child){
				var i = 0,
					len = this.childNodes.length;

				if(!child){
					throw "Can not remove child of undefiend!"
				}

				for(; i < len; i++){
					if(this.childNodes[i] === child){
						if(i === 0){

							if(this.childNodes.length > 1){
								this.firstChild = this.childNodes[ i+1 ];
							}else{
								this.firstChild = false;
							}
						}
						if(this.childNodes[i - 1]){
							this.childNodes[i - 1].nextSibling = this.childNodes[i + 1];
						}
						if(this.childNodes[i + 1]){
							this.childNodes[i + 1].previousSibling = this.childNodes[i - 1];
						}else{
							if(this.childNodes[i - 1]){
								this.lastChild = this.childNodes[i - 1];
							}else{
								this.lastChild = false;
							}
						}
						this.childNodes.splice(i, 1);
						child.parentNode = false;
						return child;
					}
				}
				return false;
			},
			replaceChild : function(newChild, child){
				var i = 0,
					len = this.childNodes.length;

				for(; i < len; i++){
					if(this.childNodes[i] === child){
						if(i === 0){
							this.firstChild = child;
						}
						if(this.childNodes[i - 1]){
							this.childNodes[i - 1].nextSibling = newChild;
							newChild.previousSibling = this.childNodes[i - 1];
						}
						if(this.childNodes[i + 1]){
							this.childNodes[i + 1].previousSibling = newChild;
							newChild.nextSibling = this.childNodes[i + 1].previousSibling;
						}else{
							this.lastChild = newChild;
						}
						this.childNodes.splice(i, 1, newChild);

						child.parentNode = false;
						return true;
					}
				}
				return false;
			},
			hasChildNodes : function(){
				if(this.childNodes.length > 0){
					return true;
				}else{
					return false;
				}
			},

			getAttributeNode : function(name){
				return Mold.find(this.attributes, function(attribute){
					if(attribute.nodeName === name.toLowerCase()){
						return true;
					}
				})
			},
			getAttribute : function(name){
				var attributeNode = this.getAttributeNode(name);
				if(attributeNode){
					return attributeNode.nodeValue;
				}
				return false;
			},
			setAttributeNode : function(attribute){
				this.attributes.push(attribute);
			},
			setAttribute : function(name, value){
				var attribute = this.getAttributeNode(name);
				if(!attribute){
					attribute = new Mold.Lib.DomNode(2, name);
					this.setAttributeNode(attribute);
				}
				attribute.nodeValue = value;
			},
			removeAttributeNode : function(attribute){
				var i = 0,
					len = this.attributes.length;

				for(; i < len; i++){
					if(this.attributes[i] === attribute){
						this.attributes.splice(i, 1);
						attribute.parentNode = false;
						return true;
					}
				}
				return false;
			},
			removeAttribute : function(name){
				var attribute = this.getAttributeNode(name);
				if(attribute){
					this.removeAttributeNode(attribute);
				}
			}
		}
	}
)<|endoftext|>


<commit_before>"use strict";

function loadDocumentation()
{
    // Only download documentation if it hasn't been obtained already.
    if ( $( html.documentationContents ).text() === "" ) {
        downloadDocumentation();
    }
}

function downloadDocumentation()
{
    var documentationContainerElement = $( html.documentationContainer )[0];
    var loadingAnimation = new Spinner().spin( documentationContainerElement );

    $.get( constants.documentationUrl, function( data ) {

        // Remove the loading animation.
        loadingAnimation.stop();

        // Documentation downloaded. Paste it.
        $( html.documentationContents ).html( data );

        // Apply syntax highlighting.
        // But not for things inside the well, which is used for examples.
        $(':not(.well) pre code').each(function(i, block) {
            hljs.highlightBlock(block);
        });

        // Load the table of contents.
        generateDocumentationTableOfContents();

        // Define behaviour of the buttons to run the examples.
        setupDocumentationTryExampleButtons();

    });
}

function generateDocumentationTableOfContents()
{
    var parsedTableOfContents = parseHtmlHeaders( html.documentationContents );

    var tableOfContentsHtml =
        tableOfContentsHtmlFromData( parsedTableOfContents, false );

    $( html.documentationTableOfContents ).html( tableOfContentsHtml );

    $( 'body' ).scrollspy({
        target: html.sidebarClass,
        offset: 130
    });

    $( html.documentationTableOfContentsList ).affix({
        offset: {
            top: 100,
        }
    });
}

function setupDocumentationTryExampleButtons()
{
    var allButtonsAndCode = $( html.documentationContents ).find(
        html.documentationTryExampleButtons + ", code" );

    allButtonsAndCode.each( function( index, element ) {
        var tagName = $( element ).prop( "tagName" );
        if ( tagName === "BUTTON" ) {
            $( element ).click( function() {
                var storyTitle = "Example";
                var storyAuthors = "Story Creator";
                var storyCode = $( allButtonsAndCode[index + 1] )
                                    .text().trim();

                loadStoryFromDocumentation( storyTitle,
                    storyAuthors, storyCode );
            });
        }
    });
}

function parseHtmlHeaders( root )
{
    var tableOfContentsEntries = [];

    $( root ).find( "h1, h2" ).each( function( index, header ) {

        // Get the header properties.
        var level = $( header ).prop( "tagName" );
        var title = $( header ).text();
        var id = $( header ).attr( "id" );

        // Assign ID if none exists.
        if ( !id ) {
            id = "documentation-header-" + index;
            $( header ).attr( "id", id );
        }

        // Populate the tree.
        if ( level === "H1" ) {
            tableOfContentsEntries.push(
                tableOfContentsEntry( title, id, [] ) );
        } else if ( level === "H2" ) {
            var latestEntryH1 = tableOfContentsEntries.pop();
            latestEntryH1.children.push(
                tableOfContentsEntry( title, id, [] ) );
            tableOfContentsEntries.push( latestEntryH1 );
        }
    });

    return tableOfContentsEntries;
}

function tableOfContentsEntry( title, id, children )
{
    return {
        title: title,
        id: id,
        children: children,
    };
}

function tableOfContentsHtmlFromData( tableOfContents )
{
    var listItems = tableOfContents.map( function( entry ) {
        return '
' +
               '' +
                entry.title +
                tableOfContentsHtmlFromData( entry.children ) +
               '' +
               '
';
    });

    if ( listItems.length > 0 ) {
        return '
' +
                listItems.join( constants.newlineCharacter ) +
                '
';
    }

    return "";
}
<commit_msg>Better example story title.<commit_after>"use strict";

function loadDocumentation()
{
    // Only download documentation if it hasn't been obtained already.
    if ( $( html.documentationContents ).text() === "" ) {
        downloadDocumentation();
    }
}

function downloadDocumentation()
{
    var documentationContainerElement = $( html.documentationContainer )[0];
    var loadingAnimation = new Spinner().spin( documentationContainerElement );

    $.get( constants.documentationUrl, function( data ) {

        // Remove the loading animation.
        loadingAnimation.stop();

        // Documentation downloaded. Paste it.
        $( html.documentationContents ).html( data );

        // Apply syntax highlighting.
        // But not for things inside the well, which is used for examples.
        $(':not(.well) pre code').each(function(i, block) {
            hljs.highlightBlock(block);
        });

        // Load the table of contents.
        generateDocumentationTableOfContents();

        // Define behaviour of the buttons to run the examples.
        setupDocumentationTryExampleButtons();

    });
}

function generateDocumentationTableOfContents()
{
    var parsedTableOfContents = parseHtmlHeaders( html.documentationContents );

    var tableOfContentsHtml =
        tableOfContentsHtmlFromData( parsedTableOfContents, false );

    $( html.documentationTableOfContents ).html( tableOfContentsHtml );

    $( 'body' ).scrollspy({
        target: html.sidebarClass,
        offset: 130
    });

    $( html.documentationTableOfContentsList ).affix({
        offset: {
            top: 100,
        }
    });
}

function setupDocumentationTryExampleButtons()
{
    var allButtonsAndCode = $( html.documentationContents ).find(
        html.documentationTryExampleButtons + ", code" );

    allButtonsAndCode.each( function( index, element ) {
        var tagName = $( element ).prop( "tagName" );
        if ( tagName === "BUTTON" ) {
            $( element ).click( function() {
                var storyTitle = "Example Story";
                var storyAuthors = "Story Creator";
                var storyCode = $( allButtonsAndCode[index + 1] )
                                    .text().trim();

                loadStoryFromDocumentation( storyTitle,
                    storyAuthors, storyCode );
            });
        }
    });
}

function parseHtmlHeaders( root )
{
    var tableOfContentsEntries = [];

    $( root ).find( "h1, h2" ).each( function( index, header ) {

        // Get the header properties.
        var level = $( header ).prop( "tagName" );
        var title = $( header ).text();
        var id = $( header ).attr( "id" );

        // Assign ID if none exists.
        if ( !id ) {
            id = "documentation-header-" + index;
            $( header ).attr( "id", id );
        }

        // Populate the tree.
        if ( level === "H1" ) {
            tableOfContentsEntries.push(
                tableOfContentsEntry( title, id, [] ) );
        } else if ( level === "H2" ) {
            var latestEntryH1 = tableOfContentsEntries.pop();
            latestEntryH1.children.push(
                tableOfContentsEntry( title, id, [] ) );
            tableOfContentsEntries.push( latestEntryH1 );
        }
    });

    return tableOfContentsEntries;
}

function tableOfContentsEntry( title, id, children )
{
    return {
        title: title,
        id: id,
        children: children,
    };
}

function tableOfContentsHtmlFromData( tableOfContents )
{
    var listItems = tableOfContents.map( function( entry ) {
        return '
' +
               '' +
                entry.title +
                tableOfContentsHtmlFromData( entry.children ) +
               '' +
               '
';
    });

    if ( listItems.length > 0 ) {
        return '
' +
                listItems.join( constants.newlineCharacter ) +
                '
';
    }

    return "";
}
<|endoftext|>