<commit_before>var game = {};

game.init = function() {
  this.ball;
  this.paddle;
  this.bricks;

  this.ballOnPaddle = true;
  this.wallBuilt = false;
  this.wallBuiltAudio = false;
  this.deadBricks = 0;
  this.brickX = 0;
  this.brickY = 0;

  this.lives = 3;
  this.score = 0;

  this.scoreText;
  this.livesText;
  this.introText;

  this.s;
};

game.create = function () {
  this.weNeedToBuild = game.add.audio('weNeedToBuild');

  this.smash1 = game.add.audio('smash1');
  this.smash2 = game.add.audio('smash2');

  this.stopIt = game.add.audio('stopIt');
  this.beef = game.add.audio('beef');
  this.cameoutofnowhere = game.add.audio('cameoutofnowhere');
  this.stupidpeopleinourcountry = game.add.audio('stupidpeopleinourcountry')

  game.physics.startSystem(Phaser.Physics.ARCADE);

  //  We check bounds collisions against all walls other than the bottom one
  game.physics.arcade.checkCollision.down = false;

  this.s = game.add.tileSprite(game.world.centerX, game.world.centerY, 600, 600, 'seal');
  this.s.anchor.setTo(0.5, 0.5);

  this.bricks = game.add.group();
  this.bricks.enableBody = true;
  this.bricks.physicsBodyType = Phaser.Physics.ARCADE;

  this.trump = game.add.sprite(game.world.centerX, 0, 'trump', 'smugtrump.png');
  game.physics.enable(this.trump, Phaser.Physics.ARCADE);

  this.trump.body.collideWorldBounds = true;
  this.trump.body.bounce.set(1);
  this.trump.body.drag.x = 300;

  this.paddle = game.add.sprite(game.world.centerX, 500, 'breakout', 'bumper.png');
  this.paddle.anchor.setTo(0.5, 0.5);

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

  this.paddle.body.collideWorldBounds = true;
  this.paddle.body.bounce.set(1);
  this.paddle.body.immovable = true;

  this.ball = game.add.sprite(game.world.centerX, this.paddle.y - 16, 'breakout', 'ball1.png');
  this.ball.anchor.set(0.5);
  this.ball.checkWorldBounds = true;

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

  this.ball.body.collideWorldBounds = true;
  this.ball.body.bounce.set(1);

  this.ball.animations.add('spin', [ 'ball1.png', 'ball2.png', 'ball3.png', 'ball4.png', 'ball5.png' ], 50, true, false);

  this.ball.events.onOutOfBounds.add(game.ballLost, this);

  this.scoreText = game.add.text(32, 600, 'score: 0', { font: "20px Arial", fill: "#ffffff", align: "left" });
  this.liveText = game.add.text(520, 600, 'lives: 3', { font: "20px Arial", fill: "#ffffff", align: "left" });
  this.introText = game.add.text(game.world.centerX, 400, '- click to start -', { font: "40px Arial", fill: "red", align: "center" });
  this.introText.anchor.setTo(0.5, 0.5);

  game.input.onDown.add(game.releaseBall, this);

};

game.update = function () {

  if(this.trump.body.velocity.x === 0) {
    this.trump.loadTexture('trump', 0);
  }
  this.trump.body.velocity.y = 0;

  //  Fun, but a little sea-sick inducing :) Uncomment if you like!
  // s.tilePosition.x += (game.input.speed.x / 2);

  game.buildThatWall();

  this.paddle.x = game.input.x;

  if (this.paddle.x < 24)
  {
      this.paddle.x = 24;
  }
  else if (this.paddle.x > game.width - 24)
  {
      this.paddle.x = game.width - 24;
  }

  if (this.ballOnPaddle)
  {
      this.ball.body.x = this.paddle.x;
  }
  else
  {
      game.physics.arcade.collide(this.ball, this.paddle, game.ballHitPaddle, null, this);
      game.physics.arcade.collide(this.ball, this.bricks, game.ballHitBrick, null, this);
      game.physics.arcade.collide(this.ball, this.trump, game.ballHitTrump, null, this);
  }
}

game.releaseBall = function () {

    if (this.ballOnPaddle)
    {
        this.ballOnPaddle = false;
        this.ball.body.velocity.y = -300;
        this.ball.body.velocity.x = -75;
        this.ball.animations.play('spin');
        this.introText.visible = false;
    }

}

game.ballLost = function () {
    this.stupidpeopleinourcountry.play();
    this.lives--;
    this.liveText.text = 'lives: ' + this.lives;

    if (this.lives === 0)
    {
        game.gameOver();
    }
    else
    {
        this.ballOnPaddle = true;

        this.ball.reset(this.paddle.body.x + 16, this.paddle.y - 16);

        this.ball.animations.stop();
    }

}

game.gameOver = function () {

    this.ball.body.velocity.setTo(0, 0);

    this.introText.text = 'Game Over!';
    this.introText.visible = true;
    this.time.events.add(Phaser.Timer.SECOND * 4, function() { this.state.start('menu'); }, this);

}

game.ballHitBrick = function (_ball, _brick) {

    if(this.rnd.integerInRange(0, 1) === 1){
      this.smash1.play();
    } else {
      this.smash2.play();
    }

    _brick.kill();

    this.score += 10;

    this.scoreText.text = 'score: ' + this.score;

    //  Are they any bricks left?
    if (this.bricks.countLiving() == 0)
    {
        //  New level starts
        this.score += 1000;
        this.scoreText.text = 'score: ' + this.score;
        this.introText.text = '- Next Level -';

        //  Let's move the ball back to the paddle
        this.ballOnPaddle = true;
        this.ball.body.velocity.set(0);
        this.ball.x = this.paddle.x + 16;
        this.ball.y = this.paddle.y - 16;
        this.ball.animations.stop();

        //  And bring the bricks back from the dead :)
        // this.bricks.callAll('revive');
        this.wallBuilt = false;
        this.wallBuiltAudio = false;
    }

}

game.ballHitPaddle = function (_ball, _paddle) {

    var diff = 0;

    if (_ball.x < _paddle.x)
    {
        //  Ball is on the left-hand side of the paddle
        diff = _paddle.x - _ball.x;
        _ball.body.velocity.x = (-10 * diff);
    }
    else if (_ball.x > _paddle.x)
    {
        //  Ball is on the right-hand side of the paddle
        diff = _ball.x -_paddle.x;
        _ball.body.velocity.x = (10 * diff);
    }
    else
    {
        //  Ball is perfectly in the middle
        //  Add a little random X to stop it bouncing straight up!
        _ball.body.velocity.x = 2 + Math.random() * 8;
    }
  }

  game.ballHitTrump = function (_ball, _trump) {

    var trumpHit = this.rnd.integerInRange(0, 2)

    switch(trumpHit) {
      case 0:
        this.stopIt.play();
        break;
      case 1:
        this.beef.play();
        break;
      case 2:
        this.cameoutofnowhere.play();
        break;
    }

    _trump.loadTexture('trump', 1);
    var diff = 0;
    this.score += 100;
    this.scoreText.text = 'score: ' + this.score;

    if (_ball.x < _trump.x)
    {
        //  Ball is on the left-hand side of the paddle
        diff = _trump.x - _ball.x;
        _ball.body.velocity.x = (-10 * diff);
    }
    else if (_ball.x > _trump.x)
    {
        //  Ball is on the right-hand side of the paddle
        diff = _ball.x -_trump.x;
        _ball.body.velocity.x = (10 * diff);
    }
    else
    {
        //  Ball is perfectly in the middle
        //  Add a little random X to stop it bouncing straight up!
        _ball.body.velocity.x = 2 + Math.random() * 8;
    }

    if (_ball.y < _trump.y)
    {
        //  Ball is on the left-hand side of the paddle
        diff = _trump.y - _ball.y;
        _ball.body.velocity.y = (-10 * diff);
    }
    else if (_ball.y > _trump.y)
    {
        //  Ball is on the right-hand side of the paddle
        diff = _ball.y -_trump.y;
        _ball.body.velocity.y = (10 * diff);
    }
    else
    {
        //  Ball is perfectly in the middle
        //  Add a little random X to stop it bouncing straight up!
        _ball.body.velocity.y = 2 + Math.random() * 8;
    }

    if(this.ball.body.velocity.y === 0){
      this.ball.body.velocity.y = 10;
    }
  }

  game.buildThatWall = function () {

    if(!this.wallBuiltAudio) {
      this.weNeedToBuild.play();
      this.wallBuiltAudio = true;
    }

    if(!this.wallBuilt)
    {
      if(this.bricks.length === 60) { //60
        if(this.bricks.countDead() > 0) {
          var dead = this.bricks.getFirstDead();
          dead.revive();
          this.deadBricks ++;
        } else {
          this.deadBricks = 0;
          this.wallBuilt = true;
        }

      } else {
        if(this.brickY === 4) { //4
          this.wallBuilt = true;
        } else {
          var brick;
          brick = this.bricks.create(30 + (this.brickX * 36), 100 + (this.brickY * 52), 'breakout', 'brick.png');
          brick.body.bounce.set(1);
          brick.body.immovable = true;
          this.brickX ++;

          if(this.brickX === 15)
          {
            this.brickX = 0
            this.brickY ++;
          }
        }
      }
    }
  }

module.exports = game;
<commit_msg>Added maxVelocity on Ball<commit_after>var game = {};

game.init = function() {
  this.ball;
  this.paddle;
  this.bricks;

  this.ballOnPaddle = true;
  this.wallBuilt = false;
  this.wallBuiltAudio = false;
  this.deadBricks = 0;
  this.brickX = 0;
  this.brickY = 0;

  this.lives = 3;
  this.score = 0;

  this.scoreText;
  this.livesText;
  this.introText;

  this.s;
};

game.create = function () {
  this.weNeedToBuild = game.add.audio('weNeedToBuild');

  this.smash1 = game.add.audio('smash1');
  this.smash2 = game.add.audio('smash2');

  this.stopIt = game.add.audio('stopIt');
  this.beef = game.add.audio('beef');
  this.cameoutofnowhere = game.add.audio('cameoutofnowhere');
  this.stupidpeopleinourcountry = game.add.audio('stupidpeopleinourcountry')

  game.physics.startSystem(Phaser.Physics.ARCADE);

  //  We check bounds collisions against all walls other than the bottom one
  game.physics.arcade.checkCollision.down = false;

  this.s = game.add.tileSprite(game.world.centerX, game.world.centerY, 600, 600, 'seal');
  this.s.anchor.setTo(0.5, 0.5);

  this.bricks = game.add.group();
  this.bricks.enableBody = true;
  this.bricks.physicsBodyType = Phaser.Physics.ARCADE;

  this.trump = game.add.sprite(game.world.centerX, 0, 'trump', 'smugtrump.png');
  game.physics.enable(this.trump, Phaser.Physics.ARCADE);

  this.trump.body.collideWorldBounds = true;
  this.trump.body.bounce.set(1);
  this.trump.body.drag.x = 300;

  this.paddle = game.add.sprite(game.world.centerX, 500, 'breakout', 'bumper.png');
  this.paddle.anchor.setTo(0.5, 0.5);

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

  this.paddle.body.collideWorldBounds = true;
  this.paddle.body.bounce.set(1);
  this.paddle.body.immovable = true;

  this.ball = game.add.sprite(game.world.centerX, this.paddle.y - 16, 'breakout', 'ball1.png');
  this.ball.anchor.set(0.5);
  this.ball.checkWorldBounds = true;

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

  this.ball.body.collideWorldBounds = true;
  this.ball.body.bounce.set(1);
  this.ball.body.maxVelocity.x = 700;
  this.ball.body.maxVelocity.y = 700;

  this.ball.animations.add('spin', [ 'ball1.png', 'ball2.png', 'ball3.png', 'ball4.png', 'ball5.png' ], 50, true, false);

  this.ball.events.onOutOfBounds.add(game.ballLost, this);

  this.scoreText = game.add.text(32, 600, 'score: 0', { font: "20px Arial", fill: "#ffffff", align: "left" });
  this.liveText = game.add.text(520, 600, 'lives: 3', { font: "20px Arial", fill: "#ffffff", align: "left" });
  this.introText = game.add.text(game.world.centerX, 400, '- click to start -', { font: "40px Arial", fill: "red", align: "center" });
  this.introText.anchor.setTo(0.5, 0.5);

  game.input.onDown.add(game.releaseBall, this);

};

