<commit_before>/**
 * @fileOverview
 * avatar.enchant.js
 * @version 0.2 (2012/08/09)
 * @requires enchant.js v0.4.0 or later
 * @author shi3z/Ubiquitous Entertainment Inc.
 *
 * @description
 * enchant.js extension for 9leap.net
 * Avatar character plugin for enchant.js with 9leap.net
 *
 * @example
 *
 *  var game = new Game(320, 320);
 * game.preload('avatarBg1.png','avatarBg2.png','avatarBg3.png','bigmonster1.gif');
 * game.onload = function(){
 * 		game.rootScene.backgroundColor="#000000";
 * 		// show infinite scrolling background
 * 		bg =new AvatarBG(0);
 * 		bg.y=50;
 * 		game.rootScene.addChild(bg);
 *
 * 		//show monster
 * 		monster = new AvatarMonster(game.assets['bigmonster1.gif']);
 * 		monster.x=200;
 * 		monster.y=100;
 * 		game.rootScene.addChild(monster);
 *
 * 		//show avatar
 * 		chara = new Avatar("2:2:1:2004:21230:22480");
 * 		game.rootScene.addChild(chara);
 * 		chara.scaleX=-1;
 * 		chara.scaleY=1;
 * 		chara.x=50;
 * 		chara.y=100;
 *  };
 * game.start();
 */

/**
 * avatar namespace object
 * @type {Object}
 */
enchant.avatar = {};

/**
 * AvatarCharacter
 * Base class of enchant.Avatar and enchant.AvatarMonster
 * @scope enchant.avatar.AvatarCharacter.prototype
 */
enchant.avatar.AvatarCharacter = enchant.Class.create(enchant.Sprite, {
    /**
     * Constructor of AvatarCharacter
     * @constructs
     * @param width
     * @param height
     */
    initialize: function(width, height) {
        enchant.Sprite.call(this, width, height);
        this.right();

        /**
         * Name of animation pattern;
         * @type {String}
         */
        this.action = "stop";

        /**
         * Array of animation patterns
         * @type {Object}
         */
        this.animPattern = { "stop": [ 0]};

        /**
         * Frame number of animation
         * @type {Object}
         */
        this.animFrame = 0;
        this.addEventListener('enterframe', function() {
            if ((~~(this.age) & 0x03) != 0)return;
            if (this.action) {
                var animPattern = this.animPattern[this.action];
                this.frame = animPattern[this.animFrame];
                this.animFrame++;
                if (animPattern[this.animFrame] == -1) {
                    this.animFrame = 0;
                    this.action = "stop";
                }
                if (animPattern[this.animFrame] == -2) {
                    this.parentNode.removeChild(this);
                }
                if (this.animFrame >= animPattern.length)
                    this.animFrame = 0;
            }
        });
    },

    /**
     * Flip to left
     */
    left: function() {
        this.scaleX = 1;
    },
    /**
     * Flip to right
     */
    right: function() {
        this.scaleX = -1;
    }
});

/**
 * AvatarMonster
 * subclass of enchant.avatar.AvatarCharacter
 * @scope enchant.avatar.AvatarMonster.prototype
 */
enchant.avatar.AvatarMonster = enchant.Class.create(enchant.avatar.AvatarCharacter, {
    /**
     * AvatarMonster
     * Manage a monter animations
     * @constructs
     * @param {image} Image of monster
     * @extends enchant.avatar.AvatarCharacter
     */
    initialize: function(image) {
        var w = ~~(image.width / 4);
        var h = w;
        enchant.avatar.AvatarCharacter.call(this, w, h);
        this.image = image;
        this.animPattern = { "stop": [ 4, 4, 4, 3, 3, 3],
            "walk": [ 2, 3, 4, 3],
            "appear": [ 0, 1, 7, 6, 5, 4, 2, 3, -1],
            "disappear": [ 3, 2, 4, 5, 6, 7, 1, 0, -2],
            "attack": [ 5, 4, 6, 6, 6, -1],
        };
        this.action = "attack";
        this.left();
    }
});

/**
 * AvatarBG
 * @scope enchant.AvatarBG.prototype
 */
enchant.avatar.AvatarBG = enchant.Class.create(enchant.Group, {
    /**
     * A class of infinite scrolling background.
     * @param mode {Number} 0 to 3
     * @constructs
     * @extends enchant.Group
     */
    initialize: function(mode) {
        enchant.Group.call(this);

        this.veryfarbg = new Sprite(320, 51);
        this.veryfarbg.y = 0;
        this.veryfarbg.image = game.assets["avatarBg2.png"];
        this.veryfarbg.frame = mode;
        this.addChild(this.veryfarbg);
        this.farbgs = [];
        for (i = 0; i < 3; i++) {
            var bg = new Sprite(320, 32);
            bg.image = game.assets["avatarBg3.png"];
            bg.frame = mode;
            bg.x = i * 320 - 320;
            bg.y = 20;
            this.farbgs[this.farbgs.length] = bg;
            this.addChild(bg);
        }

        this.tiles = [];
        for (i = 0; i < 14; i++) {
            var tile = new Sprite(32, 128);
            tile.image = game.assets["avatarBg1.png"];
            tile.frame = mode;
            tile.x = i * 31 - 48;
            tile.y = 48;
            this.tiles[this.tiles.length] = tile;
            this.addChild(tile);
        }
    },
    /**
     * horizontal scrolling.
     * @param {int} x Offset of x coordinate.
     */
    scroll: function(x) {
        var dx = ~~(x / 2) % 320, ddx = ~~(x) % 32 * 2;
        for (i = 0; i < 14; i++) {
            this.tiles[i].x = i * 31 - ddx - 48;
        }
        for (i = 0; i < 3; i++) {
            this.farbgs[i].x = i * 320 - dx - 320;
        }
    }
});

/**
 * Avatar
 * @scope enchant.Avatar.prototype
 */
enchant.avatar.Avatar = enchant.Class.create(enchant.avatar.AvatarCharacter, {
    /**
     * @param {int}code  Avatar code
     * @extends enchant.avatar.AvatarCharacter
     * @constructs
     */
    initialize: function(code) {
        enchant.avatar.AvatarCharacter.call(this, 64, 64);
        if (code) {
            this.setCode(code);
        } else {
            this.gender = 1;
            this.hairstyle = 1;
            this.haircolor = 0;
            this.weapon = 2545;
            this.armor = 2135;
            this.headpiece = 0;
            this.loadImage();
        }
        this.animPattern = { "stop": [ 0],
            "run": [ 1, 2, 3, 2],
            "attack": [ 0, 2, 9, 10, 11, 5, 0, 0, 0, -1],
            "special": [ 0, 6, 5, 13, 14, 15, 0, 0, -1],
            "damage": [ 7, 7, 7, 7, 0, 0, 0, -1],
            "dead": [8],
            "demo": [ 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 0, 0, 0, 0, 2, 9, 10, 11, 5, 0, 0, 0,
                1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 0, 6, 5, 13, 14, 15, 0, 0]
        };

    },
    /**
     * Reflesh avatar animation image
     */
    loadImage: function() {
        var ___EnchantAvatarServerURL = "http://9leap.net/meruru/draw/draw.php";
        this.opt = "gender=" + this.gender + "&job=0&hairstyle=" + this.hairstyle + "&haircolor=" + this.haircolor + "&weapon=" + this.weapon + "&armor=" + this.armor + "&headpiece=" + this.headpiece + "&invisible=0&x=0&y=0&w=4&h=4&dummy=.gif";
        this.src = ___EnchantAvatarServerURL + "?" + this.opt;
        (function(that) {
            var game = enchant.Game.instance;
            game.load(that.src, function() {
                that.image = game.assets[that.src];
            });
        })(this);
    },
    /**
     * Get avatar code from actual object
     * @return {String} code;
     */
    getCode: function() {
        return this.gender + ":" + this.hairstyle + ":" + this.haircolor + ":" +
            this.weapon + ":" + this.armor + ":" + this.headpiece;
    },
    /**
     * Set avatar code and reflesh avatar image.
     * @param {String} code
     */
    setCode: function(code) {
        data = code.split(":");
        this.gender = data[0];
        this.hairstyle = data[1];
        this.haircolor = data[2];
        this.weapon = data[3];
        this.armor = data[4];
        this.headpiece = data[5];
        this.loadImage();
    }
});<commit_msg>[hotfix] namespace bugfix on avatar.enchant.js<commit_after>/**
 * @fileOverview
 * avatar.enchant.js
 * @version 0.2 (2012/08/09)
 * @requires enchant.js v0.4.0 or later
 * @author shi3z/Ubiquitous Entertainment Inc.
 *
 * @description
 * enchant.js extension for 9leap.net
 * Avatar character plugin for enchant.js with 9leap.net
 *
 * @example
 *
 *  var game = new Game(320, 320);
 * game.preload('avatarBg1.png','avatarBg2.png','avatarBg3.png','bigmonster1.gif');
 * game.onload = function(){
 *         game.rootScene.backgroundColor="#000000";
 *         // show infinite scrolling background
 *         bg =new AvatarBG(0);
 *         bg.y=50;
 *         game.rootScene.addChild(bg);
 *
 *         //show monster
 *         monster = new AvatarMonster(game.assets['bigmonster1.gif']);
 *         monster.x=200;
 *         monster.y=100;
 *         game.rootScene.addChild(monster);
 *
 *         //show avatar
 *         chara = new Avatar("2:2:1:2004:21230:22480");
 *         game.rootScene.addChild(chara);
 *         chara.scaleX=-1;
 *         chara.scaleY=1;
 *         chara.x=50;
 *         chara.y=100;
 *  };
 * game.start();
 */

/**
 * avatar namespace object
 * @type {Object}
 */
enchant.avatar = {};

/**
 * AvatarCharacter
 * Base class of enchant.Avatar and enchant.AvatarMonster
 * @scope enchant.avatar.AvatarCharacter.prototype
 */
enchant.avatar.AvatarCharacter = enchant.Class.create(enchant.Sprite, {
    /**
     * Constructor of AvatarCharacter
     * @constructs
     * @param width
     * @param height
     */
    initialize: function(width, height) {
        enchant.Sprite.call(this, width, height);
        this.right();

        /**
         * Name of animation pattern;
         * @type {String}
         */
        this.action = "stop";

        /**
         * Array of animation patterns
         * @type {Object}
         */
        this.animPattern = { "stop": [ 0]};

        /**
         * Frame number of animation
         * @type {Object}
         */
        this.animFrame = 0;
        this.addEventListener('enterframe', function() {
            if ((~~(this.age) & 0x03) !== 0)return;
            if (this.action) {
                var animPattern = this.animPattern[this.action];
                this.frame = animPattern[this.animFrame];
                this.animFrame++;
                if (animPattern[this.animFrame] === -1) {
                    this.animFrame = 0;
                    this.action = "stop";
                }
                if (animPattern[this.animFrame] === -2) {
                    this.parentNode.removeChild(this);
                }
                if (this.animFrame >= animPattern.length) {
                    this.animFrame = 0;
                }
            }
        });
    },

    /**
     * Flip to left
     */
    left: function() {
        this.scaleX = 1;
    },
    /**
     * Flip to right
     */
    right: function() {
        this.scaleX = -1;
    }
});

/**
 * AvatarMonster
 * subclass of enchant.avatar.AvatarCharacter
 * @scope enchant.avatar.AvatarMonster.prototype
 */
enchant.avatar.AvatarMonster = enchant.Class.create(enchant.avatar.AvatarCharacter, {
    /**
     * AvatarMonster
     * Manage a monter animations
     * @constructs
     * @param {image} Image of monster
     * @extends enchant.avatar.AvatarCharacter
     */
    initialize: function(image) {
        var w = ~~(image.width / 4);
        var h = w;
        enchant.avatar.AvatarCharacter.call(this, w, h);
        this.image = image;
        this.animPattern = { "stop": [ 4, 4, 4, 3, 3, 3],
            "walk": [ 2, 3, 4, 3],
            "appear": [ 0, 1, 7, 6, 5, 4, 2, 3, -1],
            "disappear": [ 3, 2, 4, 5, 6, 7, 1, 0, -2],
            "attack": [ 5, 4, 6, 6, 6, -1]
        };
        this.action = "attack";
        this.left();
    }
});