game.update = function () {

  if(this.trump.body.velocity.x === 0) {
    this.trump.loadTexture('trump', 0);
  }
  this.trump.body.velocity.y = 0;

  //  Fun, but a little sea-sick inducing :) Uncomment if you like!
  // s.tilePosition.x += (game.input.speed.x / 2);

  game.buildThatWall();

  this.paddle.x = game.input.x;

  if (this.paddle.x < 24)
  {
      this.paddle.x = 24;
  }
  else if (this.paddle.x > game.width - 24)
  {
      this.paddle.x = game.width - 24;
  }

  if (this.ballOnPaddle)
  {
      this.ball.body.x = this.paddle.x;
  }
  else
  {
      game.physics.arcade.collide(this.ball, this.paddle, game.ballHitPaddle, null, this);
      game.physics.arcade.collide(this.ball, this.bricks, game.ballHitBrick, null, this);
      game.physics.arcade.collide(this.ball, this.trump, game.ballHitTrump, null, this);
  }
}

game.releaseBall = function () {

    if (this.ballOnPaddle)
    {
        this.ballOnPaddle = false;
        this.ball.body.velocity.y = -300;
        this.ball.body.velocity.x = -75;
        this.ball.animations.play('spin');
        this.introText.visible = false;
    }

}

game.ballLost = function () {
    this.stupidpeopleinourcountry.play();
    this.lives--;
    this.liveText.text = 'lives: ' + this.lives;

    if (this.lives === 0)
    {
        game.gameOver();
    }
    else
    {
        this.ballOnPaddle = true;

        this.ball.reset(this.paddle.body.x + 16, this.paddle.y - 16);

        this.ball.animations.stop();
    }

}

game.gameOver = function () {

    this.ball.body.velocity.setTo(0, 0);

    this.introText.text = 'Game Over!';
    this.introText.visible = true;
    this.time.events.add(Phaser.Timer.SECOND * 4, function() { this.state.start('menu'); }, this);

}

game.ballHitBrick = function (_ball, _brick) {

    if(this.rnd.integerInRange(0, 1) === 1){
      this.smash1.play();
    } else {
      this.smash2.play();
    }

    _brick.kill();

    this.score += 10;

    this.scoreText.text = 'score: ' + this.score;

    //  Are they any bricks left?
    if (this.bricks.countLiving() == 0)
    {
        //  New level starts
        this.score += 1000;
        this.scoreText.text = 'score: ' + this.score;
        this.introText.text = '- Next Level -';

        //  Let's move the ball back to the paddle
        this.ballOnPaddle = true;
        this.ball.body.velocity.set(0);
        this.ball.x = this.paddle.x + 16;
        this.ball.y = this.paddle.y - 16;
        this.ball.animations.stop();

        //  And bring the bricks back from the dead :)
        // this.bricks.callAll('revive');
        this.wallBuilt = false;
        this.wallBuiltAudio = false;
    }

}

game.ballHitPaddle = function (_ball, _paddle) {

    var diff = 0;

    if (_ball.x < _paddle.x)
    {
        //  Ball is on the left-hand side of the paddle
        diff = _paddle.x - _ball.x;
        _ball.body.velocity.x = (-10 * diff);
    }
    else if (_ball.x > _paddle.x)
    {
        //  Ball is on the right-hand side of the paddle
        diff = _ball.x -_paddle.x;
        _ball.body.velocity.x = (10 * diff);
    }
    else
    {
        //  Ball is perfectly in the middle
        //  Add a little random X to stop it bouncing straight up!
        _ball.body.velocity.x = 2 + Math.random() * 8;
    }
  }

  game.ballHitTrump = function (_ball, _trump) {

    var trumpHit = this.rnd.integerInRange(0, 2)

    switch(trumpHit) {
      case 0:
        this.stopIt.play();
        break;
      case 1:
        this.beef.play();
        break;
      case 2:
        this.cameoutofnowhere.play();
        break;
    }

    _trump.loadTexture('trump', 1);
    var diff = 0;
    this.score += 100;
    this.scoreText.text = 'score: ' + this.score;

    if (_ball.x < _trump.x)
    {
        //  Ball is on the left-hand side of the paddle
        diff = _trump.x - _ball.x;
        _ball.body.velocity.x = (-10 * diff);
    }
    else if (_ball.x > _trump.x)
    {
        //  Ball is on the right-hand side of the paddle
        diff = _ball.x -_trump.x;
        _ball.body.velocity.x = (10 * diff);
    }
    else
    {
        //  Ball is perfectly in the middle
        //  Add a little random X to stop it bouncing straight up!
        _ball.body.velocity.x = 2 + Math.random() * 8;
    }

    if (_ball.y < _trump.y)
    {
        //  Ball is on the left-hand side of the paddle
        diff = _trump.y - _ball.y;
        _ball.body.velocity.y = (-10 * diff);
    }
    else if (_ball.y > _trump.y)
    {
        //  Ball is on the right-hand side of the paddle
        diff = _ball.y -_trump.y;
        _ball.body.velocity.y = (10 * diff);
    }
    else
    {
        //  Ball is perfectly in the middle
        //  Add a little random X to stop it bouncing straight up!
        _ball.body.velocity.y = 2 + Math.random() * 8;
    }

    if(this.ball.body.velocity.y === 0){
      this.ball.body.velocity.y = 10;
    }
  }

  game.buildThatWall = function () {

    if(!this.wallBuiltAudio) {
      this.weNeedToBuild.play();
      this.wallBuiltAudio = true;
    }

    if(!this.wallBuilt)
    {
      if(this.bricks.length === 60) { //60
        if(this.bricks.countDead() > 0) {
          var dead = this.bricks.getFirstDead();
          dead.revive();
          this.deadBricks ++;
        } else {
          this.deadBricks = 0;
          this.wallBuilt = true;
        }

      } else {
        if(this.brickY === 4) { //4
          this.wallBuilt = true;
        } else {
          var brick;
          brick = this.bricks.create(30 + (this.brickX * 36), 100 + (this.brickY * 52), 'breakout', 'brick.png');
          brick.body.bounce.set(1);
          brick.body.immovable = true;
          this.brickX ++;

          if(this.brickX === 15)
          {
            this.brickX = 0
            this.brickY ++;
          }
        }
      }
    }
  }

module.exports = game;
<|endoftext|>


<commit_before>/*
 *  Copyright 2021 Google LLC
 *
 *  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.
 */

/**
 * @typedef {Object} PlayBillingLookupItem
 * @property {string} itemId - ID of the item
 * @property {PurchaseType} purchaseType - Type of purchase
 */

/**
 * Play Billing Service
 *
 * Creates an instance of Play Billing Service that can be used to make purchases from Play from within a PWA installed from Play using the Digital Goods API
 */
export class PlayBillingService {
  /**
   * @param {PlayBillingLookupItem[]} lookups - Array of lookup items (itemId, purchaseType)
   */
  constructor(lookups) {
    this.lookups = Object.freeze(lookups);
    this.serviceURL = 'https://play.google.com/billing';
    this.init();
  }

  /**
   * Runs async initialization code
   * @private
   */
  async init() {
    // Get service
    if (!this.service) {
      this.service = Object.freeze(await window.getDigitalGoodsService(this.serviceURL));
    }

    // Get item details
    if (!this.skus) {
      await this.updateSkus();
    }
  }

  /**
   * Returns the purchase type for the provided purchase
   * @private
   * @param {PurchaseDetails} purchase - Purchase to check type for
   * @return {PurchaseType}
   */
  _getPurchaseType(purchase) {
    const skuMatch = this.skus.find((sku) => sku.itemId === purchase.itemId);
    const purchaseType = skuMatch ? skuMatch.purchaseType : 'onetime';
    return purchaseType;
  }

  /**
   * Updates service's SKUs with the latest info from Play.
   * Optionally can update the available SKUs for this service, too
   * @param {PlayBillingLookupItem[]} lookups - Array of lookup items (itemId, purchaseType)
   */
  async updateSkus(lookups) {
    if (lookups) {
      this.lookups = Object.freeze(lookups);
    }

    const details = await this.service.getDetails(this.lookups.map((sku) => sku.itemId));
    this.skus = Object.freeze(
      details.map((d) =>
        Object.assign(
          d,
          this.lookups.find((s) => s.itemId === d.itemId),
        ),
      ),
    );
  }

  /**
   * List existing entitlements that haven't been consumed yet or are on-going subscriptions
   * @return {PurchaseDetails[]} Also includes the purchase's purchase type
   */
  async getPurchases() {
    if (!this.skus) {
      await this.init();
    }
    let purchases = (await this.service.listPurchases()).map((p) =>
      Object.assign(p, { purchaseType: this._getPurchaseType(p) }),
    );

    // Acknowledge any un-acknowledged purchases.
    let acknowledged = false;
    for (const purchase of purchases) {
      if (purchase.purchaseState == 'purchased' && !purchase.acknowledged) {
        await this.acknowledge(purchase.purchaseToken, purchase);
        acknowledged = true;
      }
    }

    // Update purchases again after acknowledging un-acknowledged purchases
    if (acknowledged) {
      purchases = (await this.service.listPurchases()).map((p) =>
        Object.assign(p, { purchaseType: this._getPurchaseType(p) }),
      );
    }

    return Object.freeze(purchases);
  }

  /**
   * List available SKUs
   * @return {PlayBillingServiceSku[]}
   */
  async getSkus() {
    if (!this.skus) await this.init();

    return this.skus;
  }

  /**
   * Get the SKU's price in the provided currency
   * @param {PlayBillingServiceSku} sku - SKU to determine price for
   * @param {string} locale - Locale to convert price into
   * @return {string}
   */
  getSkuPrice(sku, locale) {
    return new Intl.NumberFormat(locale, {
      style: 'currency',
      currency: sku.price.currency,
    }).format(Number(sku.price.value));
  }

  /**
   * Apps should acknowledge the purchase after confirming that the purchase token
   * has been associated with a user. This app only acknowledges purchases after
   * successfully receiving the subscription data back from the server.
   *
   * Developers can choose to acknowledge purchases from a server using the
   * Google Play Developer API. The server has direct access to the user database,
   * so using the Google Play Developer API for acknowledgement might be more reliable.
   *
   * If the purchase token is not acknowledged within 3 days,
   * then Google Play will automatically refund and revoke the purchase.
   * This behavior helps ensure that users are not charged for subscriptions unless the
   * user has successfully received access to the content.
   * This eliminates a category of issues where users complain to developers
   * that they paid for something that the app is not giving to them.
   * @param {string} token - Purchase token
   * @param {PurchaseDetailsWithType|PlayBillingServiceSku} item - SKU or purchase to acknowledge
   */
  async acknowledge(token, item) {
    if (!this.service) {
      await this.init();
    }

    // Subscriptions need to be acknowledges as 'onetime'
    const type = item.purchaseType === 'subscription' ? 'onetime' : item.purchaseType;

    return await this.service.acknowledge(token, type);
  }

  /**
   * Forces a purchase token to be consumed.
   * Purchases are coerced into a 'repeatable' type as that's the only type that will trigger consumption in Play
   * @param {string} token - Purchase token
   */
  async consume(token) {
    return await this.acknowledge(token, { purchaseType: 'repeatable' });
  }