/**
 * AvatarBG
 * @scope enchant.AvatarBG.prototype
 */
enchant.avatar.AvatarBG = enchant.Class.create(enchant.Group, {
    /**
     * A class of infinite scrolling background.
     * @param mode {Number} 0 to 3
     * @constructs
     * @extends enchant.Group
     */
    initialize: function(mode) {
        enchant.Group.call(this);

        this.veryfarbg = new enchant.Sprite(320, 51);
        this.veryfarbg.y = 0;
        this.veryfarbg.image = game.assets["avatarBg2.png"];
        this.veryfarbg.frame = mode;
        this.addChild(this.veryfarbg);
        this.farbgs = [];
        for (i = 0; i < 3; i++) {
            var bg = new enchant.Sprite(320, 32);
            bg.image = game.assets["avatarBg3.png"];
            bg.frame = mode;
            bg.x = i * 320 - 320;
            bg.y = 20;
            this.farbgs[this.farbgs.length] = bg;
            this.addChild(bg);
        }

        this.tiles = [];
        for (i = 0; i < 14; i++) {
            var tile = new enchant.Sprite(32, 128);
            tile.image = game.assets["avatarBg1.png"];
            tile.frame = mode;
            tile.x = i * 31 - 48;
            tile.y = 48;
            this.tiles[this.tiles.length] = tile;
            this.addChild(tile);
        }
    },
    /**
     * horizontal scrolling.
     * @param {int} x Offset of x coordinate.
     */
    scroll: function(x) {
        var dx = ~~(x / 2) % 320, ddx = ~~(x) % 32 * 2;
        for (i = 0; i < 14; i++) {
            this.tiles[i].x = i * 31 - ddx - 48;
        }
        for (i = 0; i < 3; i++) {
            this.farbgs[i].x = i * 320 - dx - 320;
        }
    }
});

/**
 * Avatar
 * @scope enchant.Avatar.prototype
 */
enchant.avatar.Avatar = enchant.Class.create(enchant.avatar.AvatarCharacter, {
    /**
     * @param {int}code  Avatar code
     * @extends enchant.avatar.AvatarCharacter
     * @constructs
     */
    initialize: function(code) {
        enchant.avatar.AvatarCharacter.call(this, 64, 64);
        if (code) {
            this.setCode(code);
        } else {
            this.gender = 1;
            this.hairstyle = 1;
            this.haircolor = 0;
            this.weapon = 2545;
            this.armor = 2135;
            this.headpiece = 0;
            this.loadImage();
        }
        this.animPattern = { "stop": [ 0],
            "run": [ 1, 2, 3, 2],
            "attack": [ 0, 2, 9, 10, 11, 5, 0, 0, 0, -1],
            "special": [ 0, 6, 5, 13, 14, 15, 0, 0, -1],
            "damage": [ 7, 7, 7, 7, 0, 0, 0, -1],
            "dead": [8],
            "demo": [ 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 0, 0, 0, 0, 2, 9, 10, 11, 5, 0, 0, 0,
                1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3, 2, 0, 6, 5, 13, 14, 15, 0, 0]
        };

    },
    /**
     * Reflesh avatar animation image
     */
    loadImage: function() {
        var ___EnchantAvatarServerURL = "http://9leap.net/meruru/draw/draw.php";
        this.opt = "gender=" + this.gender + "&job=0&hairstyle=" + this.hairstyle + "&haircolor=" + this.haircolor + "&weapon=" + this.weapon + "&armor=" + this.armor + "&headpiece=" + this.headpiece + "&invisible=0&x=0&y=0&w=4&h=4&dummy=.gif";
        this.src = ___EnchantAvatarServerURL + "?" + this.opt;
        (function(that) {
            var game = enchant.Game.instance;
            game.load(that.src, function() {
                that.image = game.assets[that.src];
            });
        })(this);
    },
    /**
     * Get avatar code from actual object
     * @return {String} code;
     */
    getCode: function() {
        return this.gender + ":" + this.hairstyle + ":" + this.haircolor + ":" +
            this.weapon + ":" + this.armor + ":" + this.headpiece;
    },
    /**
     * Set avatar code and reflesh avatar image.
     * @param {String} code
     */
    setCode: function(code) {
        data = code.split(":");
        this.gender = data[0];
        this.hairstyle = data[1];
        this.haircolor = data[2];
        this.weapon = data[3];
        this.armor = data[4];
        this.headpiece = data[5];
        this.loadImage();
    }
});<|endoftext|>


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

foam.CLASS({
  package: 'foam.u2.view',
  name: 'TreeViewRow',
  extends: 'foam.u2.Element',

  requires: [
    'foam.mlang.ExpressionsSingleton'
  ],

  exports: [
    'data'
  ],

  imports: [
    'selection',
    'onObjDrop'
  ],

  css: `
    ^ { white-space: nowrap; margin-left: 20px; }
    ^selected > ^label { outline: 2px solid #dddd00; }
  `,

  properties: [
    {
      name: 'data'
    },
    {
      name: 'relationship'
    },
    {
      class: 'Boolean',
      name: 'expanded',
      value: false
    },
    {
      class: 'Function',
      name: 'formatter'
    }
  ],

  methods: [
    function initE() {
      var self = this;
      this.
        addClass(this.myClass()).
        addClass(this.slot(function(selected, id) {
          if ( selected && foam.util.equals(selected.id, id) ) {
            return this.myClass('selected');
          }
          return '';
        }, this.selection$, this.data$.dot('id'))).
        attrs({ draggable: 'true' }).
        start('span').
          on('click', this.toggleExpanded).
          add(this.expanded$.map(function(v) { return v ? '\u25BD' : '\u25B7'; })).
          entity('nbsp').
        end().
        on('click', this.selected).
        on('dragstart', this.onDragStart).
        on('dragenter', this.onDragOver).
        on('dragover', this.onDragOver).
        on('drop', this.onDrop).
        start('span').addClass(self.myClass('label')).call(this.formatter, [self.data]).end().
        add(this.slot(function(e) {
          if ( ! e ) return this.E('div');
          var e2 = this.E('div');
          e2.select(this.data[self.relationship.forwardName]/*.dao*/, function(obj) {
            return self.cls_.create({
              data: obj,
              formatter: self.formatter,
              relationship: self.relationship
            }, this);
          });
          return e2;
        }, this.expanded$));
    }
  ],

  listeners: [
    function onDragStart(e) {
      e.dataTransfer.setData('application/x-foam-obj-id', this.data.id);
      e.stopPropagation();
    },

    function onDragOver(e) {
      if ( ! e.dataTransfer.types.some(function(m) { return m === 'application/x-foam-obj-id'; }) )
        return;

      var id = e.dataTransfer.getData('application/x-foam-obj-id');

      if ( foam.util.equals(id, this.data.id) )
        return;

      e.preventDefault();
      e.stopPropagation();
    },

    function onDrop(e) {
      if ( ! e.dataTransfer.types.some(function(m) { return m === 'application/x-foam-obj-id'; }) )
        return;

      var id = e.dataTransfer.getData('application/x-foam-obj-id');

      if ( foam.util.equals(id, this.data.id) ) return;

      e.preventDefault();
      e.stopPropagation();

      var self = this;
      var dao  = this.__context__[this.relationship.targetDAOKey];
      dao.find(id).then(function(obj) {
        if ( ! obj ) return null;

        // TODO: We shouldn't have to remove then put,
        // We currently have to because the FLOW editor is not updating properly
        // on a put event for an object that it already has.
        dao.remove(obj).then(function() {
          self.data[self.relationship.forwardName].dao.put(obj).then(function(obj) {
            self.onObjDrop(obj, id);
          });
        });
      });
    },

    function selected(e) {
      this.selection = this.data;
      e.preventDefault();
      e.stopPropagation();
    },

    function toggleExpanded(e) {
      this.expanded = ! this.expanded;
      e.preventDefault();
      e.stopPropagation();
    }
  ]
});

foam.CLASS({
  package: 'foam.u2.view',
  name: 'TreeView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.mlang.ExpressionsSingleton',
    'foam.u2.view.TreeViewRow'
  ],

  exports: [
    'onObjDrop',
    'selection'
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      name: 'relationship'
    },
    {
      name: 'selection'
    },
    {
      class: 'Function',
      name: 'formatter'
    },
    {
      class: 'Boolean',
      name: 'startExpanded',
      value: false
    }
  ],

  methods: [
    function initE() {
      var M   = this.ExpressionsSingleton.create();
      var of  = this.lookup(this.relationship.sourceModel);
      var dao = this.data$proxy.where(
        M.NOT(M.HAS(of.getAxiomByName(this.relationship.inverseName))));

      var self = this;
      this.addClass(this.myClass()).
        select(dao, function(obj) {
          return self.TreeViewRow.create({
            data: obj,
            relationship: self.relationship,
            expanded: self.startExpanded,
            formatter: self.formatter
          }, this);
        });
    },

    function onObjDrop(obj, target) {
      // Template Method
    }
  ]
});
<commit_msg>Change TreeView selected outline colour.<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'TreeViewRow',
  extends: 'foam.u2.Element',

  requires: [
    'foam.mlang.ExpressionsSingleton'
  ],

  exports: [
    'data'
  ],

  imports: [
    'selection',
    'onObjDrop'
  ],

  css: `
    ^ { white-space: nowrap; margin-left: 20px; }
    ^selected > ^label { outline: 2px solid #59aadd; }
  `,

  properties: [
    {
      name: 'data'
    },
    {
      name: 'relationship'
    },
    {
      class: 'Boolean',
      name: 'expanded',
      value: false
    },
    {
      class: 'Function',
      name: 'formatter'
    }
  ],

  methods: [
    function initE() {
      var self = this;
      this.
        addClass(this.myClass()).
        addClass(this.slot(function(selected, id) {
          if ( selected && foam.util.equals(selected.id, id) ) {
            return this.myClass('selected');
          }
          return '';
        }, this.selection$, this.data$.dot('id'))).
        attrs({ draggable: 'true' }).
        start('span').
          on('click', this.toggleExpanded).
          add(this.expanded$.map(function(v) { return v ? '\u25BD' : '\u25B7'; })).
          entity('nbsp').
        end().
        on('click', this.selected).
        on('dragstart', this.onDragStart).
        on('dragenter', this.onDragOver).
        on('dragover', this.onDragOver).
        on('drop', this.onDrop).
        start('span').addClass(self.myClass('label')).call(this.formatter, [self.data]).end().
        add(this.slot(function(e) {
          if ( ! e ) return this.E('div');
          var e2 = this.E('div');
          e2.select(this.data[self.relationship.forwardName]/*.dao*/, function(obj) {
            return self.cls_.create({
              data: obj,
              formatter: self.formatter,
              relationship: self.relationship
            }, this);
          });
          return e2;
        }, this.expanded$));
    }
  ],

  listeners: [
    function onDragStart(e) {
      e.dataTransfer.setData('application/x-foam-obj-id', this.data.id);
      e.stopPropagation();
    },

    function onDragOver(e) {
      if ( ! e.dataTransfer.types.some(function(m) { return m === 'application/x-foam-obj-id'; }) )
        return;

      var id = e.dataTransfer.getData('application/x-foam-obj-id');

      if ( foam.util.equals(id, this.data.id) )
        return;

      e.preventDefault();
      e.stopPropagation();
    },

    function onDrop(e) {
      if ( ! e.dataTransfer.types.some(function(m) { return m === 'application/x-foam-obj-id'; }) )
        return;

      var id = e.dataTransfer.getData('application/x-foam-obj-id');

      if ( foam.util.equals(id, this.data.id) ) return;

      e.preventDefault();
      e.stopPropagation();

      var self = this;
      var dao  = this.__context__[this.relationship.targetDAOKey];
      dao.find(id).then(function(obj) {
        if ( ! obj ) return null;

        // TODO: We shouldn't have to remove then put,
        // We currently have to because the FLOW editor is not updating properly
        // on a put event for an object that it already has.
        dao.remove(obj).then(function() {
          self.data[self.relationship.forwardName].dao.put(obj).then(function(obj) {
            self.onObjDrop(obj, id);
          });
        });
      });
    },

    function selected(e) {
      this.selection = this.data;
      e.preventDefault();
      e.stopPropagation();
    },

    function toggleExpanded(e) {
      this.expanded = ! this.expanded;
      e.preventDefault();
      e.stopPropagation();
    }
  ]
});

foam.CLASS({
  package: 'foam.u2.view',
  name: 'TreeView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.mlang.ExpressionsSingleton',
    'foam.u2.view.TreeViewRow'
  ],

  exports: [
    'onObjDrop',
    'selection'
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      name: 'relationship'
    },
    {
      name: 'selection'
    },
    {
      class: 'Function',
      name: 'formatter'
    },
    {
      class: 'Boolean',
      name: 'startExpanded',
      value: false
    }
  ],

  methods: [
    function initE() {
      var M   = this.ExpressionsSingleton.create();
      var of  = this.lookup(this.relationship.sourceModel);
      var dao = this.data$proxy.where(
        M.NOT(M.HAS(of.getAxiomByName(this.relationship.inverseName))));

      var self = this;
      this.addClass(this.myClass()).
        select(dao, function(obj) {
          return self.TreeViewRow.create({
            data: obj,
            relationship: self.relationship,
            expanded: self.startExpanded,
            formatter: self.formatter
          }, this);
        });
    },

    function onObjDrop(obj, target) {
      // Template Method
    }
  ]
});
<|endoftext|>


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

foam.CLASS({
  package: 'foam.u2.view',
  name: 'TreeViewRow',
  extends: 'foam.u2.Element',
  requires: [
    'foam.mlang.ExpressionsSingleton'
  ],
  exports: [
    'data'
  ],
  imports: [
    'selection'
  ],
  properties: [
    {
      name: 'data'
    },
    {
      name: 'relationship'
    },
    {
      class: 'Boolean',
      name: 'expanded',
      value: false
    },
    {
      class: 'Function',
      name: 'formatter'
    }
  ],
  methods: [
    function initE() {
      var self = this;
      this.
        cssClass(this.myCls()).
        start('span').
          add(this.expanded$.map(function(v) { return v ? '-' : '+'; })).
          on('click', this.toggleExpanded).
        end().
        on('click', this.selected).
        call(this.formatter).
        end().
        add(this.slot(function(e) {
          if ( ! e ) return this.E('div');

          var e = this.E('div');
          e.select(this.data[self.relationship.name], function(obj) {
            return self.cls_.create({
              data: obj,
              formatter: self.formatter,
              relationship: self.relationship
            }, this);
          });
          return e;
        }, this.expanded$));
    }
  ],
  listeners: [
    function selected() {
      this.selection = this.data;
    },
    function toggleExpanded(e) {
      this.expanded = ! this.expanded;
      e.preventDefault();
      e.stopPropagation();
    }
  ]
});

foam.CLASS({
  package: 'foam.u2.view',
  name: 'TreeView',
  extends: 'foam.u2.Element',
  requires: [
    'foam.mlang.ExpressionsSingleton',
    'foam.u2.view.TreeViewRow'
  ],
  exports: [
    'selection'
  ],
  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      name: 'relationship'
    },
    {
      name: 'selection'
    },
    {
      class: 'Function',
      name: 'formatter'
    }
  ],
  methods: [
    function initE() {
      var M = this.ExpressionsSingleton.create();
      var of = foam.lookup(this.relationship.sourceModel);

      var dao = this.data$proxy.where(
        M.NOT(M.HAS(of.getAxiomByName(this.relationship.inverseName))));

      var self = this;
      this.cssClass(this.myCls()).
        select(dao, function(obj) {
          return self.TreeViewRow.create({
            data: obj,
            relationship: self.relationship,
            formatter: self.formatter
          }, this);
        });
    }
  ]
});
<commit_msg>Add drag+drop support to tree view.<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'TreeViewRow',
  extends: 'foam.u2.Element',
  requires: [
    'foam.mlang.ExpressionsSingleton'
  ],
  exports: [
    'data'
  ],
  imports: [
    'selection'
  ],
  properties: [
    {
      name: 'data'
    },
    {
      name: 'relationship'
    },
    {
      class: 'Boolean',
      name: 'expanded',
      value: false
    },
    {
      class: 'Function',
      name: 'formatter'
    }
  ],
  methods: [
    function initE() {
      var self = this;
      this.
        cssClass(this.myCls()).
        attrs({ draggable: 'true' }).
        start('span').
          add(this.expanded$.map(function(v) { return v ? '-' : '+'; })).
          on('click', this.toggleExpanded).
        end().
        on('click', this.selected).
        on('dragstart', this.onDragStart).
        on('dragenter', this.onDragOver).
        on('dragover', this.onDragOver).
        on('drop', this.onDrop).
        call(this.formatter).
        end().
        add(this.slot(function(e) {
          if ( ! e ) return this.E('div');

          var e = this.E('div');
          e.select(this.data[self.relationship.name], function(obj) {
            return self.cls_.create({
              data: obj,
              formatter: self.formatter,
              relationship: self.relationship
            }, this);
          });
          return e;
        }, this.expanded$));
    }
  ],
  listeners: [
    function onDragStart(e) {
      e.dataTransfer.setData('application/x-foam-obj-id', this.data.id);
    },
    function onDragOver(e) {
      if ( ! e.dataTransfer.types.some(function(m) { return m === 'application/x-foam-obj-id'; }) )
        return;

      var id = e.dataTransfer.getData('application/x-foam-obj-id');

      if ( foam.util.equals(id, this.data.id) )
        return;

      e.preventDefault();
    },
    function onDrop(e) {
      if ( ! e.dataTransfer.types.some(function(m) { return m === 'application/x-foam-obj-id'; }) )
        return;

      var id = e.dataTransfer.getData('application/x-foam-obj-id');

      if ( foam.util.equals(id, this.data.id) )
        return;

      e.preventDefault();

      var self = this;

      this.__context__[this.relationship.targetDAOKey].
        find(id).then(function(obj) {
          return self.data[self.relationship.name].put(obj.clone());
        });
    },
    function selected() {
      this.selection = this.data;
    },
    function toggleExpanded(e) {
      this.expanded = ! this.expanded;
      e.preventDefault();
      e.stopPropagation();
    }
  ]
});

foam.CLASS({
  package: 'foam.u2.view',
  name: 'TreeView',
  extends: 'foam.u2.Element',
  requires: [
    'foam.mlang.ExpressionsSingleton',
    'foam.u2.view.TreeViewRow'
  ],
  exports: [
    'selection'
  ],
  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      name: 'relationship'
    },
    {
      name: 'selection'
    },
    {
      class: 'Function',
      name: 'formatter'
    }
  ],
  methods: [
    function initE() {
      var M = this.ExpressionsSingleton.create();
      var of = foam.lookup(this.relationship.sourceModel);

      var dao = this.data$proxy.where(
        M.NOT(M.HAS(of.getAxiomByName(this.relationship.inverseName))));

      var self = this;
      this.cssClass(this.myCls()).
        select(dao, function(obj) {
          return self.TreeViewRow.create({
            data: obj,
            relationship: self.relationship,
            formatter: self.formatter
          }, this);
        });
    }
  ]
});
<|endoftext|>


<commit_before>import _ from 'lodash'
import { bitCount, showFlat } from './Util'

export class Fingering {
  basePitch = 13 // C#

  lh1     = new Key(12, this, () =>                                        -2     )
  bis     = new Key(11, this, () => this.lh1._pressed && this.lh2._pressed ?  0 : -1)
  lh2     = new Key(10, this, () => this.lh1._pressed                     ? -2 : -1)
  lh3     = new Key( 9, this, () =>                                        -2)

  lpinky1 = new Key( 8, this, () =>                                         1     )
  lpinky2 = new Key( 7, this, () =>                                        -1     )

  rside   = new Key( 6, this, () => !this.lpinky1._pressed                ? +1 :  0)
  rh1     = new Key( 5, this, () => !this.lh3._pressed                    ? -1 : -2)
  rh2     = new Key( 4, this, () =>                                        -1     )
  rh3     = new Key( 3, this, () =>                                        -2     )

  rpinky1 = new Key( 2, this, () =>                                         1     )
  rpinky2 = new Key( 1, this, () =>                                        -1     )
  rpinky3 = new Key( 0, this, () =>                                        -2     )

  roller = 0 // default (middle) octave. Range: -2 to 4

  keys = [
    this.lh1, this.bis, this.lh2, this.lh3, this.lpinky1, this.lpinky2,
    this.rside, this.rh1, this.rh2, this.rh3, this.rpinky1, this.rpinky2, this.rpinky3
  ]

  constructor(bitmask = 0, roller = 0, flat = false) {
    this.bitmask = bitmask
    this.roller = roller
    this.flat = flat
    this._pitch = null

    if (!bitmask)
      return

    this.keys
      .filter(k => (bitmask & (1 << k.index)) !== 0)
      .forEach(k => k.press(false))
  }

  get id() {
    return this.bitmask
  }

  get bitmaskString() {
    return this.bitmask.toString(2)
  }

  get pitch() {
    if (this._pitch === null) {
      this._pitch = this.basePitch + this.keys
        .filter(key => key._pressed)
        .map(key => key.pitch)
        .reduce((prev, cur) => prev + cur, 0)
    }

    return this._pitch
  }

  get note() {
    let note;
    switch (this.pitch) {
      case -2: note = 'A#' ; break;
      case -1: note = 'B'  ; break;
      case  0: note = 'c'  ; break;
      case  1: note = 'c#' ; break;
      case  2: note = 'd'  ; break;
      case  3: note = 'd#' ; break;
      case  4: note = 'e'  ; break;
      case  5: note = 'f'  ; break;
      case  6: note = 'f#' ; break;
      case  7: note = 'g'  ; break;
      case  8: note = 'g#' ; break;
      case  9: note = 'a'  ; break;
      case 10: note = 'a#' ; break;
      case 11: note = 'b'  ; break;
      case 12: note = "c'" ; break;
      case 13: note = "c#'"; break;
      case 14: note = "d'" ; break;
      case 15: note = "d#'"; break;
      default:
        console.error('Invalid pitch')
    }
    if (this.flat)
      note = showFlat(note)

    return note
  }

  distance(other) {
    return bitCount(this.bitmask ^ other.bitmask)
  }

  updateBitmask(index, pressed) {
    let pos = 1 << index
    if (Boolean(this.bitmask & pos) !== pressed) {
      this.bitmask = this.bitmask ^ pos
      this._pitch = null
    }
  }

  applyDiff(previousFingering) {
    for(let i = 0; i < this.keys.length; i++) {
      if (this.keys[i]._pressed && !previousFingering.keys[i].pressed)
        this.keys[i].diff = 1
      else if (!this.keys[i]._pressed && previousFingering.keys[i].pressed)
        this.keys[i].diff = -1
      else
        this.keys[i].diff = 0
    }
  }

  get pressedKeys() { return this.keys.filter(k => k._pressed) }

  get redundant() { return this.keys.some(k => k.redundant) }

  get hasNeutralizingKeys() {
    let pressed = this.pressedKeys
    let hasKeyWithPitch = x => pressed.some(k => k.pitch === x)
    return hasKeyWithPitch(1) && hasKeyWithPitch(-1)
  }

  get allRightPinkyKeysPressed() {
    // NOTE: subset of hasNeutralizingKeys
    return this.rpinky1._pressed && this.rpinky2._pressed && this.rpinky3._pressed
  }

  get badBisKeyUsage() {
    return this.bis._pressed && !(this.lh1._pressed || this.lh2._pressed)
  }
}

class Key {
  constructor(index, fingering, pitch, pressed = false) {
    this.index = index
    this.fingering = fingering
    this._pitch = pitch
    this._pressed = pressed
    this.diff = 0
  }

  get pitch() { return this._pitch() }

  get pressed() { return this._pressed }

  get redundant() { return this.pitch === 0 && this._pressed }

  press(updateFingering=true) {
    this._pressed = true
    if (updateFingering)
      this.fingering.updateBitmask(this.index, true)
  }

  unpress() {
    this._pressed = false
    this.fingering.updateBitmask(this.index, false)
  }

  toggle() {
    this._pressed = !this._pressed
    this.fingering.updateBitmask(this.index, this._pressed)
  }
}