  /**
   * Request that the provided SKU is purchased. Will acknowledge purchase on success
   * @param {PlayBillingServiceSku|string} purchaseSku - Either the SKU ID to purchase, or the actual SKU
   * @param {PurchaseDetails} oldPurchase - The previous subscription purchase to be replaced
   * @param {string} subType - The type of subscription purchase (upgrade, downgrade, or normal purchase)
   */
  async purchase(purchaseSku, oldPurchase, subType) {
    if (!this.service || !this.skus) await this.init();

    const sku =
      typeof purchaseSku === 'string'
        ? this.skus.find((s) => s.itemId === purchaseSku)
        : purchaseSku;

    /*
    Set appropriate proration mode based on scenario.
    See https://developer.android.com/google/play/billing/subscriptions#proration-recommendations
    for more information about the different proration modes and recommendations.
    */
    let prorationMode = 'deferred';
    if (subType === 'upgrade') {
      prorationMode = 'immediateAndChargeProratedPrice';
    }
    // Build payment request
    const paymentMethod = [
      {
        supportedMethods: this.serviceURL,
        data: {
          sku: sku.itemId,
          oldSku: oldPurchase?.itemId,
          purchaseToken: oldPurchase?.purchaseToken,
          prorationMode: prorationMode,
        },
      },
    ];

    const payment = new PaymentRequest(paymentMethod);
    const response = await payment.show();
    const { token } = response.details;

    // Call backend to validate the purchase
    const valid = await this.validatePurchaseOnBackend(sku, token);
    if (valid) {
      // Tell PaymentRequest API validation was successful.
      // The user-agent may show a "payment successul" message to the user.
      await response.complete('success');
    } else {
      // Tell PaymentRequest API validation failed.
      // The user-agent may show a message to the user.
      await response.complete('fail');
    }

    return { response, valid };
  }

  /**
   *
   * @param {PlayBillingServiceSku} sku
   * @param {string} token
   */
  async validatePurchaseOnBackend(sku, token) {
    let request = '';

    if (sku.purchaseType === 'subscription') {
      request = '/api/validateSubPurchase';
    } else {
      request = '/api/validatePurchase';
    }

    try {
      const response = await (
        await fetch(request, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            sku: sku.itemId,
            token,
          }),
        })
      ).json();

      if (!('error' in response)) {
        return response.status;
      } else {
        return false;
      }
    } catch (error) {
      throw new Error(`Failed to verify purchase token: ${error.message}`);
    }
  }
}
<commit_msg>Fix the bug where proration mode was always set by default to DEFERRED even for a regular purchase<commit_after>/*
 *  Copyright 2021 Google LLC
 *
 *  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.
 */

/**
 * @typedef {Object} PlayBillingLookupItem
 * @property {string} itemId - ID of the item
 * @property {PurchaseType} purchaseType - Type of purchase
 */

/**
 * Play Billing Service
 *
 * Creates an instance of Play Billing Service that can be used to make purchases from Play from within a PWA installed from Play using the Digital Goods API
 */
export class PlayBillingService {
  /**
   * @param {PlayBillingLookupItem[]} lookups - Array of lookup items (itemId, purchaseType)
   */
  constructor(lookups) {
    this.lookups = Object.freeze(lookups);
    this.serviceURL = 'https://play.google.com/billing';
    this.init();
  }

  /**
   * Runs async initialization code
   * @private
   */
  async init() {
    // Get service
    if (!this.service) {
      this.service = Object.freeze(await window.getDigitalGoodsService(this.serviceURL));
    }

    // Get item details
    if (!this.skus) {
      await this.updateSkus();
    }
  }

  /**
   * Returns the purchase type for the provided purchase
   * @private
   * @param {PurchaseDetails} purchase - Purchase to check type for
   * @return {PurchaseType}
   */
  _getPurchaseType(purchase) {
    const skuMatch = this.skus.find((sku) => sku.itemId === purchase.itemId);
    const purchaseType = skuMatch ? skuMatch.purchaseType : 'onetime';
    return purchaseType;
  }

  /**
   * Updates service's SKUs with the latest info from Play.
   * Optionally can update the available SKUs for this service, too
   * @param {PlayBillingLookupItem[]} lookups - Array of lookup items (itemId, purchaseType)
   */
  async updateSkus(lookups) {
    if (lookups) {
      this.lookups = Object.freeze(lookups);
    }

    const details = await this.service.getDetails(this.lookups.map((sku) => sku.itemId));
    this.skus = Object.freeze(
      details.map((d) =>
        Object.assign(
          d,
          this.lookups.find((s) => s.itemId === d.itemId),
        ),
      ),
    );
  }

  /**
   * List existing entitlements that haven't been consumed yet or are on-going subscriptions
   * @return {PurchaseDetails[]} Also includes the purchase's purchase type
   */
  async getPurchases() {
    if (!this.skus) {
      await this.init();
    }
    let purchases = (await this.service.listPurchases()).map((p) =>
      Object.assign(p, { purchaseType: this._getPurchaseType(p) }),
    );

    // Acknowledge any un-acknowledged purchases.
    let acknowledged = false;
    for (const purchase of purchases) {
      if (purchase.purchaseState == 'purchased' && !purchase.acknowledged) {
        await this.acknowledge(purchase.purchaseToken, purchase);
        acknowledged = true;
      }
    }

    // Update purchases again after acknowledging un-acknowledged purchases
    if (acknowledged) {
      purchases = (await this.service.listPurchases()).map((p) =>
        Object.assign(p, { purchaseType: this._getPurchaseType(p) }),
      );
    }

    return Object.freeze(purchases);
  }

  /**
   * List available SKUs
   * @return {PlayBillingServiceSku[]}
   */
  async getSkus() {
    if (!this.skus) await this.init();

    return this.skus;
  }

  /**
   * Get the SKU's price in the provided currency
   * @param {PlayBillingServiceSku} sku - SKU to determine price for
   * @param {string} locale - Locale to convert price into
   * @return {string}
   */
  getSkuPrice(sku, locale) {
    return new Intl.NumberFormat(locale, {
      style: 'currency',
      currency: sku.price.currency,
    }).format(Number(sku.price.value));
  }

  /**
   * Apps should acknowledge the purchase after confirming that the purchase token
   * has been associated with a user. This app only acknowledges purchases after
   * successfully receiving the subscription data back from the server.
   *
   * Developers can choose to acknowledge purchases from a server using the
   * Google Play Developer API. The server has direct access to the user database,
   * so using the Google Play Developer API for acknowledgement might be more reliable.
   *
   * If the purchase token is not acknowledged within 3 days,
   * then Google Play will automatically refund and revoke the purchase.
   * This behavior helps ensure that users are not charged for subscriptions unless the
   * user has successfully received access to the content.
   * This eliminates a category of issues where users complain to developers
   * that they paid for something that the app is not giving to them.
   * @param {string} token - Purchase token
   * @param {PurchaseDetailsWithType|PlayBillingServiceSku} item - SKU or purchase to acknowledge
   */
  async acknowledge(token, item) {
    if (!this.service) {
      await this.init();
    }

    // Subscriptions need to be acknowledges as 'onetime'
    const type = item.purchaseType === 'subscription' ? 'onetime' : item.purchaseType;

    return await this.service.acknowledge(token, type);
  }

  /**
   * Forces a purchase token to be consumed.
   * Purchases are coerced into a 'repeatable' type as that's the only type that will trigger consumption in Play
   * @param {string} token - Purchase token
   */
  async consume(token) {
    return await this.acknowledge(token, { purchaseType: 'repeatable' });
  }

  /**
   * Request that the provided SKU is purchased. Will acknowledge purchase on success
   * @param {PlayBillingServiceSku|string} purchaseSku - Either the SKU ID to purchase, or the actual SKU
   * @param {PurchaseDetails} oldPurchase - The previous subscription purchase to be replaced
   * @param {string} subType - The type of subscription purchase (upgrade, downgrade, or normal purchase)
   */
  async purchase(purchaseSku, oldPurchase, subType) {
    if (!this.service || !this.skus) await this.init();

    const sku =
      typeof purchaseSku === 'string'
        ? this.skus.find((s) => s.itemId === purchaseSku)
        : purchaseSku;

    /*
    Set appropriate proration mode based on scenario.
    See https://developer.android.com/google/play/billing/subscriptions#proration-recommendations
    for more information about the different proration modes and recommendations.
    */
    let prorationMode = null;
    switch (subType) {
      case 'upgrade':
        prorationMode = 'immediateAndChargeProratedPrice';
        break;
      case 'downgrade':
        prorationMode = 'deferred';
        break;
    }

    // Build payment request
    const paymentMethod = [
      {
        supportedMethods: this.serviceURL,
        data: {
          sku: sku.itemId,
          oldSku: oldPurchase?.itemId,
          purchaseToken: oldPurchase?.purchaseToken,
          prorationMode: prorationMode,
        },
      },
    ];

    const payment = new PaymentRequest(paymentMethod);
    const response = await payment.show();
    const { token } = response.details;

    // Call backend to validate the purchase
    const valid = await this.validatePurchaseOnBackend(sku, token);
    if (valid) {
      // Tell PaymentRequest API validation was successful.
      // The user-agent may show a "payment successul" message to the user.
      await response.complete('success');
    } else {
      // Tell PaymentRequest API validation failed.
      // The user-agent may show a message to the user.
      await response.complete('fail');
    }

    return { response, valid };
  }

  /**
   *
   * @param {PlayBillingServiceSku} sku
   * @param {string} token
   */
  async validatePurchaseOnBackend(sku, token) {
    let request = '';

    if (sku.purchaseType === 'subscription') {
      request = '/api/validateSubPurchase';
    } else {
      request = '/api/validatePurchase';
    }

    try {
      const response = await (
        await fetch(request, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            sku: sku.itemId,
            token,
          }),
        })
      ).json();

      if (!('error' in response)) {
        return response.status;
      } else {
        return false;
      }
    } catch (error) {
      throw new Error(`Failed to verify purchase token: ${error.message}`);
    }
  }
}
<|endoftext|>


<commit_before>/* Licensed under the MIT license:
 * http://www.opensource.org/licenses/mit-license.php
 * Copyright (c) 2010 Mr.doob, rhyolight, bebraw
 */
// import brush modifiers
modifiers = ["mirror"]; // "jitter"
importDirectory(modifiers, "modifiers");