export function allCombinations(fingering) {
  // console.time('allCombinations()')

  let numCombinations = 2 ** fingering.keys.length;
  let fingerings = _.times(numCombinations, bitmask => new Fingering(bitmask))

  // console.log('combinations: ', fingerings.length)

  // console.time('filter out redundant')
  fingerings = fingerings.filter(fingering => !fingering.redundant)
  // console.timeEnd('filter out redundant')
  // console.log('combinations: ', fingerings.length)

  // console.time('filter out allRightPinkyKeysPressed')
  fingerings = fingerings.filter(fingering => !fingering.allRightPinkyKeysPressed)
  // console.timeEnd('filter out allRightPinkyKeysPressed')
  // console.log('combinations: ', fingerings.length)

  // console.time('filter out neutralizing keys')
  fingerings = fingerings.filter(fingering => !fingering.hasNeutralizingKeys)
  // console.timeEnd('filter out neutralizing keys')
  // console.log('combinations: ', fingerings.length)

  // console.time('filter out bad bis key usage')
  fingerings = fingerings.filter(fingering => !fingering.badBisKeyUsage)
  // console.timeEnd('filter out bad bis key usage')
  // console.log('combinations: ', fingerings.length)

  // console.time('group by pitch')
  let fingeringsByPitch = _.groupBy(fingerings, fingering => fingering.pitch)
  // console.log(fingeringsByPitch)
  // console.timeEnd('group by pitch')

  for (let pitch in fingeringsByPitch) {
    if (!fingeringsByPitch.hasOwnProperty(pitch))
      continue
    fingeringsByPitch[pitch] = _.sortBy(fingeringsByPitch[pitch], fingering => fingering.pressedKeys.length)

    // console.log(fingeringsByPitch[pitch][0].note)
    // console.log(fingeringsByPitch[pitch].slice(0,5).map(ewi => `${ewi.id}, ${ewi.pressedKeys.length}]`))
  }

  // TODO!: further/different sort criteria...
  // - distance to default fingering (smaller = better)
  // - number of 'gaps' between pressed keys?? (smaller = better)
  // - penalty for bis key?

  //  console.timeEnd('allCombinations()')

  return fingeringsByPitch
}

// Standard fingerings as found in the EWI 5000 User Guide page 39
const STANDARD_FINGERINGS_EWI = [
  0b1011010111011, // A#
  0b1011010111001, // B
  0b1011000111001, // c
  0b1011100111001, // c#
  0b1011000111000, // d
  0b1011000111100, // d#
  0b1011000110000, // e
  0b1011000100000, // f
  0b1011000010000, // f#
  0b1011000000000, // g
  0b1011100000000, // g#
  0b1010000000000, // a
  0b1010001000000, // a#
  0b1100000000000, // a# (alt.)
  0b1000000000000, // b
  0b0010000000000, // c'
  0b0000000000000, // c#'
  0b0000100000000, // d'
  0b0000100000100, // d#' (not in manual, but there's only one possibility)
]

export const STANDARD_FINGERINGS_BY_NOTE = {
  "A#" : STANDARD_FINGERINGS_EWI[ 0], "Bb" : STANDARD_FINGERINGS_EWI[0],
  "B"  : STANDARD_FINGERINGS_EWI[ 1], "Cb" : STANDARD_FINGERINGS_EWI[1],
  "c"  : STANDARD_FINGERINGS_EWI[ 2], "B#" : STANDARD_FINGERINGS_EWI[2],
  "c#" : STANDARD_FINGERINGS_EWI[ 3], "db" : STANDARD_FINGERINGS_EWI[3],
  "d"  : STANDARD_FINGERINGS_EWI[ 4],
  "d#" : STANDARD_FINGERINGS_EWI[ 5], "eb" : STANDARD_FINGERINGS_EWI[5],
  "e"  : STANDARD_FINGERINGS_EWI[ 6], "fb" : STANDARD_FINGERINGS_EWI[6],
  "f"  : STANDARD_FINGERINGS_EWI[ 7], "e#" : STANDARD_FINGERINGS_EWI[7],
  "f#" : STANDARD_FINGERINGS_EWI[ 8], "gb" : STANDARD_FINGERINGS_EWI[8],
  "g"  : STANDARD_FINGERINGS_EWI[ 9],
  "g#" : STANDARD_FINGERINGS_EWI[10], "ab" : STANDARD_FINGERINGS_EWI[10],
  "a"  : STANDARD_FINGERINGS_EWI[11],
  "a#" : STANDARD_FINGERINGS_EWI[12], "bb" : STANDARD_FINGERINGS_EWI[12],
  "a#_": STANDARD_FINGERINGS_EWI[13], "bb_": STANDARD_FINGERINGS_EWI[13], // TODO: ?
  "b"  : STANDARD_FINGERINGS_EWI[14], "cb" : STANDARD_FINGERINGS_EWI[14],
  "c'" : STANDARD_FINGERINGS_EWI[15], "b#'": STANDARD_FINGERINGS_EWI[15],
  "c#'": STANDARD_FINGERINGS_EWI[16], "db'": STANDARD_FINGERINGS_EWI[16],
  "d'" : STANDARD_FINGERINGS_EWI[17],
  "d#'" : STANDARD_FINGERINGS_EWI[18],
}

export function isStandardFingering(fingering) {
  return STANDARD_FINGERINGS_EWI.indexOf(fingering.bitmask) !== -1
}
<commit_msg>improve alternate sorting<commit_after>import _ from 'lodash'
import { bitCount, showFlat } from './Util'

export class Fingering {
  basePitch = 13 // C#

  lh1     = new Key(12, this, () =>                                        -2     )
  bis     = new Key(11, this, () => this.lh1._pressed && this.lh2._pressed ?  0 : -1)
  lh2     = new Key(10, this, () => this.lh1._pressed                     ? -2 : -1)
  lh3     = new Key( 9, this, () =>                                        -2)

  lpinky1 = new Key( 8, this, () =>                                         1     )
  lpinky2 = new Key( 7, this, () =>                                        -1     )

  rside   = new Key( 6, this, () => !this.lpinky1._pressed                ? +1 :  0)
  rh1     = new Key( 5, this, () => !this.lh3._pressed                    ? -1 : -2)
  rh2     = new Key( 4, this, () =>                                        -1     )
  rh3     = new Key( 3, this, () =>                                        -2     )

  rpinky1 = new Key( 2, this, () =>                                         1     )
  rpinky2 = new Key( 1, this, () =>                                        -1     )
  rpinky3 = new Key( 0, this, () =>                                        -2     )

  roller = 0 // default (middle) octave. Range: -2 to 4

  keys = [
    this.lh1, this.bis, this.lh2, this.lh3, this.lpinky1, this.lpinky2,
    this.rside, this.rh1, this.rh2, this.rh3, this.rpinky1, this.rpinky2, this.rpinky3
  ]

  constructor(bitmask = 0, roller = 0, flat = false) {
    this.bitmask = bitmask
    this.roller = roller
    this.flat = flat
    this._pitch = null

    if (!bitmask)
      return

    this.keys
      .filter(k => (bitmask & (1 << k.index)) !== 0)
      .forEach(k => k.press(false))
  }

  get id() {
    return this.bitmask
  }

  get bitmaskString() {
    return this.bitmask.toString(2)
  }

  get pitch() {
    if (this._pitch === null) {
      this._pitch = this.basePitch + this.keys
        .filter(key => key._pressed)
        .map(key => key.pitch)
        .reduce((prev, cur) => prev + cur, 0)
    }

    return this._pitch
  }

  get note() {
    let note;
    switch (this.pitch) {
      case -2: note = 'A#' ; break;
      case -1: note = 'B'  ; break;
      case  0: note = 'c'  ; break;
      case  1: note = 'c#' ; break;
      case  2: note = 'd'  ; break;
      case  3: note = 'd#' ; break;
      case  4: note = 'e'  ; break;
      case  5: note = 'f'  ; break;
      case  6: note = 'f#' ; break;
      case  7: note = 'g'  ; break;
      case  8: note = 'g#' ; break;
      case  9: note = 'a'  ; break;
      case 10: note = 'a#' ; break;
      case 11: note = 'b'  ; break;
      case 12: note = "c'" ; break;
      case 13: note = "c#'"; break;
      case 14: note = "d'" ; break;
      case 15: note = "d#'"; break;
      default:
        console.error('Invalid pitch')
    }
    if (this.flat)
      note = showFlat(note)

    return note
  }

  distance(other) {
    return bitCount(this.bitmask ^ other.bitmask)
  }

  updateBitmask(index, pressed) {
    let pos = 1 << index
    if (Boolean(this.bitmask & pos) !== pressed) {
      this.bitmask = this.bitmask ^ pos
      this._pitch = null
    }
  }

  applyDiff(previousFingering) {
    for(let i = 0; i < this.keys.length; i++) {
      if (this.keys[i]._pressed && !previousFingering.keys[i].pressed)
        this.keys[i].diff = 1
      else if (!this.keys[i]._pressed && previousFingering.keys[i].pressed)
        this.keys[i].diff = -1
      else
        this.keys[i].diff = 0
    }
  }

  get pressedKeys() { return this.keys.filter(k => k._pressed) }

  get redundant() { return this.keys.some(k => k.redundant) }

  get hasNeutralizingKeys() {
    let pressed = this.pressedKeys
    let hasKeyWithPitch = x => pressed.some(k => k.pitch === x)
    return hasKeyWithPitch(1) && hasKeyWithPitch(-1)
  }

  get allRightPinkyKeysPressed() {
    // NOTE: subset of hasNeutralizingKeys
    return this.rpinky1._pressed && this.rpinky2._pressed && this.rpinky3._pressed
  }

  get badBisKeyUsage() {
    return this.bis._pressed && !(this.lh1._pressed || this.lh2._pressed)
  }
}

class Key {
  constructor(index, fingering, pitch, pressed = false) {
    this.index = index
    this.fingering = fingering
    this._pitch = pitch
    this._pressed = pressed
    this.diff = 0
  }

  get pitch() { return this._pitch() }

  get pressed() { return this._pressed }

  get redundant() { return this.pitch === 0 && this._pressed }

  press(updateFingering=true) {
    this._pressed = true
    if (updateFingering)
      this.fingering.updateBitmask(this.index, true)
  }

  unpress() {
    this._pressed = false
    this.fingering.updateBitmask(this.index, false)
  }

  toggle() {
    this._pressed = !this._pressed
    this.fingering.updateBitmask(this.index, this._pressed)
  }
}

export function allCombinations(fingering) {
  // console.time('allCombinations()')

  let numCombinations = 2 ** fingering.keys.length;
  let fingerings = _.times(numCombinations, bitmask => new Fingering(bitmask))

  // console.log('combinations: ', fingerings.length)

  // console.time('filter out redundant')
  fingerings = fingerings.filter(fingering => !fingering.redundant)
  // console.timeEnd('filter out redundant')
  // console.log('combinations: ', fingerings.length)

  // console.time('filter out allRightPinkyKeysPressed')
  fingerings = fingerings.filter(fingering => !fingering.allRightPinkyKeysPressed)
  // console.timeEnd('filter out allRightPinkyKeysPressed')
  // console.log('combinations: ', fingerings.length)

  // console.time('filter out neutralizing keys')
  fingerings = fingerings.filter(fingering => !fingering.hasNeutralizingKeys)
  // console.timeEnd('filter out neutralizing keys')
  // console.log('combinations: ', fingerings.length)

  // console.time('filter out bad bis key usage')
  fingerings = fingerings.filter(fingering => !fingering.badBisKeyUsage)
  // console.timeEnd('filter out bad bis key usage')
  // console.log('combinations: ', fingerings.length)

  // console.time('group by pitch')
  let fingeringsByPitch = _.groupBy(fingerings, fingering => fingering.pitch)
  // console.log(fingeringsByPitch)
  // console.timeEnd('group by pitch')

  for (let pitch in fingeringsByPitch) {
    if (!fingeringsByPitch.hasOwnProperty(pitch))
      continue

    // reverse to make fingerings that use more 'upper' keys appear first
    fingeringsByPitch[pitch].reverse()

    // sort by number of pressed keys
    fingeringsByPitch[pitch] = _.sortBy(fingeringsByPitch[pitch], fingering => fingering.pressedKeys.length)

    // console.log(fingeringsByPitch[pitch][0].note)
    // console.log(fingeringsByPitch[pitch].slice(0,5).map(ewi => `${ewi.id}, ${ewi.pressedKeys.length}]`))
  }

  // TODO!: further/different sort criteria...
  // - distance to default fingering (smaller = better)
  // - number of 'gaps' between pressed keys?? (smaller = better)
  // - penalty for bis key?

  //  console.timeEnd('allCombinations()')

  return fingeringsByPitch
}

// Standard fingerings as found in the EWI 5000 User Guide page 39
const STANDARD_FINGERINGS_EWI = [
  0b1011010111011, // A#
  0b1011010111001, // B
  0b1011000111001, // c
  0b1011100111001, // c#
  0b1011000111000, // d
  0b1011000111100, // d#
  0b1011000110000, // e
  0b1011000100000, // f
  0b1011000010000, // f#
  0b1011000000000, // g
  0b1011100000000, // g#
  0b1010000000000, // a
  0b1010001000000, // a#
  0b1100000000000, // a# (alt.)
  0b1000000000000, // b
  0b0010000000000, // c'
  0b0000000000000, // c#'
  0b0000100000000, // d'
  0b0000100000100, // d#' (not in manual, but there's only one possibility)
]

export const STANDARD_FINGERINGS_BY_NOTE = {
  "A#" : STANDARD_FINGERINGS_EWI[ 0], "Bb" : STANDARD_FINGERINGS_EWI[0],
  "B"  : STANDARD_FINGERINGS_EWI[ 1], "Cb" : STANDARD_FINGERINGS_EWI[1],
  "c"  : STANDARD_FINGERINGS_EWI[ 2], "B#" : STANDARD_FINGERINGS_EWI[2],
  "c#" : STANDARD_FINGERINGS_EWI[ 3], "db" : STANDARD_FINGERINGS_EWI[3],
  "d"  : STANDARD_FINGERINGS_EWI[ 4],
  "d#" : STANDARD_FINGERINGS_EWI[ 5], "eb" : STANDARD_FINGERINGS_EWI[5],
  "e"  : STANDARD_FINGERINGS_EWI[ 6], "fb" : STANDARD_FINGERINGS_EWI[6],
  "f"  : STANDARD_FINGERINGS_EWI[ 7], "e#" : STANDARD_FINGERINGS_EWI[7],
  "f#" : STANDARD_FINGERINGS_EWI[ 8], "gb" : STANDARD_FINGERINGS_EWI[8],
  "g"  : STANDARD_FINGERINGS_EWI[ 9],
  "g#" : STANDARD_FINGERINGS_EWI[10], "ab" : STANDARD_FINGERINGS_EWI[10],
  "a"  : STANDARD_FINGERINGS_EWI[11],
  "a#" : STANDARD_FINGERINGS_EWI[12], "bb" : STANDARD_FINGERINGS_EWI[12],
  "a#_": STANDARD_FINGERINGS_EWI[13], "bb_": STANDARD_FINGERINGS_EWI[13], // TODO: ?
  "b"  : STANDARD_FINGERINGS_EWI[14], "cb" : STANDARD_FINGERINGS_EWI[14],
  "c'" : STANDARD_FINGERINGS_EWI[15], "b#'": STANDARD_FINGERINGS_EWI[15],
  "c#'": STANDARD_FINGERINGS_EWI[16], "db'": STANDARD_FINGERINGS_EWI[16],
  "d'" : STANDARD_FINGERINGS_EWI[17],
  "d#'" : STANDARD_FINGERINGS_EWI[18],
}

export function isStandardFingering(fingering) {
  return STANDARD_FINGERINGS_EWI.indexOf(fingering.bitmask) !== -1
}
<|endoftext|>


<commit_before><commit_msg>Removed useless alert call<commit_after><|endoftext|>


<commit_before>// # Quintus platformer example
//
// [Run the example](../quintus/examples/platformer/index.html)
// WARNING: this game must be run from a non-file:// url
// as it loads a level json file.
//
// This is the example from the website homepage, it consists
// a simple, non-animated platformer with some enemies and a
// target for the player.

window.addEventListener("load",function() {

// Set up an instance of the Quintus engine  and include
// the Sprites, Scenes, Input and 2D module. The 2D module
// includes the `TileLayer` class as well as the `2d` componet.
var Q = window.Q = Quintus()
        .include("Sprites, Scenes, Input, 2D, Anim, Touch, UI")
        // Maximize this game to whatever the size of the browser is
        .setup({ maximize: true })
        // And turn on default input controls and touch input (for UI)
        .controls().touch()

// ## Player Sprite
// The very basic player sprite, this is just a normal sprite
// using the player sprite sheet with default controls added to it.
Q.Sprite.extend("Player",{

  // the init constructor is called on creation
  init: function(p) {

    // You can call the parent's constructor with this._super(..)
    this._super(p, {
      sheet: "player",  // Setting a sprite sheet sets sprite width and height
      x: 410,           // You can also set additional properties that can
      y: 90             // be overridden on object creation
    });

    // Add in pre-made components to get up and running quickly
    // The `2d` component adds in default 2d collision detection
    // and kinetics (velocity, gravity)
    // The `platformerControls` makes the player controllable by the
    // default input actions (left, right to move,  up or action to jump)
    // It also checks to make sure the player is on a horizontal surface before
    // letting them jump.
    this.add('2d, platformerControls');

    // Write event handlers to respond hook into behaviors.
    // hit.sprite is called everytime the player collides with a sprite
    this.on("hit.sprite",function(collision) {

      // Check the collision, if it's the Tower, you win!
      if(collision.obj.isA("Tower")) {
        Q.stageScene("endGame",1, { label: "You Won!" });
        this.destroy();
      }
    });

  }

});

//component for common enemy behaviors
Q.component("commonEnemy", {
    added: function() {
        var entity = this.entity;
        entity.on("bump.left,bump.right,bump.bottom",function(collision) {
            if(collision.obj.isA("Player")) {
              Q.stageScene("endGame",1, { label: "Game Over" });
              collision.obj.destroy();
            }
        });
        entity.on("bump.top",function(collision) {
            if(collision.obj.isA("Player")) {
                //make the player jump
                collision.obj.p.vy = -100;

                //kill enemy
                this.destroy();
            }
        });
    },

});

// ## Tower Sprite
// Sprites can be simple, the Tower sprite just sets a custom sprite sheet
Q.Sprite.extend("Tower", {
  init: function(p) {
    this._super(p, { sheet: 'tower' });
  }
});

// ## Enemy Sprite
// Create the Enemy class to add in some baddies
Q.Sprite.extend("Enemy",{
  init: function(p) {
    this._super(p, { sheet: 'enemy', vx: 100 });

    // Enemies use the Bounce AI to change direction
    // whenver they run into something.
    this.add("2d, aiBounce, commonEnemy");

    // Listen for a sprite collision, if it's the player,
    // end the game unless the enemy is hit on top
    this.on("bump.left,bump.right,bump.bottom",function(collision) {
      if(collision.obj.isA("Player")) {
        Q.stageScene("endGame",1, { label: "You Died" });
        collision.obj.destroy();
      }
    });

    // If the enemy gets hit on the top, destroy it
    // and give the user a "hop"
    this.on("bump.top",function(collision) {
      if(collision.obj.isA("Player")) {
        this.destroy();
        collision.obj.p.vy = -300;
      }
    });
  }
});

//enemy that goes up and down
Q.Sprite.extend("VerticalEnemy", {
    init: function(p) {
        this._super(p, {vy: -100, rangeY: 200, gravity: 0 });
        this.add("2d, aiBounce, commonEnemy");
        this.p.initialY = this.p.y;

        // Listen for a sprite collision, if it's the player,
        // end the game unless the enemy is hit on top
        this.on("bump.left,bump.right,bump.bottom",function(collision) {
          if(collision.obj.isA("Player")) {
            Q.stageScene("endGame",1, { label: "You Died" });
            collision.obj.destroy();
          }
        });

        // If the enemy gets hit on the top, destroy it
        // and give the user a "hop"
        this.on("bump.top",function(collision) {
          if(collision.obj.isA("Player")) {
            this.destroy();
            collision.obj.p.vy = -300;
          }
        });
    },
    step: function(dt) {
        if(this.p.y - this.p.initialY >= this.p.rangeY && this.p.vy > 0) {
            this.p.vy = -this.p.vy;
        }
        else if(-this.p.y + this.p.initialY >= this.p.rangeY && this.p.vy < 0) {
            this.p.vy = -this.p.vy;
        }
    }
});

// ## Level1 scene
// Create a new scene called level 1
Q.scene("level1",function(stage) {

  // Add in a repeater for a little parallax action
  stage.insert(new Q.Repeater({ asset: "background-wall.png", speedX: 0.5, speedY: 0.5 }));

  // Add in a tile layer, and make it the collision layer
  stage.collisionLayer(new Q.TileLayer({
                             dataAsset: 'level.json',
                             sheet:     'tiles' }));

  // Create the player and add them to the stage
  var player = stage.insert(new Q.Player());

  // Give the stage a moveable viewport and tell it
  // to follow the player.
  stage.add("viewport").follow(player);

  // Add in a couple of enemies
  stage.insert(new Q.Enemy({ x: 600, y: 0 }));
  stage.insert(new Q.Enemy({ x: 700, y: 0 }));
  stage.insert(new Q.VerticalEnemy({x: 800, y: 120, rangeY: 70, asset: "fly.png" }));
  stage.insert(new Q.VerticalEnemy({x: 900, y: 120, rangeY: 70, asset: "fly.png" }));
  stage.insert(new Q.Enemy({ x: 400, y: 0 }));
  stage.insert(new Q.Enemy({ x: 500, y: 0 }));

  // Finally add in the tower goal
  stage.insert(new Q.Tower({ x: 180, y: 50 }));
});

// To display a game over / game won popup box,
// create a endGame scene that takes in a `label` option
// to control the displayed message.
Q.scene('endGame',function(stage) {
  var container = stage.insert(new Q.UI.Container({
    x: Q.width/2, y: Q.height/2, fill: "rgba(0,0,0,0.5)"
  }));

  var button = container.insert(new Q.UI.Button({ x: 0, y: 0, fill: "#CCCCCC",
                                                  label: "Play Again" }))
  var label = container.insert(new Q.UI.Text({x:10, y: -10 - button.p.h,
                                                   label: stage.options.label }));
  // When the button is clicked, clear all the stages
  // and restart the game.
  button.on("click",function() {
    Q.clearStages();
    Q.stageScene('level1');
  });

  // Expand the container to visibily fit it's contents
  // (with a padding of 20 pixels)
  container.fit(20);
});

// ## Asset Loading and Game Launch
// Q.load can be called at any time to load additional assets
// assets that are already loaded will be skipped
// The callback will be triggered when everything is loaded
Q.load("sprites.png, sprites.json, level.json, fly.png, tiles.png, background-wall.png", function() {
  // Sprites sheets can be created manually
  Q.sheet("tiles","tiles.png", { tilew: 32, tileh: 32 });

  // Or from a .json asset that defines sprite locations
  Q.compileSheets("sprites.png","sprites.json");

  // Finally, call stageScene to run the game
  Q.stageScene("level1");
});

// ## Possible Experimentations:
//
// The are lots of things to try out here.
//
// 1. Modify level.json to change the level around and add in some more enemies.
// 2. Add in a second level by creating a level2.json and a level2 scene that gets
//    loaded after level 1 is complete.
// 3. Add in a title screen
// 4. Add in a hud and points for jumping on enemies.
// 5. Add in a `Repeater` behind the TileLayer to create a paralax scrolling effect.

});
<commit_msg>asdfawwefsvcxbbdf<commit_after>// # Quintus platformer example
//
// [Run the example](../quintus/examples/platformer/index.html)
// WARNING: this game must be run from a non-file:// url
// as it loads a level json file.
//
// This is the example from the website homepage, it consists
// a simple, non-animated platformer with some enemies and a
// target for the player.