function Modifiers() {
    this.init();
}
Modifiers.prototype = {
    init: function () {
        setUpPod("Modifiers");

        $('#modifiersPod').click(function() {
            $(this).hide();
            $('#modifiersCheckbox').attr('checked', false);
            $('#modifiersPanel').dialog('open');
        });

        // <input type="checkbox" id="check" />Toggle

        // set up modifiers panel
        $("body").append('
 \
            <input type="checkbox" id="horizontalMirrorModifier" />Horizontal Mirror \
            <input type="checkbox" id="verticalMirrorModifier" />Vertical Mirror \
            <input type="checkbox" id="radialMirrorModifier" />Radial Mirror \
            <input type="checkbox" id="jitterModifier" />Jitter \

');

        $("#modifiersPanel input").button();
        $("#modifiersPanel label").css("width", "100%").css("margin-bottom", "0.5em");

        $("#modifiersPanel").dialog({
           closeOnEscape: false, resizable: false, width: 230, autoOpen: false
        });

        $("#modifiersPanel").dialog( "option", "position", ["right", "top"] );
        $("#modifiersPanel").bind( "dialogclose", function(event, ui) { $("#modifiersPod").show();} );
    },
    destroy: function () {}
}
<commit_msg>Resized modifiers panel a bit.<commit_after>/* Licensed under the MIT license:
 * http://www.opensource.org/licenses/mit-license.php
 * Copyright (c) 2010 Mr.doob, rhyolight, bebraw
 */
// import brush modifiers
modifiers = ["mirror"]; // "jitter"
importDirectory(modifiers, "modifiers");

function Modifiers() {
    this.init();
}
Modifiers.prototype = {
    init: function () {
        setUpPod("Modifiers");

        $('#modifiersPod').click(function() {
            $(this).hide();
            $('#modifiersCheckbox').attr('checked', false);
            $('#modifiersPanel').dialog('open');
        });

        // <input type="checkbox" id="check" />Toggle

        // set up modifiers panel
        $("body").append('
 \
            <input type="checkbox" id="horizontalMirrorModifier" />Horizontal Mirror \
            <input type="checkbox" id="verticalMirrorModifier" />Vertical Mirror \
            <input type="checkbox" id="radialMirrorModifier" />Radial Mirror \
            <input type="checkbox" id="jitterModifier" />Jitter \

');

        $("#modifiersPanel input").button();
        $("#modifiersPanel label").css("width", "100%").css("margin-bottom", "0.5em");

        $("#modifiersPanel").dialog({
           closeOnEscape: false, resizable: false, width: 150, autoOpen: false
        });

        $("#modifiersPanel").dialog( "option", "position", ["right", "top"] );
        $("#modifiersPanel").bind( "dialogclose", function(event, ui) { $("#modifiersPod").show();} );
    },
    destroy: function () {}
}
<|endoftext|>


<commit_before>/* @flow */

import clone from 'lodash.clone'
import isEqual from 'lodash.isequal'
import pick from 'lodash.pick'
import path from 'path'

import logger from './logger'
import { sameDate } from './timestamp'

const log = logger({
  prefix: 'Metadata',
  date: true
})

export type MetadataRemoteInfo = {
  _id: string,
  _rev: string
}

export type MetadataSidesInfo = {
  remote?: ?number,
  local?: ?number
}

// The files/dirs metadata, as stored in PouchDB
export type Metadata = {
  _id: string,
  _rev: string,
  // TODO: v3: Rename to md5sum to match remote
  checksum?: string,
  class?: string,
  creationDate: string,
  // TODO: v3: Use the same local *type fields as the remote ones
  docType: string,
  executable?: boolean,
  lastModification: string,
  mime?: string,
  path: string,
  remote: MetadataRemoteInfo,
  size?: string,
  tags: string[],
  sides: MetadataSidesInfo,
  toBeTrashed?: true
}

export let buildId: (doc: Metadata) => void = (_) => {}

switch (process.platform) {
  case 'linux': case 'freebsd': case 'sunos':
    buildId = buildIdUnix
    break
  case 'darwin':
    buildId = buildIdHFS
    break
  case 'win32':
    // FIXME: Temporary hack to run the CLI on Windows
    // TODO: Proper implementation of buildId on Windows
    buildId = buildIdUnix
    break
  default:
    log.error(`Sorry, ${process.platform} is not supported!`)
    process.exit(1)
}

// Build an _id from the path for a case sensitive file system (Linux, BSD)
function buildIdUnix (doc: Metadata) {
  doc._id = doc.path
}

// Build an _id from the path for OSX (HFS+ file system):
// - case preservative, but not case sensitive
// - unicode NFD normalization (sort of)
//
// See https://nodejs.org/en/docs/guides/working-with-different-filesystems/
// for why toUpperCase is better than toLowerCase
//
// Note: String.prototype.normalize is not available on node 0.10 and does
// nothing when node is compiled without intl option.
function buildIdHFS (doc: Metadata) {
  let id = doc.path
  if (id.normalize) { id = id.normalize('NFD') }
  doc._id = id.toUpperCase()
}

// Return true if the document has not a valid path
// (ie a path inside the mount point)
export function invalidPath (doc: Metadata) {
  if (!doc.path) { return true }
  doc.path = path.normalize(doc.path)
  doc.path = doc.path.replace(/^\//, '')
  let parts = doc.path.split(path.sep)
  return (doc.path === '.') ||
          (doc.path === '') ||
          (parts.indexOf('..') >= 0)
}

export function ensureValidPath (doc: Metadata) {
  if (invalidPath(doc)) {
    log.warn(`Invalid path: ${JSON.stringify(doc, null, 2)}`)
    throw new Error('Invalid path')
  }
}

// Return true if the checksum is invalid
// If the checksum is missing, it is not invalid, just missing,
// so it returns false.
// MD5 has 16 bytes.
// Base64 encoding must include padding.
export function invalidChecksum (doc: Metadata) {
  if (doc.checksum == null) return false

  const buffer = Buffer.from(doc.checksum, 'base64')

  return buffer.byteLength !== 16 ||
    buffer.toString('base64') !== doc.checksum
}

export function ensureValidChecksum (doc: Metadata) {
  if (invalidChecksum(doc)) {
    log.warn(`Invalid checksum: ${JSON.stringify(doc, null, 2)}`)
    throw new Error('Invalid checksum')
  }
}

// Extract the revision number, or 0 it not found
export function extractRevNumber (infos: Metadata) {
  try {
    let rev = infos._rev.split('-')[0]
    return Number(rev)
  } catch (error) {
    return 0
  }
}

// Return true if the remote file is up-to-date for this document
export function isUpToDate (doc: Metadata) {
  let currentRev = doc.sides.remote || 0
  let lastRev = extractRevNumber(doc)
  return currentRev === lastRev
}

// Return true if the metadata of the two folders are the same
// The creationDate of the two folders are not compared, because the local
// filesystem can't give us a relevant information for that.
// For lastModification, we accept up to 3s of differences because we can't
// rely on file systems to be precise to the millisecond.
export function sameFolder (one: Metadata, two: Metadata) {
  if (!sameDate(one.lastModification, two.lastModification)) { return false }
  let fields = ['_id', 'docType', 'remote', 'tags']
  one = pick(one, fields)
  two = pick(two, fields)
  const same = isEqual(one, two)
  if (!same) log.diff(one, two)
  return same
}

// Return true if the metadata of the two files are the same
// The creationDate of the two files are not compared, because the local
// filesystem can't give us a relevant information for that.
// For lastModification, we accept up to 3s of differences because we can't
// rely on file systems to be precise to the millisecond.
export function sameFile (one: Metadata, two: Metadata) {
  if (!sameDate(one.lastModification, two.lastModification)) { return false }
  let fields = ['_id', 'docType', 'checksum', 'remote',
    'tags', 'size', 'class', 'mime']
  one = {...pick(one, fields), executable: !!one.executable}
  two = {...pick(two, fields), executable: !!two.executable}
  const same = isEqual(one, two)
  if (!same) log.diff(one, two)
  return same
}

// Return true if the two files have the same binary content
export function sameBinary (one: Metadata, two: Metadata) {
  if ((one.docType !== 'file') || (two.docType !== 'file')) {
    return false
  } else if ((one.checksum != null) && (one.checksum === two.checksum)) {
    return true
  } else if ((one.remote != null) && (two.remote != null)) {
    let oneId = one.remote._id
    let twoId = two.remote._id
    return (oneId != null) && (oneId === twoId)
  } else {
    return false
  }
}

// Mark the next rev for this side
//
// To track which side has made which modification, a revision number is
// associated to each side. When a side make a modification, we extract the
// revision from the previous state, increment it by one to have the next
// revision and associate this number to the side that makes the
// modification.
export function markSide (side: string, doc: Metadata, prev: ?Metadata): Metadata {
  let rev = 0
  if (prev) { rev = extractRevNumber(prev) }
  if (doc.sides == null) {
    const was = prev && prev.sides
    doc.sides = clone(was || {})
  }
  doc.sides[side] = ++rev
  return doc
}
<commit_msg>metadata: Introduce inRemoteTrash()<commit_after>/* @flow */

import clone from 'lodash.clone'
import isEqual from 'lodash.isequal'
import pick from 'lodash.pick'
import path from 'path'

import logger from './logger'
import { TRASH_DIR_NAME } from './remote/constants'
import { sameDate } from './timestamp'

const log = logger({
  prefix: 'Metadata',
  date: true
})

export type MetadataRemoteInfo = {
  _id: string,
  _rev: string
}

export type MetadataSidesInfo = {
  remote?: ?number,
  local?: ?number
}

// The files/dirs metadata, as stored in PouchDB
export type Metadata = {
  _id: string,
  _rev: string,
  // TODO: v3: Rename to md5sum to match remote
  checksum?: string,
  class?: string,
  creationDate: string,
  // TODO: v3: Use the same local *type fields as the remote ones
  docType: string,
  executable?: boolean,
  lastModification: string,
  mime?: string,
  path: string,
  remote: MetadataRemoteInfo,
  size?: string,
  tags: string[],
  sides: MetadataSidesInfo,
  toBeTrashed?: true
}

export let buildId: (doc: Metadata) => void = (_) => {}

switch (process.platform) {
  case 'linux': case 'freebsd': case 'sunos':
    buildId = buildIdUnix
    break
  case 'darwin':
    buildId = buildIdHFS
    break
  case 'win32':
    // FIXME: Temporary hack to run the CLI on Windows
    // TODO: Proper implementation of buildId on Windows
    buildId = buildIdUnix
    break
  default:
    log.error(`Sorry, ${process.platform} is not supported!`)
    process.exit(1)
}

// Build an _id from the path for a case sensitive file system (Linux, BSD)
function buildIdUnix (doc: Metadata) {
  doc._id = doc.path
}

// Build an _id from the path for OSX (HFS+ file system):
// - case preservative, but not case sensitive
// - unicode NFD normalization (sort of)
//
// See https://nodejs.org/en/docs/guides/working-with-different-filesystems/
// for why toUpperCase is better than toLowerCase
//
// Note: String.prototype.normalize is not available on node 0.10 and does
// nothing when node is compiled without intl option.
function buildIdHFS (doc: Metadata) {
  let id = doc.path
  if (id.normalize) { id = id.normalize('NFD') }
  doc._id = id.toUpperCase()
}

// Return true if the document has not a valid path
// (ie a path inside the mount point)
export function invalidPath (doc: Metadata) {
  if (!doc.path) { return true }
  doc.path = path.normalize(doc.path)
  doc.path = doc.path.replace(/^\//, '')
  let parts = doc.path.split(path.sep)
  return (doc.path === '.') ||
          (doc.path === '') ||
          (parts.indexOf('..') >= 0)
}

export function ensureValidPath (doc: Metadata) {
  if (invalidPath(doc)) {
    log.warn(`Invalid path: ${JSON.stringify(doc, null, 2)}`)
    throw new Error('Invalid path')
  }
}

// Return true if the checksum is invalid
// If the checksum is missing, it is not invalid, just missing,
// so it returns false.
// MD5 has 16 bytes.
// Base64 encoding must include padding.
export function invalidChecksum (doc: Metadata) {
  if (doc.checksum == null) return false

  const buffer = Buffer.from(doc.checksum, 'base64')

  return buffer.byteLength !== 16 ||
    buffer.toString('base64') !== doc.checksum
}

export function ensureValidChecksum (doc: Metadata) {
  if (invalidChecksum(doc)) {
    log.warn(`Invalid checksum: ${JSON.stringify(doc, null, 2)}`)
    throw new Error('Invalid checksum')
  }
}

// Extract the revision number, or 0 it not found
export function extractRevNumber (infos: Metadata) {
  try {
    let rev = infos._rev.split('-')[0]
    return Number(rev)
  } catch (error) {
    return 0
  }
}

// Return true if the remote file is up-to-date for this document
export function isUpToDate (doc: Metadata) {
  let currentRev = doc.sides.remote || 0
  let lastRev = extractRevNumber(doc)
  return currentRev === lastRev
}

// Return true if the metadata of the two folders are the same
// The creationDate of the two folders are not compared, because the local
// filesystem can't give us a relevant information for that.
// For lastModification, we accept up to 3s of differences because we can't
// rely on file systems to be precise to the millisecond.
export function sameFolder (one: Metadata, two: Metadata) {
  if (!sameDate(one.lastModification, two.lastModification)) { return false }
  let fields = ['_id', 'docType', 'remote', 'tags']
  one = pick(one, fields)
  two = pick(two, fields)
  const same = isEqual(one, two)
  if (!same) log.diff(one, two)
  return same
}

// Return true if the metadata of the two files are the same
// The creationDate of the two files are not compared, because the local
// filesystem can't give us a relevant information for that.
// For lastModification, we accept up to 3s of differences because we can't
// rely on file systems to be precise to the millisecond.
export function sameFile (one: Metadata, two: Metadata) {
  if (!sameDate(one.lastModification, two.lastModification)) { return false }
  let fields = ['_id', 'docType', 'checksum', 'remote',
    'tags', 'size', 'class', 'mime']
  one = {...pick(one, fields), executable: !!one.executable}
  two = {...pick(two, fields), executable: !!two.executable}
  const same = isEqual(one, two)
  if (!same) log.diff(one, two)
  return same
}

// Return true if the two files have the same binary content
export function sameBinary (one: Metadata, two: Metadata) {
  if ((one.docType !== 'file') || (two.docType !== 'file')) {
    return false
  } else if ((one.checksum != null) && (one.checksum === two.checksum)) {
    return true
  } else if ((one.remote != null) && (two.remote != null)) {
    let oneId = one.remote._id
    let twoId = two.remote._id
    return (oneId != null) && (oneId === twoId)
  } else {
    return false
  }
}

// Mark the next rev for this side
//
// To track which side has made which modification, a revision number is
// associated to each side. When a side make a modification, we extract the
// revision from the previous state, increment it by one to have the next
// revision and associate this number to the side that makes the
// modification.
export function markSide (side: string, doc: Metadata, prev: ?Metadata): Metadata {
  let rev = 0
  if (prev) { rev = extractRevNumber(prev) }
  if (doc.sides == null) {
    const was = prev && prev.sides
    doc.sides = clone(was || {})
  }
  doc.sides[side] = ++rev
  return doc
}

export function inRemoteTrash (doc: Metadata): boolean {
  return doc.path.startsWith(TRASH_DIR_NAME)
}
<|endoftext|>


<commit_before>(function() {
  angular.module('githubgame')

    .controller('TokenController', ['$scope', '$rootScope', '$cookies', '$http', function ($scope, $rootScope, $cookies, $http) {
      $cookies.tokenValid = undefined;
      $scope.token = $cookies.token;

      $scope.saveToken = function (token) {
        $scope.editingToken = false;

        if (token != $cookies.token)
          $http({method: 'GET', url: 'https://api.github.com/user', headers: { 'Authorization': 'token ' + token}})
            .success(function (data, status, headers, config) {
              $rootScope.addAlert('Authorized as: ' + data.login + '', 'success');
              $rootScope.token = token;
              $cookies.token = token;
            })
            .error(function (data, status, headers, config) {
              $rootScope.addAlert('Could not authorize token: ' + token + '');
              $rootScope.token = '';
              $cookies.token = '';
            });
      };
    }]);

})();<commit_msg>Removed clearing of unused cookie<commit_after>(function() {
  angular.module('githubgame')

    .controller('TokenController', ['$scope', '$rootScope', '$cookies', '$http', function ($scope, $rootScope, $cookies, $http) {
      $scope.token = $cookies.token;

      $scope.saveToken = function (token) {
        $scope.editingToken = false;

        if (token != $cookies.token)
          $http({method: 'GET', url: 'https://api.github.com/user', headers: { 'Authorization': 'token ' + token}})
            .success(function (data, status, headers, config) {
              $rootScope.addAlert('Authorized as: ' + data.login + '', 'success');
              $rootScope.token = token;
              $cookies.token = token;
            })
            .error(function (data, status, headers, config) {
              $rootScope.addAlert('Could not authorize token: ' + token + '');
              $rootScope.token = '';
              $cookies.token = '';
            });
      };
    }]);

})();<|endoftext|>


<commit_before>
import { ClusterMarkers } from './ClusterMarkers';
import { STATION_ENTRANCE, FEATURE_ENTRANCES } from '../core/constants';
import { Points, PointsMaterial, BufferGeometry, Float32BufferAttribute, TextureLoader } from '../Three';
import { Cfg } from '../core/lib';

function Entrances ( survey ) {

	ClusterMarkers.call( this, survey.modelLimits, 4 );

	const self = this;
	const surveyTree = survey.surveyTree;
	const entrances = survey.metadata.entrances;
	const vertices = [];
	const stations = [];

	const geometry = new BufferGeometry();
	const material = new PointsMaterial();

	material.map = new TextureLoader().load( Cfg.value( 'home', '' ) + 'images/disc.png' );
	material.opacity = 1.0;
	material.alphaTest = 0.8;
	material.sizeAttenuation = false;
	material.transparent = true;
	material.sizeAttenuation = false;
	material.size = 10;

	const markers = new Points( geometry, material );

	markers.layers.set( FEATURE_ENTRANCES );

	// remove common elements from station names if no alternatives available

	var endNode = surveyTree;

	while ( endNode.children.length === 1 ) endNode = endNode.children [ 0 ];

	// find entrances and add Markers

	surveyTree.traverse( _addEntrance );

	let l = vertices.length;

	if ( l > 0 ) {

		const positions = new Float32BufferAttribute( l * 3, 3 );

		positions.copyVector3sArray( vertices );
		geometry.addAttribute( 'position', positions );

	} else {

		this.visible = false;

	}

	this.markers = markers;
	this.stations = stations;
	this.metadata = survey.metadata;

	this.addStatic( markers );

	return this;

	function _addEntrance( node ) {

		var name;

		if ( node.type !== STATION_ENTRANCE ) return;

		const entranceInfo = entrances[ node.getPath() ];

		if ( entranceInfo !== undefined && entranceInfo.name !== undefined ) {

			name = entranceInfo.name;

		} else if ( node.comment !== undefined ) {

			name = node.comment;

		} else {

			name = node.getPath( endNode );

		}

		vertices.push( node.p );
		stations.push( node );

		if ( name === '-skip' ) return;

		self.addMarker( node, ' ' + name + ' ' );

	}

}

Entrances.prototype = Object.create( ClusterMarkers.prototype );

Entrances.prototype.getStation = function ( index ) {

	const station = this.stations[ index ];
	const stationName = station.getPath();

	return {
		station: station,
		name: stationName,
		info: this.metadata.entrances[ stationName ]
	};

};

Entrances.prototype.setStation = function ( station, info ) {

	const metadata = this.metadata;

	metadata.entrances[ station.getPath() ] = info;

	metadata.saveLocal();

};

Entrances.prototype.intersectLabels = function ( mouse, camera, scale ) {

	return this.labels.filter( _filter ).sort( _sort );

	function _filter ( label ) {

		return label.intersects( mouse, camera, scale );

	}

	function _sort ( a, b ) {

		return a.depth - b.depth;

	}

};

export { Entrances };
<commit_msg>return top label only or null<commit_after>
import { ClusterMarkers } from './ClusterMarkers';
import { STATION_ENTRANCE, FEATURE_ENTRANCES } from '../core/constants';
import { Points, PointsMaterial, BufferGeometry, Float32BufferAttribute, TextureLoader } from '../Three';
import { Cfg } from '../core/lib';

function Entrances ( survey ) {

	ClusterMarkers.call( this, survey.modelLimits, 4 );

	const self = this;
	const surveyTree = survey.surveyTree;
	const entrances = survey.metadata.entrances;
	const vertices = [];
	const stations = [];

	const geometry = new BufferGeometry();
	const material = new PointsMaterial();

	material.map = new TextureLoader().load( Cfg.value( 'home', '' ) + 'images/disc.png' );
	material.opacity = 1.0;
	material.alphaTest = 0.8;
	material.sizeAttenuation = false;
	material.transparent = true;
	material.sizeAttenuation = false;
	material.size = 10;

	const markers = new Points( geometry, material );

	markers.layers.set( FEATURE_ENTRANCES );

	// remove common elements from station names if no alternatives available

	var endNode = surveyTree;

	while ( endNode.children.length === 1 ) endNode = endNode.children [ 0 ];

	// find entrances and add Markers

	surveyTree.traverse( _addEntrance );

	let l = vertices.length;

	if ( l > 0 ) {

		const positions = new Float32BufferAttribute( l * 3, 3 );

		positions.copyVector3sArray( vertices );
		geometry.addAttribute( 'position', positions );

	} else {

		this.visible = false;

	}

	this.markers = markers;
	this.stations = stations;
	this.metadata = survey.metadata;

	this.addStatic( markers );

	return this;

	function _addEntrance( node ) {

		var name;

		if ( node.type !== STATION_ENTRANCE ) return;

		const entranceInfo = entrances[ node.getPath() ];

		if ( entranceInfo !== undefined && entranceInfo.name !== undefined ) {

			name = entranceInfo.name;

		} else if ( node.comment !== undefined ) {

			name = node.comment;

		} else {

			name = node.getPath( endNode );

		}

		vertices.push( node.p );
		stations.push( node );

		if ( name === '-skip' ) return;

		self.addMarker( node, ' ' + name + ' ' );

	}

}

Entrances.prototype = Object.create( ClusterMarkers.prototype );

Entrances.prototype.getStation = function ( index ) {

	const station = this.stations[ index ];
	const stationName = station.getPath();

	return {
		station: station,
		name: stationName,
		info: this.metadata.entrances[ stationName ]
	};

};

Entrances.prototype.setStation = function ( station, info ) {

	const metadata = this.metadata;

	metadata.entrances[ station.getPath() ] = info;

	metadata.saveLocal();

};

Entrances.prototype.intersectLabels = function ( mouse, camera, scale ) {

	var labels = this.labels.filter( _filter ).sort( _sort );

	return ( labels.length === 0 ) ? null : labels[ 0 ];

	function _filter ( label ) {

		return label.intersects( mouse, camera, scale );

	}

	function _sort ( a, b ) {

		return a.depth - b.depth;

	}

};

export { Entrances };
<|endoftext|>


<commit_before>var View = require('ampersand-view');
var Utils = require('../utils');
var throttle = require('lodash.throttle');

var WorkoutBar = module.exports = View.extend({

  initialize: function (options) {
    if (this.parent) { this.model = this.parent.model; }
    this.updateTimeOnClick = !!options.updateTimeOnClick;

    this.listenTo(this.model, 'change:elapsedSkip', this.drawBar);
    this.listenTo(this.model, 'change:currentEvent', this.drawBar);

    this.resizeHandler = throttle(this.drawBar.bind(this), 66);
    window.addEventListener('resize', this.resizeHandler, false);
    window.addEventListener('orientationchange', this.resizeHandler, false);
  },

  remove: function () {
    window.removeEventListener('resize', this.resizeHandler);
    window.removeEventListener('orientationchange', this.resizeHandler);
    View.prototype.remove.call(this);
  },

  render: function () {
    if (!this.el) {
      this.el = document.createElement('canvas');
    }
    this.drawBar();
  },

  drawBar: function () {
    var self = this;

    var parentNode = this.el.parentNode;
    if (!parentNode) { return; }

    // HACK: Defer drawing until there's a parent node size available
    // This seems like a really terrible way to do it.
    if (!(parentNode.offsetWidth && parentNode.offsetHeight)) {
      setTimeout(this.drawBar.bind(this), 10);
      return this;
    }

    this.el.width = parentNode.offsetWidth;
    this.el.height = parentNode.offsetHeight;

    var ctx = this.el.getContext('2d');

    var widthByDuration = this.widthByDuration = this.el.width / this.model.duration;

    // TODO: Make this configurable
    var colors = {
      'warmup': '#2F4F4F',
      'walk': '#006400',
      'run': '#228B22',
      'cooldown': '#2F4F4F'
    };

    var height = this.el.height;

    var currentEvent = this.model.currentEvent;
    if (currentEvent) {
      ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
      this.drawEventBar(ctx, widthByDuration, currentEvent, height);
    }

    this.model.events.forEach(function (event) {
      ctx.fillStyle = colors[event.type];
      self.drawEventBar(ctx, widthByDuration, event, height * 0.66);
    });

    var elapsedPos = this.model.elapsed * widthByDuration;
    ctx.fillStyle = "#fff";
    ctx.fillRect(elapsedPos, 0, 1, height);

  },

  drawEventBar: function (ctx, widthByDuration, event, barHeight) {
    var startX = event.startElapsed * widthByDuration;
    var width = event.endElapsed * widthByDuration - startX;
    var startY = (this.el.height - barHeight) / 2;
    ctx.fillRect(startX, startY, width, barHeight);
  },

  events: {
    'click': 'click',
    'mousedown': 'mousedown',
    'mouseup': 'mouseup',
    'mousemove': 'mousemove',
    'touchstart': 'touchstart',
    'touchend': 'touchend',
    'touchmove': 'touchmove'
  },

  updateTimeFromUI: function (x) {
    if (!this.updateTimeOnClick) { return; }
    if (!this.widthByDuration) { return; }
    var barRect = this.el.getBoundingClientRect();
    this.model.elapsed = (x - barRect.left) / this.widthByDuration;
  },

  click: function (ev) {
    return this.updateTimeFromUI(ev.clientX);
  },

  mousedown: function (ev) {
    this.isDragging = true;
  },

  mouseup: function (ev) {
    this.isDragging = false;
  },

  mousemove: function (ev) {
    if (this.isDragging) {
      return this.updateTimeFromUI(ev.clientX);
    }
  },

  touchstart: function (ev) {
    this.isTouching = true;
  },

  touchend: function (ev) {
    this.isTouching = false;
  },

  touchmove: function (ev) {
    if (this.isTouching) {
      return this.updateTimeFromUI(ev.touches[0].clientX);
    }
  }

});
<commit_msg>Throttle bar drawing a bit more<commit_after>var View = require('ampersand-view');
var Utils = require('../utils');
var throttle = require('lodash.throttle');

var WorkoutBar = module.exports = View.extend({

  initialize: function (options) {
    if (this.parent) { this.model = this.parent.model; }
    this.updateTimeOnClick = !!options.updateTimeOnClick;

    this.throttledDrawBar = throttle(this.drawBar.bind(this), 16);

    this.listenTo(this.model, 'change:elapsedSkip', this.throttledDrawBar);
    this.listenTo(this.model, 'change:currentEvent', this.throttledDrawBar);

    window.addEventListener('resize', this.throttledDrawBar, false);
    window.addEventListener('orientationchange', this.throttledDrawBar, false);
  },

  remove: function () {
    window.removeEventListener('resize', this.resizeHandler);
    window.removeEventListener('orientationchange', this.resizeHandler);
    View.prototype.remove.call(this);
  },

  render: function () {
    if (!this.el) {
      this.el = document.createElement('canvas');
    }
    this.drawBar();
  },

  drawBar: function () {
    var self = this;

    var parentNode = this.el.parentNode;
    if (!parentNode) { return; }

    // HACK: Defer drawing until there's a parent node size available
    // This seems like a really terrible way to do it.
    if (!(parentNode.offsetWidth && parentNode.offsetHeight)) {
      setTimeout(this.drawBar.bind(this), 10);
      return this;
    }

    this.el.width = parentNode.offsetWidth;
    this.el.height = parentNode.offsetHeight;

    var ctx = this.el.getContext('2d');

    var widthByDuration = this.widthByDuration = this.el.width / this.model.duration;

    // TODO: Make this configurable
    var colors = {
      'warmup': '#2F4F4F',
      'walk': '#006400',
      'run': '#228B22',
      'cooldown': '#2F4F4F'
    };

    var height = this.el.height;

    var currentEvent = this.model.currentEvent;
    if (currentEvent) {
      ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
      this.drawEventBar(ctx, widthByDuration, currentEvent, height);
    }

    this.model.events.forEach(function (event) {
      ctx.fillStyle = colors[event.type];
      self.drawEventBar(ctx, widthByDuration, event, height * 0.66);
    });

    var elapsedPos = this.model.elapsed * widthByDuration;
    ctx.fillStyle = "#fff";
    ctx.fillRect(elapsedPos, 0, 1, height);

  },

  drawEventBar: function (ctx, widthByDuration, event, barHeight) {
    var startX = event.startElapsed * widthByDuration;
    var width = event.endElapsed * widthByDuration - startX;
    var startY = (this.el.height - barHeight) / 2;
    ctx.fillRect(startX, startY, width, barHeight);
  },

  events: {
    'click': 'click',
    'mousedown': 'mousedown',
    'mouseup': 'mouseup',
    'mousemove': 'mousemove',
    'touchstart': 'touchstart',
    'touchend': 'touchend',
    'touchmove': 'touchmove'
  },

  updateTimeFromUI: function (x) {
    if (!this.updateTimeOnClick) { return; }
    if (!this.widthByDuration) { return; }
    var barRect = this.el.getBoundingClientRect();
    this.model.elapsed = (x - barRect.left) / this.widthByDuration;
  },

  click: function (ev) {
    return this.updateTimeFromUI(ev.clientX);
  },

  mousedown: function (ev) {
    this.isDragging = true;
  },

  mouseup: function (ev) {
    this.isDragging = false;
  },

  mousemove: function (ev) {
    if (this.isDragging) {
      return this.updateTimeFromUI(ev.clientX);
    }
  },

  touchstart: function (ev) {
    this.isTouching = true;
  },

  touchend: function (ev) {
    this.isTouching = false;
  },

  touchmove: function (ev) {
    if (this.isTouching) {
      return this.updateTimeFromUI(ev.touches[0].clientX);
    }
  }

});
<|endoftext|>


<commit_before>(function(L) {
  'use strict';

  L.views.AddnotePage = Backbone.View.extend({
    className: 'note-creator',
    id: 'addnote',
    events: {
      'click .save-icon': '_onSaveClicked',
      'click .close-icon': '_onCloseClicked',
      'click .pin-icon': '_onPinClicked',
      'keydown .editor': '_onKeyDown',
    },
    _onKeyDown: function(event) {
      var keyCode;
      event = event || window.event;
      keyCode = event.keyCode || event.which;
      switch(keyCode) {
        case KeyCode.ENTER:
          if (event.shiftKey) {
            this._onSaveClicked();
            event.preventDefault(); // Let model update
          }
          break;
        case KeyCode.ESC:
          this._onCloseClicked();
          break;
      }
    },
    _onSaveClicked: function() {
      this.saveNote();
    },
    _onCloseClicked: function() {
      this.model.destroy();
    },
    _onPinClicked: function() {
      var meta = this.model.get('meta', {});
      meta.pinned = true;
      this.model.set('meta', meta);
      this.saveNot();
    },
    saveNote: function() {
      this.model.set({contents: L.util.strip(this.editor.getText())});
      this.model.save();
    },
    remove: function() {
      if (this._rendered) {
        this.editor.remove();
        return Backbone.View.prototype.remove.call(this);
      }
    },
    render: function(options) {
      if (!this._rendered) {
        this.editor = new L.views.Editor({
          text: this.model.get('contents'),
          autoResize: false,
          actions: L.templates['create-actions']()
        });
        this.$el.append('

List.it: Add Note

');
        this.$el.append(this.editor.render().el);
      }
      this._rendered = true;
      return this;
    }
  });

})(ListIt);
<commit_msg>Confirm close of unsaved note<commit_after>(function(L) {
  'use strict';

  L.views.AddnotePage = Backbone.View.extend({
    className: 'note-creator',
    id: 'addnote',
    events: {
      'click .save-icon': '_onSaveClicked',
      'click .close-icon': '_onCloseClicked',
      'click .pin-icon': '_onPinClicked',
      'keydown .editor': '_onKeyDown',
    },
    initialize: function() {
      var that = this;
      window.onbeforeunload = function() {
        if (that._rendered) {
          return "You have an unsaved list.it note open.";
        }
      };

    },
    _onKeyDown: function(event) {
      var keyCode;
      event = event || window.event;
      keyCode = event.keyCode || event.which;
      switch(keyCode) {
        case KeyCode.ENTER:
          if (event.shiftKey) {
            this._onSaveClicked();
            event.preventDefault(); // Let model update
          }
          break;
        case KeyCode.ESC:
          this._onCloseClicked();
          break;
      }
    },
    _onSaveClicked: function() {
      this.saveNote();
    },
    _onCloseClicked: function() {
      this.model.destroy();
    },
    _onPinClicked: function() {
      var meta = this.model.get('meta', {});
      meta.pinned = true;
      this.model.set('meta', meta);
      this.saveNot();
    },
    saveNote: function() {
      this.model.set({contents: L.util.strip(this.editor.getText())});
      this.model.save();
    },
    remove: function() {
      if (this._rendered) {
        this.editor.remove();
        Backbone.View.prototype.remove.call(this);
        this._rendered = false;
      }
      return this;
    },
    render: function(options) {
      if (!this._rendered) {
        this.editor = new L.views.Editor({
          text: this.model.get('contents'),
          autoResize: false,
          actions: L.templates['create-actions']()
        });
        this.$el.append('

List.it: Add Note

');
        this.$el.append(this.editor.render().el);
      }
      this._rendered = true;
      return this;
    }
  });

})(ListIt);
<|endoftext|>


<commit_before>/* Copyright 2012-2014, Marc S. Brooks (http://mbrooks.info) */
eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(6($){3 j={"A":6(a,b){$.11({B:0,C:10,D:1},a);E($(8),a,b)},"12":6(){$(8).13()}};$.14.F=6(a){4(j[a]){n j[a].G(8,15.16.17.18(H,1))}r 4(19 a===\'1a\'||!a){n j.A.G(8,H)}r{$.1b(\'1c \'+a+\' 1d 1e 1f 1g I.F\')}};6 E(a,b,c){3 d=J(b);4(d.9>0){3 e=a.1h(\'.K\');4(e[0])e.1i();3 f=$(\'\').o(\'K\');3 g=L(d,c),M=N(d,c);f.5(M,g);a.1j(f);3 h=f.1k(1l);h.5(h.O().1m().1n());a.5(h)}}6 L(a,b){3 c=$(\'
\'+a.s+\'
\'),P=$(\'
\'+a.v+\'
\'),Q=$(\'
\'+a.9+\'
\');3 d=$(\'<k></k>\').5(c,\' - \',P,\' 1o \',Q,\' 1p\');3 e=$(\'<R></R>\');4(a.9>10&&!a.7){3 f=$(\'<S></S>\').5(\'1q\');e.5(f);3 g=$(\'<w>-</w>\'),m=$(\'<m></m>\').5(g);3 h=[10,1r,1s,1t,1u];$.1v(h,6(){4(8<=a.9){g=$(\'<w>\'+8+\'</w>\').1w(\'T\',8);m.5(g);4(8==a.7){m.1x=8}}});4(b){m.1y(6(){a.7=1z(8.T);b(a)})}e.5(m)}n $(\'\').o(\'1A\').5(d,e)}6 N(b,c){3 d=$(\'\').o(\'t\');3 e=$(\'<q></q>\').o(\'1B\');6 x(a){a.1C();b.l=a.1D;c(b)}3 f=10,V=(b.s/b.7)-(f/2),W=(b.v/b.7)+(f/2);1E(3 i=1;i<=b.t;i++){4(V>i)X;4(W<i)X;3 g;4(b.l!=i){g=$(\'\'+i+\'\').y(\'z\',i,x)}r{g=$(\'<k>\'+i+\'</k>\')}e.5(g)}4(b.9>0){3 h=$(\'<q></q>\').o(\'v\');3 g;4(b.l>1){g=$(\'\').y(\'z\',(b.l-1),x)}r{g=$(\'<k></k>\')}g.5(\'1F Y\');h.5(g);d.5(h)}4(e.O()){d.5(e);4(b.t==1){e.1G(\'1H\',\'1I\')}}4(b.9>1){3 h=$(\'<q></q>\').o(\'1J\');3 g;4(b.t!=b.l){g=$(\'\').y(\'z\',(b.l+1),x)}r{g=$(\'<k></k>\')}g.5(\'1K Y\');h.5(g);d.5(h)}n d}6 J(a){3 b={};b.9=a.B;b.7=a.C;b.l=a.D||1;b.t=Z(b.9,b.7);b.s=b.l*b.7+1-b.7;b.v=((b.s+b.7-1)<b.9)?(b.s+b.7-1):b.9;n b}6 Z(a,b){3 c=1L.1M(a/b);4((c*b)<a){c++}n c}})(I);',62,111,'|||var|if|append|function|limit|this|total|||||||||||span|start|select|return|addClass|strong|li|else|first|pages|div|last|option|onClickEvent|on|click|init|totalResults|perPage|startPage|genResults|PoppyPagination|apply|arguments|jQuery|calcPageResults|poppy_pagination|createResultBarElm|div2|createPaginateElm|children|strong2|strong3|form|label|value|ul|first_link|last_link|continue|Page|getTotalRows||extend|destroy|removeData|fn|Array|prototype|slice|call|typeof|object|error|Method|does|not|exist|in|find|remove|prepend|clone|true|get|reverse|of|found|Viewing|20|30|40|50|each|attr|selectedIndex|change|parseInt|options|crumbs|preventDefault|data|for|Last|css|visibility|hidden|next|Next|Math|round'.split('|'),0,{}))
<commit_msg>Packed new version with latest changes<commit_after>/* Copyright 2012-2014, Marc S. Brooks (http://mbrooks.info) */
eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(6($){3 j={"A":6(a,b){a=$.11({B:0,C:10,D:1},a);E($(8),a,b)},"12":6(){$(8).13()}};$.14.F=6(a){4(j[a]){n j[a].G(8,15.16.17.18(H,1))}r 4(19 a===\'1a\'||!a){n j.A.G(8,H)}r{$.1b(\'1c \'+a+\' 1d 1e 1f 1g I.F\')}};6 E(a,b,c){3 d=J(b);4(d.9>0){3 e=a.1h(\'.K\');4(e[0])e.1i();3 f=$(\'\').o(\'K\');3 g=L(d,c),M=N(d,c);f.5(M,g);a.1j(f);3 h=f.1k(1l);h.5(h.O().1m().1n());a.5(h)}}6 L(a,b){3 c=$(\'
\'+a.s+\'
\'),P=$(\'
\'+a.v+\'
\'),Q=$(\'
\'+a.9+\'
\');3 d=$(\'<k></k>\').5(c,\' - \',P,\' 1o \',Q,\' 1p\');3 e=$(\'<R></R>\');4(a.9>10&&a.7>1){3 f=$(\'<S></S>\').5(\'1q\');e.5(f);3 g=$(\'<w>-</w>\'),m=$(\'<m></m>\').5(g);3 h=[10,1r,1s,1t,1u];$.1v(h,6(){4(8<=a.9){g=$(\'<w>\'+8+\'</w>\').1w(\'T\',8);m.5(g);4(8==a.7){m.1x=8}}});4(b){m.x(\'1y\',6(){a.7=1z(8.T);b(a)})}e.5(m)}n $(\'\').o(\'1A\').5(d,e)}6 N(b,c){3 d=$(\'\').o(\'t\');3 e=$(\'<q></q>\').o(\'1B\');6 y(a){a.1C();b.l=a.1D;c(b)}3 f=10,V=(b.s/b.7)-(f/2),W=(b.v/b.7)+(f/2);1E(3 i=1;i<=b.t;i++){4(V>i)X;4(W<i)X;3 g;4(b.l!=i){g=$(\'\'+i+\'\').x(\'z\',i,y)}r{g=$(\'<k>\'+i+\'</k>\')}e.5(g)}4(b.9>0){3 h=$(\'<q></q>\').o(\'v\');3 g;4(b.l>1){g=$(\'\').x(\'z\',(b.l-1),y)}r{g=$(\'<k></k>\')}g.5(\'1F Y\');h.5(g);d.5(h)}4(e.O()){d.5(e);4(b.t==1){e.1G(\'1H\',\'1I\')}}4(b.9>1){3 h=$(\'<q></q>\').o(\'1J\');3 g;4(b.t!=b.l){g=$(\'\').x(\'z\',(b.l+1),y)}r{g=$(\'<k></k>\')}g.5(\'1K Y\');h.5(g);d.5(h)}n d}6 J(a){3 b={};b.9=a.B;b.7=a.C;b.l=a.D||1;b.t=Z(b.9,b.7);b.s=b.l*b.7+1-b.7;b.v=((b.s+b.7-1)<b.9)?(b.s+b.7-1):b.9;n b}6 Z(a,b){3 c=1L.1M(a/b);4((c*b)<a){c++}n c}})(I);',62,111,'|||var|if|append|function|limit|this|total|||||||||||span|start|select|return|addClass|strong|li|else|first|pages|div|last|option|on|onClickEvent|click|init|totalResults|perPage|startPage|genResults|PoppyPagination|apply|arguments|jQuery|calcPageResults|poppy_pagination|createResultBarElm|div2|createPaginateElm|children|strong2|strong3|form|label|value|ul|first_link|last_link|continue|Page|getTotalRows||extend|destroy|removeData|fn|Array|prototype|slice|call|typeof|object|error|Method|does|not|exist|in|find|remove|prepend|clone|true|get|reverse|of|found|Viewing|20|30|40|50|each|attr|selectedIndex|change|parseInt|options|crumbs|preventDefault|data|for|Last|css|visibility|hidden|next|Next|Math|round'.split('|'),0,{}))
<|endoftext|>


<commit_before>;(function ( $, window, document, undefined ) {
    "use strict";

    var pluginName = "textareaHighlighter",
        defaults = {
            match: [
                {'name': '.match', 'color': '#09f'},
                {'name': '.hoge', 'color': '#4c9'},
            ]
        };

    // constructor
    function Plugin ( element, options ) {
        this.element = element;
        this.$element = $(element);
        this.settings = $.extend( {}, defaults, options );
        this._defaults = defaults;
        this._name = pluginName;

        // textarea style
        this.style = {
            paddingLeft : parseInt( this.$element.css('padding-left'), 10 ),
            paddingRight: parseInt( this.$element.css('padding-right'), 10 ),
            borderLeft  : parseInt( this.$element.css('border-left-width'), 10 ),
            borderRight : parseInt( this.$element.css('border-right-width'), 10 )
        };

        this.widthExtra = this.style.paddingLeft + this.style.paddingRight + this.style.borderLeft + this.style.borderRight;
        // Hack for firefox, some how width needs to be 2px smallet then the textarea
        if( this.getBrowser().firefox ){
            this.widthExtra = this.widthExtra + 2;
        }

        this.init();
    }

    Plugin.prototype = {
        init: function () {

            var _this          = this,
                $this          = $(this.element),
                $source        = $this.closest('.translation').find('.source'),
                $div           = $(document.createElement('div')).addClass('textarea-wrap').css({ 'position': 'relative' }),
                $backgroundDiv = $(document.createElement('div'));

            $backgroundDiv.addClass('background-div').css({
                'height': $this.height(),
                'width' : $this.width() - _this.widthExtra,

                'position': 'absolute',
                'overflow': 'auto'
            });
            $this.css({
                'position': 'relative',
                'background': 'transparent'
            });

            $this
                .on('scroll', function(){
                    $backgroundDiv.scrollTop( $this.scrollTop() );
                })

                .on('change input', function(e){

                    var textareaText = $(document.createElement('div')).text( $this.val() ).html();

                    for ( var i = 0, imax = _this.settings.match.length; i < imax; i++ ) {
                        $source.find( _this.settings.match[i].name ).each(function(){
                            var $_this      = $(this),
                                matchText   = $_this.text(),
                                replaceText = ''+ matchText +'';

                            var reg = new RegExp( _escapeRegExp( matchText ), 'g');
                            if( reg.test( textareaText) ){
                                textareaText = textareaText.replace( reg, replaceText );
                                $_this.addClass('added');
                            }
                            else{
                                $_this.removeClass('added');
                            }
                        });
                    }
                    $backgroundDiv.html( textareaText );

                    // update size
                    if( $backgroundDiv.height() !== $this.height() ){
                        $backgroundDiv.css({
                            'width' : $this.outerWidth() - _this.widthExtra,
                            'height': $this.height()
                        });
                    }
                });

            $this.wrap( $div ).before( $backgroundDiv );
        },
        /**
         *
         * HELPERS
         *
         */
        getBrowser: function(){
            var ua      = navigator.userAgent,
                msie    = /(msie|trident)/i.test(ua),
                chrome  = /chrome/i.test(ua),
                firefox = /firefox/i.test(ua),
                safari  = /safari/i.test(ua) && !chrome;

            if( msie ){ return { msie: true }; }
            if( chrome ){ return { chrome: true }; }
            if( firefox ){ return { firefox: true }; }
            if( safari ){ return { safari: true }; }
        }
    };

    /**
     *
     * HELPER FUNCTIONS
     *
     */
    var _escapeRegExp = function(str){
        return str.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
    };

    // A really lightweight plugin wrapper around the constructor,
    // preventing against multiple instantiations
    $.fn[ pluginName ] = function ( options ) {
        return this.each(function(i) {
            if ( ! $.data( this, "plugin_" + pluginName ) ) {
                $.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
            }
        });
    };

})( jQuery, window, document );<commit_msg>update/calculate padding for background div<commit_after>;(function ( $, window, document, undefined ) {
    "use strict";

    var pluginName = "textareaHighlighter",
        defaults = {
            match: [
                {'name': '.match', 'color': '#09f'},
                {'name': '.hoge', 'color': '#4c9'},
            ]
        };

    // constructor
    function Plugin ( element, options ) {
        this.element = element;
        this.$element = $(element);
        this.settings = $.extend( {}, defaults, options );
        this._defaults = defaults;
        this._name = pluginName;

        // textarea style
        this.style = {
            paddingTop   : parseInt( this.$element.css('padding-top'), 10 ),
            paddingRight : parseInt( this.$element.css('padding-right'), 10 ),
            paddingBottom: parseInt( this.$element.css('padding-bottom'), 10 ),
            paddingLeft  : parseInt( this.$element.css('padding-left'), 10 ),
            borderTop    : parseInt( this.$element.css('border-top-width'), 10 ),
            borderRight  : parseInt( this.$element.css('border-right-width'), 10 ),
            borderBottom : parseInt( this.$element.css('border-bottom-width'), 10 ),
            borderLeft   : parseInt( this.$element.css('border-left-width'), 10 ),
            lineHeight   : this.$element.css('line-height')
        };

        this.widthExtra = this.style.paddingLeft + this.style.paddingRight + this.style.borderLeft + this.style.borderRight;

        this.style.paddingTop += this.style.borderTop;
        this.style.paddingLeft += this.style.borderLeft;

        // Hack for firefox, some how width needs to be 2px smallet then the textarea
        // and padding-left needs to be added 1px
        if( this.getBrowser().firefox ){
            this.widthExtra += 2;
            this.style.paddingLeft += 1;
        }

        this.init();
    }

    Plugin.prototype = {
        init: function () {

            var _this          = this,
                $this          = $(this.element),
                $source        = $this.closest('.translation').find('.source'),
                $div           = $(document.createElement('div')).addClass('textarea-wrap').css({ 'position': 'relative' }),
                $backgroundDiv = $(document.createElement('div'));

            $backgroundDiv.addClass('background-div').css({
                'height': $this.height(),
                'width' : $this.width() - _this.widthExtra,

                'line-height'   : _this.style.lineHeight,
                'padding-top'   : _this.style.paddingTop,
                'padding-right' : _this.style.paddingRight,
                'padding-bottom': _this.style.paddingBottom,
                'padding-left'  : _this.style.paddingLeft,
                'position'      : 'absolute',
                'overflow'      : 'auto'
            });
            $this.css({
                'position': 'relative',
                'background': 'transparent'
            });

            $this
                .on('scroll', function(){
                    $backgroundDiv.scrollTop( $this.scrollTop() );
                })

                .on('change input', function(e){

                    var textareaText = $(document.createElement('div')).text( $this.val() ).html();

                    for ( var i = 0, imax = _this.settings.match.length; i < imax; i++ ) {
                        $source.find( _this.settings.match[i].name ).each(function(){
                            var $_this      = $(this),
                                matchText   = $_this.text(),
                                replaceText = ''+ matchText +'';

                            var reg = new RegExp( _escapeRegExp( matchText ), 'g');
                            if( reg.test( textareaText) ){
                                textareaText = textareaText.replace( reg, replaceText );
                                $_this.addClass('added');
                            }
                            else{
                                $_this.removeClass('added');
                            }
                        });
                    }
                    $backgroundDiv.html( textareaText );

                    // update size
                    if( $backgroundDiv.height() !== $this.height() ){
                        $backgroundDiv.css({
                            'width' : $this.outerWidth() - _this.widthExtra,
                            'height': $this.height()
                        });
                    }
                });

            $this.wrap( $div ).before( $backgroundDiv );
        },
        /**
         *
         * HELPERS
         *
         */
        getBrowser: function(){
            var ua      = navigator.userAgent,
                msie    = /(msie|trident)/i.test(ua),
                chrome  = /chrome/i.test(ua),
                firefox = /firefox/i.test(ua),
                safari  = /safari/i.test(ua) && !chrome;

            if( msie ){ return { msie: true }; }
            if( chrome ){ return { chrome: true }; }
            if( firefox ){ return { firefox: true }; }
            if( safari ){ return { safari: true }; }
        }
    };

    /**
     *
     * HELPER FUNCTIONS
     *
     */
    var _escapeRegExp = function(str){
        return str.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
    };

    // A really lightweight plugin wrapper around the constructor,
    // preventing against multiple instantiations
    $.fn[ pluginName ] = function ( options ) {
        return this.each(function(i) {
            if ( ! $.data( this, "plugin_" + pluginName ) ) {
                $.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
            }
        });
    };

})( jQuery, window, document );<|endoftext|>


<commit_before>'use strict';
function leveshtein(a, b){
  if(a.length === 0) return b.length;
  if(b.length === 0) return a.length;

  var matrix = [];

  // increment along the first column of each row
  var i;
  for(i = 0; i <= b.length; i++){
    matrix[i] = [i];
  }

  // increment each column in the first row
  var j;
  for(j = 0; j <= a.length; j++){
    matrix[0][j] = j;
  }

  // Fill in the rest of the matrix
  for(i = 1; i <= b.length; i++){
    for(j = 1; j <= a.length; j++){
      if(b.charAt(i-1) == a.charAt(j-1)){
        matrix[i][j] = matrix[i-1][j-1];
      } else {
        matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
                                Math.min(matrix[i][j-1] + 1, // insertion
                                         matrix[i-1][j] + 1)); // deletion
      }
    }
  }

  return matrix[b.length][a.length];
}

function getSmallestIndex(arr) {
  var i = 0, l = arr.length;
  var min = Number.MAX_VALUE;
  var mi = -1;
  for (; i < l; i++) {
    if (arr[i] < min) {
      min = arr[i];
      mi = i;
    }
  }
  return mi;
}

// TODO not very fast at all
function getAllProperties(t) {
  let prop = [];
  function acq(obj) {
    if (obj === null || obj === undefined) return; // recursive approach
    const p = Object.getOwnPropertyNames(obj);
    for (let i = 0; i < p.length; i++) {
      const a = p[i];
      if (!prop.includes(a)) {
        prop.push(a);
      }
    }
    acq(Object.getPrototypeOf(obj));
  }
  acq(t);
  return prop;
}
function getClosest(target, name) {
  const rankings = [];
  // getOwnPropertyNames does not return inherited properties, but .keys does not return non-enumerable properties; discussion needed
  const targ = getAllProperties(target);

  // sort the array, for fast-tracking (the best match should be the shortest one, right?)
  // for verifying: aw.clear === clearTimeout, not clearInterval
  // longest string first:
  //targ.sort((a, b) => b.length - a.length);
  // shortest string first:
  targ.sort((a, b) => a.length - b.length);

  console.log('targ: ' + targ);

  for (let i = 0; i < targ.length; i++) {
    const t = targ[i];
    // FAST-TRACK if any key in the object starts with the requested name. without this, aw.clo === aw.clc rather than aw.close
    if (t.startsWith(name)) {
      //return target[t];
      console.log('1:'+t);
      return tryProxy(target[t]);
    }

    rankings.push(leveshtein(name, t));
  }

  const res = target[targ[getSmallestIndex(rankings)]];
  // debugging purposes, get the name of the real key being returned:
  console.log('2:'+res);

  // return a new proxy on the object using window._proxyHandler
  //return target[targ[getSmallestIndex(rankings)]];
  return tryProxy(res);
}

function tryProxy(res) {
  console.log('the RES ' + res);
  // Proxy will only accept non-null, non-undefined 'object' (and apparently 'function')
  return res !== undefined && res !== null && (typeof res == 'object' || typeof res == 'function') ? new Proxy(res, window._proxyHandler) : res;
}

{
  // proxyHandler is mirrored to window so we can call it recursively
  const proxyHandler = window._proxyHandler = {
    get: function(target, name) {
      // side note: zZçÇyYx (only the lowercase form of x) are all fairly unused in the global namespace
      // ones i've used: qQ$
      // ones i haven't used yet: _ (lodash)

      // fast-track if we've got jQuery/moo business
      if (name == '$' && '_steamrollerjQuery' in target) {
        return target._steamrollerjQuery;
      }
      if (name == 'Q' && '_steamrollerMooTools' in target) {
        return target._steamrollerMooTools;
      }
      //
      return name in target ?
        tryProxy(target[name]) :
        getClosest(target, name);
    }
  };

  let aw = new Proxy(window, proxyHandler);
  window.aw = aw;

  // expose jQuery from SleazyCatcher if it's available
  if (window._steamrollerjQuery) {
    if (window.$) {
      console.error('you have two jquery installed. gonna expose to window.q instead; you can still use aw.$');
      window.q = new Proxy(window._steamrollerjQuery, proxyHandler);
    } else {
      window.$ = new Proxy(window._steamrollerjQuery, proxyHandler);
    }
  }
  // the same for mootools
  if (window._steamrollerMooTools) {
    window.oo = new Proxy(window._steamrollerMooTools, proxyHandler);
  }
}
<commit_msg>Update sleazyProxy.js<commit_after>'use strict';
function leveshtein(a, b){
  if(a.length === 0) return b.length;
  if(b.length === 0) return a.length;

  var matrix = [];

  // increment along the first column of each row
  var i;
  for(i = 0; i <= b.length; i++){
    matrix[i] = [i];
  }

  // increment each column in the first row
  var j;
  for(j = 0; j <= a.length; j++){
    matrix[0][j] = j;
  }

  // Fill in the rest of the matrix
  for(i = 1; i <= b.length; i++){
    for(j = 1; j <= a.length; j++){
      if(b.charAt(i-1) == a.charAt(j-1)){
        matrix[i][j] = matrix[i-1][j-1];
      } else {
        matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
                                Math.min(matrix[i][j-1] + 1, // insertion
                                         matrix[i-1][j] + 1)); // deletion
      }
    }
  }

  return matrix[b.length][a.length];
}

function getSmallestIndex(arr) {
  var i = 0, l = arr.length;
  var min = Number.MAX_VALUE;
  var mi = -1;
  for (; i < l; i++) {
    if (arr[i] < min) {
      min = arr[i];
      mi = i;
    }
  }
  return mi;
}

// TODO not very fast at all
function getAllProperties(t) {
  let prop = [];
  function acq(obj) {
    if (obj === null || obj === undefined) return; // recursive approach
    const p = Object.getOwnPropertyNames(obj);
    for (let i = 0; i < p.length; i++) {
      const a = p[i];
      if (!prop.includes(a)) {
        prop.push(a);
      }
    }
    acq(Object.getPrototypeOf(obj));
  }
  acq(t);
  return prop;
}
function getClosest(target, name) {
  const rankings = [];
  // getOwnPropertyNames does not return inherited properties, but .keys does not return non-enumerable properties; discussion needed
  const targ = getAllProperties(target);

  // sort the array, for fast-tracking (the best match should be the shortest one, right?)
  // for verifying: aw.clear === clearTimeout, not clearInterval
  // longest string first:
  //targ.sort((a, b) => b.length - a.length);
  // shortest string first:
  targ.sort((a, b) => a.length - b.length);

  console.log('targ: ' + targ);

  for (let i = 0; i < targ.length; i++) {
    const t = targ[i];
    // FAST-TRACK if any key in the object starts with the requested name. without this, aw.clo === aw.clc rather than aw.close
    if (t.startsWith(name)) {
      //return target[t];
      console.log('1:'+t);
      return tryProxy(target[t]);
    }

    rankings.push(leveshtein(name, t));
  }

  const res = target[targ[getSmallestIndex(rankings)]];
  // debugging purposes, get the name of the real key being returned:
  console.log('2:'+res);

  // return a new proxy on the object using window._proxyHandler
  //return target[targ[getSmallestIndex(rankings)]];
  return tryProxy(res);
}

function tryProxy(res) {
  console.log('the RES ' + res);
  // Proxy will only accept non-null, non-undefined 'object' (and apparently 'function')
  return res !== undefined && res !== null && (typeof res == 'object' || typeof res == 'function') ? new Proxy(res, window._proxyHandler) : res;
}

{
  // proxyHandler is mirrored to window so we can call it recursively
  const proxyHandler = window._proxyHandler = {
    get: function(target, name) {
      // side note: zZçÇyYx (only the lowercase form of x) are all fairly unused in the global namespace
      // ones i've used: qQ$
      // ones i haven't used yet: _ (lodash)

      // fast-track if we've got jQuery/moo business
      if (name == '$' && '_steamrollerjQuery' in target) {
        return target._steamrollerjQuery;
      }
      if (name == 'Q' && '_steamrollerMooTools' in target) {
        return target._steamrollerMooTools;
      }
      //
      return name in target ?
        tryProxy(target[name]) :
        getClosest(target, name);
    }
  };

  let aw = new Proxy(window, proxyHandler);
  window.aw = aw;

  // expose jQuery from SleazyCatcher if it's available
  if (window._steamrollerjQuery) {
    if (window.$) {
      console.error('you have two jquery installed. gonna expose to window.q instead; you can still use aw.$');
      window.q = new Proxy(window._steamrollerjQuery, proxyHandler);
    } else {
      window.$ = new Proxy(window._steamrollerjQuery, proxyHandler);
    }
  }
  // the same for mootools
  if (window._steamrollerMooTools) {
    window.oo = new Proxy(window._steamrollerMooTools, proxyHandler);
  }

  // now we get to the fun stuff!
  function getInfinity() {
    return typeof Infinity !== 'undefined' ? Infinity : 'Infinity' in window ? window['Infinity'] : 9e999;
  }
  window.ƒ = {
      π: Math.PI,
      ø: [],
      Ø: NaN,
      e: 2.7182818284590452353602874713527,
      root2: 2.7182818284590452353602874713527,
      rt2: 2.7182818284590452353602874713527,
      sqrt2: 2.7182818284590452353602874713527,
      α: 2.5029,
      δ: 4.6692,
      ζ: 1.2020569,
      φ: 1.61803398874,
      γ: 1.30357,
      K: 2.6854520010,
      oo: getInfinity(),
      A: 1.2824271291,
      C10: 0.12345678910111213141516171819202122232425252728293031323334353637,
      c: 299792458
  };
}
<|endoftext|>