window.addEventListener("load",function() {

// Set up an instance of the Quintus engine  and include
// the Sprites, Scenes, Input and 2D module. The 2D module
// includes the `TileLayer` class as well as the `2d` componet.
var Q = window.Q = Quintus()
        .include("Sprites, Scenes, Input, 2D, Anim, Touch, UI")
        // Maximize this game to whatever the size of the browser is
        .setup({ maximize: true })
        // And turn on default input controls and touch input (for UI)
        .controls().touch()

// ## Player Sprite
// The very basic player sprite, this is just a normal sprite
// using the player sprite sheet with default controls added to it.
Q.Sprite.extend("Player",{

  // the init constructor is called on creation
  init: function(p) {

    // You can call the parent's constructor with this._super(..)
    this._super(p, {
      sheet: "player",  // Setting a sprite sheet sets sprite width and height
      x: 410,           // You can also set additional properties that can
      y: 90             // be overridden on object creation
    });

    // Add in pre-made components to get up and running quickly
    // The `2d` component adds in default 2d collision detection
    // and kinetics (velocity, gravity)
    // The `platformerControls` makes the player controllable by the
    // default input actions (left, right to move,  up or action to jump)
    // It also checks to make sure the player is on a horizontal surface before
    // letting them jump.
    this.add('2d, platformerControls');

    // Write event handlers to respond hook into behaviors.
    // hit.sprite is called everytime the player collides with a sprite
    this.on("hit.sprite",function(collision) {

      // Check the collision, if it's the Tower, you win!
      if(collision.obj.isA("Tower")) {
        Q.stageScene("endGame",1, { label: "You Won!" });
        this.destroy();
      }
    });

  }

});

//component for common enemy behaviors
Q.component("commonEnemy", {
    added: function() {
        var entity = this.entity;
        entity.on("bump.left,bump.right,bump.bottom",function(collision) {
            if(collision.obj.isA("Player")) {
              Q.stageScene("endGame",1, { label: "Game Over" });
              collision.obj.destroy();
            }
        });
        entity.on("bump.top",function(collision) {
            if(collision.obj.isA("Player")) {
                //make the player jump
                collision.obj.p.vy = -100;

                //kill enemy
                this.destroy();
            }
        });
    },

});

// ## Tower Sprite
// Sprites can be simple, the Tower sprite just sets a custom sprite sheet
Q.Sprite.extend("Tower", {
  init: function(p) {
    this._super(p, { sheet: 'tower' });
  }
});

// ## Enemy Sprite
// Create the Enemy class to add in some baddies
Q.Sprite.extend("Enemy",{
  init: function(p) {
    this._super(p, { sheet: 'enemy', vx: 100 });

    // Enemies use the Bounce AI to change direction
    // whenver they run into something.
    this.add("2d, aiBounce, commonEnemy");

    // Listen for a sprite collision, if it's the player,
    // end the game unless the enemy is hit on top
    this.on("bump.left,bump.right,bump.bottom",function(collision) {
      if(collision.obj.isA("Player")) {
        Q.stageScene("endGame",1, { label: "You Died" });
        collision.obj.destroy();
      }
    });

    // If the enemy gets hit on the top, destroy it
    // and give the user a "hop"
    this.on("bump.top",function(collision) {
      if(collision.obj.isA("Player")) {
        this.destroy();
        collision.obj.p.vy = -300;
      }
    });
  }
});

//enemy that goes up and down
Q.Sprite.extend("VerticalEnemy", {
    init: function(p) {
        this._super(p, {sheet: 'enemy', vy: -100 });
        this.add("2d, aiBounce, commonEnemy");
        this.p.initialY = this.p.y;

        // Listen for a sprite collision, if it's the player,
        // end the game unless the enemy is hit on top
        this.on("bump.left,bump.right,bump.bottom",function(collision) {
          if(collision.obj.isA("Player")) {
            Q.stageScene("endGame",1, { label: "You Died" });
            collision.obj.destroy();
          }
        });

        // If the enemy gets hit on the top, destroy it
        // and give the user a "hop"
        this.on("bump.top",function(collision) {
          if(collision.obj.isA("Player")) {
            this.destroy();
            collision.obj.p.vy = -300;
          }
        });
    },
    step: function(dt) {
        if(this.p.y - this.p.initialY >= this.p.rangeY && this.p.vy > 0) {
            this.p.vy = -this.p.vy;
        }
        else if(-this.p.y + this.p.initialY >= this.p.rangeY && this.p.vy < 0) {
            this.p.vy = -this.p.vy;
        }
    }
});

// ## Level1 scene
// Create a new scene called level 1
Q.scene("level1",function(stage) {

  // Add in a repeater for a little parallax action
  stage.insert(new Q.Repeater({ asset: "background-wall.png", speedX: 0.5, speedY: 0.5 }));

  // Add in a tile layer, and make it the collision layer
  stage.collisionLayer(new Q.TileLayer({
                             dataAsset: 'level.json',
                             sheet:     'tiles' }));

  // Create the player and add them to the stage
  var player = stage.insert(new Q.Player());

  // Give the stage a moveable viewport and tell it
  // to follow the player.
  stage.add("viewport").follow(player);

  // Add in a couple of enemies
  stage.insert(new Q.Enemy({ x: 600, y: 0 }));
  stage.insert(new Q.Enemy({ x: 700, y: 0 }));
  stage.insert(new Q.VerticalEnemy({x: 800, y: 120, rangeY: 70, asset: "fly.png" }));
  stage.insert(new Q.VerticalEnemy({x: 900, y: 120, rangeY: 70, asset: "fly.png" }));
  stage.insert(new Q.Enemy({ x: 400, y: 0 }));
  stage.insert(new Q.Enemy({ x: 500, y: 0 }));

  // Finally add in the tower goal
  stage.insert(new Q.Tower({ x: 180, y: 50 }));
});

// To display a game over / game won popup box,
// create a endGame scene that takes in a `label` option
// to control the displayed message.
Q.scene('endGame',function(stage) {
  var container = stage.insert(new Q.UI.Container({
    x: Q.width/2, y: Q.height/2, fill: "rgba(0,0,0,0.5)"
  }));

  var button = container.insert(new Q.UI.Button({ x: 0, y: 0, fill: "#CCCCCC",
                                                  label: "Play Again" }))
  var label = container.insert(new Q.UI.Text({x:10, y: -10 - button.p.h,
                                                   label: stage.options.label }));
  // When the button is clicked, clear all the stages
  // and restart the game.
  button.on("click",function() {
    Q.clearStages();
    Q.stageScene('level1');
  });

  // Expand the container to visibily fit it's contents
  // (with a padding of 20 pixels)
  container.fit(20);
});

// ## Asset Loading and Game Launch
// Q.load can be called at any time to load additional assets
// assets that are already loaded will be skipped
// The callback will be triggered when everything is loaded
Q.load("sprites.png, sprites.json, level.json, fly.png, tiles.png, background-wall.png", function() {
  // Sprites sheets can be created manually
  Q.sheet("tiles","tiles.png", { tilew: 32, tileh: 32 });

  // Or from a .json asset that defines sprite locations
  Q.compileSheets("sprites.png","sprites.json");

  // Finally, call stageScene to run the game
  Q.stageScene("level1");
});

// ## Possible Experimentations:
//
// The are lots of things to try out here.
//
// 1. Modify level.json to change the level around and add in some more enemies.
// 2. Add in a second level by creating a level2.json and a level2 scene that gets
//    loaded after level 1 is complete.
// 3. Add in a title screen
// 4. Add in a hud and points for jumping on enemies.
// 5. Add in a `Repeater` behind the TileLayer to create a paralax scrolling effect.

});
<|endoftext|>


<commit_before>/*global Pusher*/

import { extend } from 'flarum/extend';
import app from 'flarum/app';
import DiscussionList from 'flarum/components/DiscussionList';
import DiscussionPage from 'flarum/components/DiscussionPage';
import IndexPage from 'flarum/components/IndexPage';
import Button from 'flarum/components/Button';

app.initializers.add('flarum-pusher', () => {
  const loadPusher = m.deferred();

  $.getScript('//js.pusher.com/3.0/pusher.min.js', () => {
    const socket = new Pusher(app.forum.attribute('pusherKey'), {
      authEndpoint: app.forum.attribute('apiUrl') + '/pusher/auth',
      cluster: app.forum.attribute('pusherCluster'),
      auth: {
        headers: {
          'X-CSRF-Token': app.session.csrfToken
        }
      }
    });

    loadPusher.resolve({
      main: socket.subscribe('public'),
      user: app.session.user ? socket.subscribe('private-user' + app.session.user.id()) : null
    });
  });

  app.pusher = loadPusher.promise;
  app.pushedUpdates = [];

  extend(DiscussionList.prototype, 'config', function(x, isInitialized, context) {
    if (isInitialized) return;

    app.pusher.then(channels => {
      channels.main.bind('newPost', data => {
        const params = this.props.params;

        if (!params.q && !params.sort && !params.filter) {
          if (params.tags) {
            const tag = app.store.getBy('tags', 'slug', params.tags);

            if (data.tagIds.indexOf(tag.id()) === -1) return;
          }

          const id = String(data.discussionId);

          if ((!app.current.discussion || id !== app.current.discussion.id()) && app.pushedUpdates.indexOf(id) === -1) {
            app.pushedUpdates.push(id);

            if (app.current instanceof IndexPage) {
              app.setTitleCount(app.pushedUpdates.length);
            }

            m.redraw();
          }
        }
      });

      extend(context, 'onunload', () => channels.main.unbind('newPost'));
    });
  });

  extend(DiscussionList.prototype, 'view', function(vdom) {
    if (app.pushedUpdates) {
      const count = app.pushedUpdates.length;

      if (count) {
        vdom.children.unshift(
          Button.component({
            className: 'Button Button--block DiscussionList-update',
            onclick: () => {
              this.refresh(false).then(() => {
                this.loadingUpdated = false;
                app.pushedUpdates = [];
                app.setTitleCount(0);
                m.redraw();
              });
              this.loadingUpdated = true;
            },
            loading: this.loadingUpdated,
            children: app.translator.transChoice('flarum-pusher.forum.discussion_list.show_updates_text', count, {count})
          })
        );
      }
    }
  });

  // Prevent any newly-created discussions from triggering the discussion list
  // update button showing.
  // TODO: Might be better pause the response to the push updates while the
  // composer is loading? idk
  extend(DiscussionList.prototype, 'addDiscussion', function(returned, discussion) {
    const index = app.pushedUpdates.indexOf(discussion.id());

    if (index !== -1) {
      app.pushedUpdates.splice(index, 1);
    }

    if (app.current instanceof IndexPage) {
      app.setTitleCount(app.pushedUpdates.length);
    }

    m.redraw();
  });

  extend(DiscussionPage.prototype, 'config', function(x, isInitialized, context) {
    if (isInitialized) return;

    app.pusher.then(channels => {
      channels.main.bind('newPost', data => {
        const id = String(data.discussionId);

        if (this.discussion && this.discussion.id() === id && this.stream) {
          const oldCount = this.discussion.commentCount();

          app.store.find('discussions', this.discussion.id()).then(() => {
            this.stream.update();

            if (!document.hasFocus()) {
              app.setTitleCount(Math.max(0, this.discussion.commentCount() - oldCount));

              $(window).one('focus', () => app.setTitleCount(0));
            }
          });
        }
      });

      extend(context, 'onunload', () => channels.main.unbind('newPost'));
    });
  });

  extend(IndexPage.prototype, 'actionItems', items => {
    items.remove('refresh');
  });

  app.pusher.then(channels => {
    if (channels.user) {
      channels.user.bind('notification', () => {
        app.session.user.pushAttributes({
          unreadNotificationCount: app.session.user.unreadNotificationCount() + 1,
          newNotificationCount: app.session.user.newNotificationCount() + 1
        });
        delete app.cache.notifications;
        m.redraw();
      });
    }
  });
});
<commit_msg>Use different CDN for loading Pusher JS library (#20)<commit_after>/*global Pusher*/

import { extend } from 'flarum/extend';
import app from 'flarum/app';
import DiscussionList from 'flarum/components/DiscussionList';
import DiscussionPage from 'flarum/components/DiscussionPage';
import IndexPage from 'flarum/components/IndexPage';
import Button from 'flarum/components/Button';

app.initializers.add('flarum-pusher', () => {
  const loadPusher = m.deferred();

  $.getScript('//cdn.jsdelivr.net/npm/pusher-js@3.0.0/dist/pusher.min.js', () => {
    const socket = new Pusher(app.forum.attribute('pusherKey'), {
      authEndpoint: app.forum.attribute('apiUrl') + '/pusher/auth',
      cluster: app.forum.attribute('pusherCluster'),
      auth: {
        headers: {
          'X-CSRF-Token': app.session.csrfToken
        }
      }
    });

    loadPusher.resolve({
      main: socket.subscribe('public'),
      user: app.session.user ? socket.subscribe('private-user' + app.session.user.id()) : null
    });
  });

  app.pusher = loadPusher.promise;
  app.pushedUpdates = [];

  extend(DiscussionList.prototype, 'config', function(x, isInitialized, context) {
    if (isInitialized) return;

    app.pusher.then(channels => {
      channels.main.bind('newPost', data => {
        const params = this.props.params;

        if (!params.q && !params.sort && !params.filter) {
          if (params.tags) {
            const tag = app.store.getBy('tags', 'slug', params.tags);

            if (data.tagIds.indexOf(tag.id()) === -1) return;
          }

          const id = String(data.discussionId);

          if ((!app.current.discussion || id !== app.current.discussion.id()) && app.pushedUpdates.indexOf(id) === -1) {
            app.pushedUpdates.push(id);

            if (app.current instanceof IndexPage) {
              app.setTitleCount(app.pushedUpdates.length);
            }

            m.redraw();
          }
        }
      });

      extend(context, 'onunload', () => channels.main.unbind('newPost'));
    });
  });

  extend(DiscussionList.prototype, 'view', function(vdom) {
    if (app.pushedUpdates) {
      const count = app.pushedUpdates.length;

      if (count) {
        vdom.children.unshift(
          Button.component({
            className: 'Button Button--block DiscussionList-update',
            onclick: () => {
              this.refresh(false).then(() => {
                this.loadingUpdated = false;
                app.pushedUpdates = [];
                app.setTitleCount(0);
                m.redraw();
              });
              this.loadingUpdated = true;
            },
            loading: this.loadingUpdated,
            children: app.translator.transChoice('flarum-pusher.forum.discussion_list.show_updates_text', count, {count})
          })
        );
      }
    }
  });

  // Prevent any newly-created discussions from triggering the discussion list
  // update button showing.
  // TODO: Might be better pause the response to the push updates while the
  // composer is loading? idk
  extend(DiscussionList.prototype, 'addDiscussion', function(returned, discussion) {
    const index = app.pushedUpdates.indexOf(discussion.id());

    if (index !== -1) {
      app.pushedUpdates.splice(index, 1);
    }

    if (app.current instanceof IndexPage) {
      app.setTitleCount(app.pushedUpdates.length);
    }

    m.redraw();
  });

  extend(DiscussionPage.prototype, 'config', function(x, isInitialized, context) {
    if (isInitialized) return;

    app.pusher.then(channels => {
      channels.main.bind('newPost', data => {
        const id = String(data.discussionId);

        if (this.discussion && this.discussion.id() === id && this.stream) {
          const oldCount = this.discussion.commentCount();

          app.store.find('discussions', this.discussion.id()).then(() => {
            this.stream.update();

            if (!document.hasFocus()) {
              app.setTitleCount(Math.max(0, this.discussion.commentCount() - oldCount));

              $(window).one('focus', () => app.setTitleCount(0));
            }
          });
        }
      });

      extend(context, 'onunload', () => channels.main.unbind('newPost'));
    });
  });

  extend(IndexPage.prototype, 'actionItems', items => {
    items.remove('refresh');
  });

  app.pusher.then(channels => {
    if (channels.user) {
      channels.user.bind('notification', () => {
        app.session.user.pushAttributes({
          unreadNotificationCount: app.session.user.unreadNotificationCount() + 1,
          newNotificationCount: app.session.user.newNotificationCount() + 1
        });
        delete app.cache.notifications;
        m.redraw();
      });
    }
  });
});
<|endoftext|>


<commit_before>evolution = function(game) {
    Phaser.State.call(this);

    this._game = game;
    this.ticks = 0;
    this._game.stage.smoothed = false;
}

var introDone = false;
var modulo = 50;
var secondModulo = 2;

//Extend the evolution object to be a Phaser.State
evolution.prototype = Object.create(Phaser.State.prototype);
evolution.prototype.constructor = evolution;

evolution.prototype.create = function(){
	introDone = false;
	modulo = 50;
	secondModulo = 2;

	this.stage.backgroundColor = 0x0f4737;

	this._game.showMessage("What? Mixel is evolving!");

	var firstSprite;
	var secondSprite;

	if(this._game._level == 3){
		firstSprite = 'player_pixel3';
		secondSprite = 'player_pacman1';
	} else if (this._game._level == 6){
		firstSprite = 'player_pacman1';
		secondSprite = 'player_dungeon1';
	} else if (this._game._level == 9){
		firstSprite = 'player_dungeon3';
		secondSprite = 'player_rpg';
	}

	this._firstCharacter = this._game.add.sprite(this._game.camera.width/2, this._game.camera.height/2 - 50, firstSprite);
	this._secondCharacter = this._game.add.sprite(this._game.camera.width/2, this._game.camera.height/2 - 50, secondSprite);

	//set to 4th frame if applicable (make sure pacman looks to the right)
	if(this._game._level < 8){
		this._firstCharacter.frame  = 3;
		this._secondCharacter.frame = 3;
	}

	this._firstCharacter.anchor.setTo(0.5, 0.5);
	this._secondCharacter.anchor.setTo(0.5, 0.5);

	//make sure all sprite are the same size, large
	this._firstCharacter.width = 150;
	this._firstCharacter.height = 150;
	this._secondCharacter.width = 200;
	this._secondCharacter.height = 200;

	//this._firstCharacter.tint = 0x000000;
	this._secondCharacter.tint = 0x000000;

	this._firstCharacter.alpha = 0;
	this._secondCharacter.alpha = 0;

	var fadeIn = this._game.add.tween(this._firstCharacter).to({ alpha: 1 }, 1000, Phaser.Easing.Linear.None).delay(500).start();

	//Color tween
	var obj = this._firstCharacter;
	var startColor = 0xffffff;
	var endColor = 0x000000;
	var time = 1000;

	var colorBlend = {step: 0};
    var colorTween = pixelEvolution.add.tween(colorBlend).to({step: 100}, time);
    colorTween.onUpdateCallback(function() {
      obj.tint = Phaser.Color.interpolateColor(startColor, endColor, 100, colorBlend.step);
    });

    obj.tint = startColor;
    colorTween.delay(1000);

    fadeIn.chain(colorTween);
    colorTween.onComplete = {dispatch:function(){ introDone = true; }};
}

evolution.prototype.update = function(){
	if(introDone){
		this.ticks++;

			if(modulo > 0){
				if(this.ticks % modulo == 0 && this._firstCharacter.alpha == 1){
					this.ticks = 0;
					modulo -= 5;
					this._firstCharacter.alpha = 0;
					this._secondCharacter.alpha = 1;
				} else {
					this._firstCharacter.alpha = 1;
					this._secondCharacter.alpha = 0;
				}
			} else if (secondModulo < 30) {
				if(this.ticks % secondModulo == 0 && this._secondCharacter.alpha == 1){
					this.ticks = 0;
					secondModulo += 10;
					this._secondCharacter.alpha = 0;
					this._firstCharacter.alpha = 1;
				} else {
					this._secondCharacter.alpha = 1;
					this._firstCharacter.alpha = 0;
				}
			} else if (secondModulo < 40) {
				this._secondCharacter.alpha = 1;
				this._firstCharacter.alpha = 0;

				var obj = this._secondCharacter;
				var startColor = 0x000000;
				var endColor = 0xffffff;
				var time = 1000;

				var colorBlend = {step: 0};
			    var colorTween = pixelEvolution.add.tween(colorBlend).to({step: 100}, time);
			    colorTween.onUpdateCallback(function() {
			      obj.tint = Phaser.Color.interpolateColor(startColor, endColor, 100, colorBlend.step);
			    });

			    obj.tint = startColor;
			    colorTween.start();

			    this._game.showMessage("Nice! Mixel now knows additional skills!");

			    this.ticks =0;

			    //very hacky way to end this if
			    secondModulo = 100;
			} else {
				this.transitionToNextLevel();
			}
	}

}

evolution.prototype.transitionToNextLevel = function() {
	if(this._game._level == 3){

		setTimeout((function(self) { return function() {
            if((self.input.activePointer.isDown || self.input.keyboard.isDown(Phaser.Keyboard.ENTER) || self.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR))){
				transitions.to('pacman');
			}
        }})(this),70);

	} else if (this._game._level == 6){
		setTimeout((function(self) { return function() {
			if((self.input.activePointer.isDown || self.input.keyboard.isDown(Phaser.Keyboard.ENTER) || self.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR))){
				transitions.to('dungeon');
			}
        }})(this),70);

	} else if (this._game._level == 9){
		setTimeout((function(self) { return function() {
			if((self.input.activePointer.isDown || self.input.keyboard.isDown(Phaser.Keyboard.ENTER) || self.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR))){
				transitions.to('rpg');
			}
        }})(this),70);

	}
}<commit_msg>Use the right sprite for the pacman to dungeon evolution<commit_after>evolution = function(game) {
    Phaser.State.call(this);

    this._game = game;
    this.ticks = 0;
    this._game.stage.smoothed = false;
}

var introDone = false;
var modulo = 50;
var secondModulo = 2;

//Extend the evolution object to be a Phaser.State
evolution.prototype = Object.create(Phaser.State.prototype);
evolution.prototype.constructor = evolution;

evolution.prototype.create = function(){
	introDone = false;
	modulo = 50;
	secondModulo = 2;

	this.stage.backgroundColor = 0x0f4737;

	this._game.showMessage("What? Mixel is evolving!");

	var firstSprite;
	var secondSprite;

	if(this._game._level == 3){
		firstSprite = 'player_pixel3';
		secondSprite = 'player_pacman1';
	} else if (this._game._level == 6){
		firstSprite = 'player_pacman3';
		secondSprite = 'player_dungeon1';
	} else if (this._game._level == 9){
		firstSprite = 'player_dungeon3';
		secondSprite = 'player_rpg';
	}

	this._firstCharacter = this._game.add.sprite(this._game.camera.width/2, this._game.camera.height/2 - 50, firstSprite);
	this._secondCharacter = this._game.add.sprite(this._game.camera.width/2, this._game.camera.height/2 - 50, secondSprite);

	//set to 4th frame if applicable (make sure pacman looks to the right)
	if(this._game._level < 8){
		this._firstCharacter.frame  = 3;
		this._secondCharacter.frame = 3;
	}

	this._firstCharacter.anchor.setTo(0.5, 0.5);
	this._secondCharacter.anchor.setTo(0.5, 0.5);

	//make sure all sprite are the same size, large
	this._firstCharacter.width = 150;
	this._firstCharacter.height = 150;
	this._secondCharacter.width = 200;
	this._secondCharacter.height = 200;

	//this._firstCharacter.tint = 0x000000;
	this._secondCharacter.tint = 0x000000;

	this._firstCharacter.alpha = 0;
	this._secondCharacter.alpha = 0;

	var fadeIn = this._game.add.tween(this._firstCharacter).to({ alpha: 1 }, 1000, Phaser.Easing.Linear.None).delay(500).start();

	//Color tween
	var obj = this._firstCharacter;
	var startColor = 0xffffff;
	var endColor = 0x000000;
	var time = 1000;

	var colorBlend = {step: 0};
    var colorTween = pixelEvolution.add.tween(colorBlend).to({step: 100}, time);
    colorTween.onUpdateCallback(function() {
      obj.tint = Phaser.Color.interpolateColor(startColor, endColor, 100, colorBlend.step);
    });

    obj.tint = startColor;
    colorTween.delay(1000);

    fadeIn.chain(colorTween);
    colorTween.onComplete = {dispatch:function(){ introDone = true; }};
}

evolution.prototype.update = function(){
	if(introDone){
		this.ticks++;

			if(modulo > 0){
				if(this.ticks % modulo == 0 && this._firstCharacter.alpha == 1){
					this.ticks = 0;
					modulo -= 5;
					this._firstCharacter.alpha = 0;
					this._secondCharacter.alpha = 1;
				} else {
					this._firstCharacter.alpha = 1;
					this._secondCharacter.alpha = 0;
				}
			} else if (secondModulo < 30) {
				if(this.ticks % secondModulo == 0 && this._secondCharacter.alpha == 1){
					this.ticks = 0;
					secondModulo += 10;
					this._secondCharacter.alpha = 0;
					this._firstCharacter.alpha = 1;
				} else {
					this._secondCharacter.alpha = 1;
					this._firstCharacter.alpha = 0;
				}
			} else if (secondModulo < 40) {
				this._secondCharacter.alpha = 1;
				this._firstCharacter.alpha = 0;

				var obj = this._secondCharacter;
				var startColor = 0x000000;
				var endColor = 0xffffff;
				var time = 1000;

				var colorBlend = {step: 0};
			    var colorTween = pixelEvolution.add.tween(colorBlend).to({step: 100}, time);
			    colorTween.onUpdateCallback(function() {
			      obj.tint = Phaser.Color.interpolateColor(startColor, endColor, 100, colorBlend.step);
			    });

			    obj.tint = startColor;
			    colorTween.start();

			    this._game.showMessage("Nice! Mixel now knows additional skills!");

			    this.ticks =0;

			    //very hacky way to end this if
			    secondModulo = 100;
			} else {
				this.transitionToNextLevel();
			}
	}

}

evolution.prototype.transitionToNextLevel = function() {
	if(this._game._level == 3){

		setTimeout((function(self) { return function() {
            if((self.input.activePointer.isDown || self.input.keyboard.isDown(Phaser.Keyboard.ENTER) || self.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR))){
				transitions.to('pacman');
			}
        }})(this),70);

	} else if (this._game._level == 6){
		setTimeout((function(self) { return function() {
			if((self.input.activePointer.isDown || self.input.keyboard.isDown(Phaser.Keyboard.ENTER) || self.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR))){
				transitions.to('dungeon');
			}
        }})(this),70);

	} else if (this._game._level == 9){
		setTimeout((function(self) { return function() {
			if((self.input.activePointer.isDown || self.input.keyboard.isDown(Phaser.Keyboard.ENTER) || self.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR))){
				transitions.to('rpg');
			}
        }})(this),70);

	}
}<|endoftext|>


<commit_before><cheatsheet>

Cheatsheet
 {cheatsheet.version}

Written with ♥ by @{cheatsheet.author}

Part of happy-css.com

HTML CONSTRUCTS

Accessing Elements

HTML Elements

					You can also use id if you are more comfortable with that

							<input name="username"/>
							<script>
								this.username.value = 'admin'
							</script>

Riot Sub Tags

							<my-sub-tag name="subtag"></my-sub-tag>
							<script>
								this.tags.subtag
							</script>

Foreach

Arrays

							<div each=\{item, index in items\}>
								\{index\} - \{item\}
							</div>
							<script>
								this.items = ['this', 'is', 'a' 'riot!']
							</script>

Objects

							<div each=\{key, value in items\}>
								\{key\} - \{value\}
							</div>
							<script>
								this.items =
								\{
									file: 'riot.png',
									path: 'images/'
								\}
							</script>

Yield

HTML writtin inside custom tags you call, can be retrieved via the yield tag

						<yield/>

Conditions

Block Element

						<div if=\{ condition \}></div>

Inline

						<div class=\{ active : condition\}></div>

Ternary

					This is a JS expression, you need quotes for strings

						<div class=\{ condition ? 'enabled' : 'disabled'\}></div>

Tag Object

				Retrievable within Tag Scope (this)

Methods

mixin()

TRIGGER EVENTS

update()

mount()

unmount()

TRIGGER METHODS

trigger()

on()

one()

off()

Properties

isMounted

opts

parent

root - tag element

tags - children riot tags: defined by their name attribute or their tagname

Triggers - Observable

				The Riot event system

Events

this.update() is same as this.trigger('update')

update()

updated()

mount()

unmount()

Trigger

trigger('event') - trigger event

on('event', function()\{\}) - react on event trigger

one('event', function()\{\}) - only once

off() - don't listen to event anymore

Riot Object

Methods

compile()

mixin()

mount()

mountTo()

observable()

route()

tag()

update()

Properties

parsers

version

Router

				Listening to and changing the url hash (#customer/edit/3)

riot.route.route(args, function)

riot.route.route('customer/list')

riot.route.start()

riot.route.stop()

riot.route.exec()

riot.route.parser()

Mixin

				Reusable functions

Defining Mixins

						var object = \{ init : function()\{\} \}

load mixin under namespace

						riot.mixin('name', object)

Using mixin within tag

						this.mixin('name')

Global Events

			All GlobalEventHandlers

Events are prevented by default! you can avoid that by returning true at the end of your event function (return true)

				Usage:

					<div onclick=\{ method \}></div>

{section}

						{event}

This is a privately maintained inofficial cheatsheet, but you are welcome to contribute!

</cheatsheet><commit_msg>Adding a link to the event handlers, reorganizing the description<commit_after><cheatsheet>

Cheatsheet
 {cheatsheet.version}

Written with ♥ by @{cheatsheet.author}

Part of happy-css.com

HTML CONSTRUCTS

Accessing Elements

HTML Elements

					You can also use id if you are more comfortable with that

							<input name="username"/>
							<script>
								this.username.value = 'admin'
							</script>

Riot Sub Tags

							<my-sub-tag name="subtag"></my-sub-tag>
							<script>
								this.tags.subtag
							</script>

Foreach

Arrays

							<div each=\{item, index in items\}>
								\{index\} - \{item\}
							</div>
							<script>
								this.items = ['this', 'is', 'a' 'riot!']
							</script>

Objects

							<div each=\{key, value in items\}>
								\{key\} - \{value\}
							</div>
							<script>
								this.items =
								\{
									file: 'riot.png',
									path: 'images/'
								\}
							</script>

Yield

HTML writtin inside custom tags you call, can be retrieved via the yield tag

						<yield/>

Conditions

Block Element

						<div if=\{ condition \}></div>

Inline

						<div class=\{ active : condition\}></div>

Ternary

					This is a JS expression, you need quotes for strings

						<div class=\{ condition ? 'enabled' : 'disabled'\}></div>

Tag Object

				Retrievable within Tag Scope (this)

Methods

mixin()

TRIGGER EVENTS

update()

mount()

unmount()

TRIGGER METHODS

trigger()

on()

one()

off()

Properties

isMounted

opts

parent

root - tag element

tags - children riot tags: defined by their name attribute or their tagname

Triggers - Observable

				The Riot event system

Events

this.update() is same as this.trigger('update')

update()

updated()

mount()

unmount()

Trigger

trigger('event') - trigger event

on('event', function()\{\}) - react on event trigger

one('event', function()\{\}) - only once

off() - don't listen to event anymore

Riot Object

Methods

compile()

mixin()

mount()

mountTo()

observable()

route()

tag()

update()

Properties

parsers

version

Router

				Listening to and changing the url hash (#customer/edit/3)

riot.route.route(args, function)

riot.route.route('customer/list')

riot.route.start()

riot.route.stop()

riot.route.exec()

riot.route.parser()

Mixin

				Reusable functions

Defining Mixins

						var object = \{ init : function()\{\} \}

load mixin under namespace

						riot.mixin('name', object)

Using mixin within tag

						this.mixin('name')

Global Events

			All GlobalEventHandlers

The default event handler behavior is automatically cancelled unless the element is a checkbox or radio button.
 This means that e.preventDefault() is already called for you, because this is what you usually want.

			you can avoid that by returning true at the end of your event function (return true) return true)

				Usage:

					<div onclick=\{ method \}></div>

{section}

						{event}

This is a privately maintained inofficial cheatsheet, but you are welcome to contribute!

</cheatsheet><|endoftext|>


<commit_before>import get from 'lodash/fp/get'
import diffMatchPatch from 'diff-match-patch'

export const Sameness = {
    EXACTLY: 5,    // Perfect match, containing exactly the same data.
    OSTENSIBLY: 4, // Semantically the same, differing only in e.g. whitespace.
    MOSTLY: 3,     // Only minor visible edits, e.g. some typo corrections.
    PARTLY: 2,     // Some content changed, e.g. parts were added or (re)moved.
    HARDLY: 1,     // Whole page seems newly created, but with similar purpose.
    UNRELATED: 0,  // Completely different topic, e.g. domain was transferred.
    UNKNOWN: NaN,  // Not enough information to make a comparison.
}

// Get a rough similarity metric between strings (a number between 0 and 1).
function stringSimilarity(text1, text2) {
    const dmp = new diffMatchPatch()
    dmp.Diff_Timeout = 0.1
    const diff = dmp.diff_main(text1, text2)
    dmp.diff_cleanupSemantic(diff)
    const distance = dmp.diff_levenshtein(diff)
    const maxDistance = Math.max(text1.length, text2.length)
    const similarity = 1 - distance / maxDistance
    return similarity
}

// Tell how similar two strings are in a qualitative way.
function textSameness(text1, text2) {
    if (!text1 || !text2) { return Sameness.UNKNOWN }

    if (text1 === text2) { return Sameness.EXACTLY }

    const normaliseWhitespace = s => s.trim().replace(/s+/g, ' ')
    if (normaliseWhitespace(text1) === normaliseWhitespace(text2)) { return Sameness.OSTENSIBLY }

    const textSimilarity = stringSimilarity(text1, text2)

    if (textSimilarity > 0.9) { return Sameness.MOSTLY }

    if (textSimilarity > 0.7) { return Sameness.PARTLY }

    if (textSimilarity > 0.5) { return Sameness.HARDLY }

    return Sameness.UNRELATED
}

// Tell to which degree two versions of a page can be considered to be the same.
export default function determinePageSameness(page1, page2) {
    const pages = [page1, page2]

    // Currently we just compare the title and body text of the page.
    // TODO Replace this simple code with something more sophisticated.

    // Compare page titles
    const titleSameness = textSameness(...pages.map(get('title')))

    // Compare page text contents
    const textContents = pages.map(get('extractedText.bodyInnerText'))
    const textContentSameness = textSameness(...textContents)

    // Return the lowest (most pessimistic) score among them.
    return Math.min(titleSameness, textContentSameness)
}
<commit_msg>fix capital letter constructor<commit_after>import get from 'lodash/fp/get'
import DiffMatchPatch from 'diff-match-patch'

export const Sameness = {
    EXACTLY: 5,    // Perfect match, containing exactly the same data.
    OSTENSIBLY: 4, // Semantically the same, differing only in e.g. whitespace.
    MOSTLY: 3,     // Only minor visible edits, e.g. some typo corrections.
    PARTLY: 2,     // Some content changed, e.g. parts were added or (re)moved.
    HARDLY: 1,     // Whole page seems newly created, but with similar purpose.
    UNRELATED: 0,  // Completely different topic, e.g. domain was transferred.
    UNKNOWN: NaN,  // Not enough information to make a comparison.
}

// Get a rough similarity metric between strings (a number between 0 and 1).
function stringSimilarity(text1, text2) {
    const dmp = new DiffMatchPatch()
    dmp.Diff_Timeout = 0.1
    const diff = dmp.diff_main(text1, text2)
    dmp.diff_cleanupSemantic(diff)
    const distance = dmp.diff_levenshtein(diff)
    const maxDistance = Math.max(text1.length, text2.length)
    const similarity = 1 - distance / maxDistance
    return similarity
}

// Tell how similar two strings are in a qualitative way.
function textSameness(text1, text2) {
    if (!text1 || !text2) { return Sameness.UNKNOWN }

    if (text1 === text2) { return Sameness.EXACTLY }

    const normaliseWhitespace = s => s.trim().replace(/s+/g, ' ')
    if (normaliseWhitespace(text1) === normaliseWhitespace(text2)) { return Sameness.OSTENSIBLY }

    const textSimilarity = stringSimilarity(text1, text2)

    if (textSimilarity > 0.9) { return Sameness.MOSTLY }

    if (textSimilarity > 0.7) { return Sameness.PARTLY }

    if (textSimilarity > 0.5) { return Sameness.HARDLY }

    return Sameness.UNRELATED
}

// Tell to which degree two versions of a page can be considered to be the same.
export default function determinePageSameness(page1, page2) {
    const pages = [page1, page2]

    // Currently we just compare the title and body text of the page.
    // TODO Replace this simple code with something more sophisticated.

    // Compare page titles
    const titleSameness = textSameness(...pages.map(get('title')))

    // Compare page text contents
    const textContents = pages.map(get('extractedText.bodyInnerText'))
    const textContentSameness = textSameness(...textContents)

    // Return the lowest (most pessimistic) score among them.
    return Math.min(titleSameness, textContentSameness)
}
<|endoftext|>