<commit_before>var fs = Npm.require("fs"), path = Npm.require("path"),
    basepath = path.resolve(".") + "/packages/reward";

// Constants

var CASH_IMAGE_FILES = {
    0: "cash-coins.png",
    1: "cash-money-bag.png",
    2: "cash-many-money-bags.png",
    3: "cash-bars.png",
    4: "cash-jackpot.png"
};

var LIGHT_ORANGE = "#E0C39D",
    LIGHT_GREY = "#78665A",
    DARK_GREY = "#484640";

var SMALL = "19.5px",
    MEDIUM = "26px",
    LARGE = "39px";

// Helper Functions

var RewardFormatter = function (options) {
    this.options = options;
};

RewardFormatter.prototype.CONFIGS = {
    USER_NAME_MAX_CHARACTER: 10,
    CLAIMER_MAX_COUNT: 2,
    CLAIMER_USER_NAME_MAX_CHARACTER: 10
};

RewardFormatter.prototype._limitStringLength = function (string, maxCharacter) {
    if (string.length <= maxCharacter)
        return string;

    return string.substring(0, maxCharacter - 2) + "...";
};

RewardFormatter.prototype._formatCurrency = function (amount) {
    if (this.options.currency === "usd") {
        if (amount > 999999999) {
            return "$" + parseFloat((amount / 1000000000).toFixed(2)) + "B";
        }

        if (amount > 999999) {
            return "$" + parseFloat((amount / 1000000).toFixed(2)) + "M";
        }

        if (amount > 999) {
            return "$" + parseFloat((amount / 1000).toFixed(2)) + "K";
        }

        return "$" + parseFloat(amount.toFixed(2));
    } else {
        throw this.options.currency + " not implemented";
    }

};

RewardFormatter.prototype.getRewardAmount = function () {
    var rewardAmount = this._formatCurrency(this.options.amount);
    // TODO: convert unit

    return rewardAmount;
};

RewardFormatter.prototype.getUserName = function () {
    return this._limitStringLength(this.options.userName, this.CONFIGS.USER_NAME_MAX_CHARACTER);
};

/**
 * Generate reward claimer strings. Default only displays limited entries, others
 * will be replaced by "+X others".
 * @return {Array.String}
 */
RewardFormatter.prototype.getClaimerStrings = function () {
    var claimers = [];

    var i, claimer;
    var max = Math.min(this.options.claimedBy.length, this.CONFIGS.CLAIMER_MAX_COUNT);

    for (i = 0; i < max; i++) {
        claimer = this._limitStringLength(this.options.claimedBy[i].userName,
                  this.CONFIGS.CLAIMER_USER_NAME_MAX_CHARACTER) + " " +
                  this._formatCurrency(this.options.claimedBy[i].amount);

        claimers.push(claimer);
    }

    var othersCount = this.options.claimedBy.length - max;
    if (othersCount === 1)
        claimers.push("+1 other");
    else if (othersCount > 1)
        claimers.push("+" + othersCount + " others");

    return claimers;
};

//setup fonts

var woodshop = "Woodshop", futuraLT = "FuturaLT";
var Fonts = {
    "Woodshop": new Canvas.Font(woodshop, RewardUtils.assetFile("woodshop-regular.otf")),
    "FuturaLT": new Canvas.Font(futuraLT, RewardUtils.assetFile("futura-lt.ttf"))
};
Fonts.FuturaLT.addFace(RewardUtils.assetFile("futura-lt-heavy.otf"), "bold");

/**
 * generates the reward status comment image
 * @param {{status: string, amount: Number, currency: string, expiredDate: Date, userName: string,
 *          claimedBy: Array.<{userName: string, amount: Number}>}=} options
 * - status: "open", "closed", "reopened", "claimed"
 * - userName: user who posted the reward
 * - claimedBy: only used on "closed" or "claimed"
 * @return {Canvas}
 */
RewardUtils.statusComment = function (options) {
    var status = options.status;
    var formatter = new RewardFormatter(options);

    var width = 712, height = 368;
    var centerX = Math.floor(width / 2),
        centerY = Math.floor(height / 2);

    // Initialize canvas
    var canvas = new Canvas(width, height),
        ctx = canvas.getContext("2d");

    ctx.addFont(Fonts.FuturaLT);
    ctx.addFont(Fonts.Woodshop);

    // Draw background
    ctx.fillStyle = LIGHT_ORANGE;
    ctx.fillRect(0, 0, width, height);

    if (status === "open" || status === "closed" || status === "reopened") {
        var statusHeader;
        var bountyAmount;
        var bountyStatusImageFile;

        if (status === "open") {
            statusHeader = "BOUNTY NOW OPEN!";
            bountyAmount = "This bounty is posted for " + formatter.getRewardAmount();
            bountyStatusImageFile = "banner-bounty-open.png";
        } else if (status === "closed") {
            statusHeader = "BOUNTY CLOSED";
            bountyAmount = "This bounty was posted for " + formatter.getRewardAmount();
            bountyStatusImageFile = "banner-bounty-closed.png";
        } else if (status === "reopened") {
            statusHeader = "BOUNTY REOPENED";
            bountyAmount = "This bounty is posted for " + formatter.getRewardAmount();
            bountyStatusImageFile = "banner-bounty-reopened.png";
        }

        // Setup styles
        var headerFontColor = DARK_GREY,
            contentFontColor = LIGHT_GREY,
            footerFontColor = DARK_GREY,
            posterFontColor = status !== "closed" ? footerFontColor : LIGHT_GREY;

        var headerFontSize = LARGE,
            contentFontSize = MEDIUM,
            footerFontSize = MEDIUM,
            footerSmallerFontSize = SMALL,
            posterFontSize = status !== "closed" ? footerFontSize : SMALL;

        //
        // Align elements
        //

        // Bounty Status
        var bountyStatusOriginX = centerX - 332;
        var bountyStatusOriginY = Math.floor((height - 336) / 2);   // horizontally center (bounty status image valid height is 328)

        // Bounty Cash
        var bountyCashOriginX = bountyStatusOriginX + 147;
        var bountyCashOriginY = bountyStatusOriginY + 233;

        // Bounty Header
        var statusHeaderOriginX = centerX - 86;
        var statusHeaderOriginY = bountyStatusOriginY + 65;

        // Bounty Content
        var leftOffsetIndent = 5;
        var bountyContentOriginX = statusHeaderOriginX + leftOffsetIndent;
        var bountyAmountOriginY = statusHeaderOriginY + 53;
        var bountyExpirationOriginY = bountyAmountOriginY + 37;

        // Bounty Footer (align right)
        var footerOriginX = centerX + 319;
        var siteLinkOriginY = bountyStatusOriginY + 321;
        var posterUserOriginY = status !== "closed" ? siteLinkOriginY - 31 : siteLinkOriginY - 23;

        // Claimed by text (when closed)
        var claimedByTextOriginX = centerX - 35;
        var claimedByTextOriginY = bountyExpirationOriginY + 45;
        var claimedByPersonOriginX = {
            0: claimedByTextOriginX + 142,
            1: claimedByTextOriginX + 142,
            2: claimedByTextOriginX + 142
        };
        var claimedByPersonOriginY = {
            0: claimedByTextOriginY,
            1: claimedByTextOriginY + 30,
            2: claimedByTextOriginY + 60
        };

        //
        // Draw elements
        //

        // Draw Bounty status
        ctx.font = RewardUtils.canvasFontString(headerFontSize, woodshop);
        ctx.fillStyle = headerFontColor;
        ctx.fillText(statusHeader, statusHeaderOriginX, statusHeaderOriginY);

        // Draw bounty amount and expiration
        ctx.font = RewardUtils.canvasFontString(contentFontSize, futuraLT, "bold");
        ctx.fillStyle = contentFontColor;
        ctx.fillText(bountyAmount, bountyContentOriginX, bountyAmountOriginY);
        var bountyExpiration = "Expires: " + Tools.formatDate(options.expiredDate);
        ctx.fillText(bountyExpiration, bountyContentOriginX, bountyExpirationOriginY);

        // Draw codebounty plug and link (align right)
        ctx.textAlign = "right";
        ctx.fillStyle = posterFontColor;
        ctx.font = RewardUtils.canvasFontString(posterFontSize, futuraLT);
        var posterUser = "Posted by " + formatter.getUserName();
        ctx.fillText(posterUser, footerOriginX, posterUserOriginY);

        ctx.fillStyle = footerFontColor;
        ctx.font = RewardUtils.canvasFontString(footerSmallerFontSize, futuraLT);
        var siteLink = "codebounty.co";
        ctx.fillText(siteLink, footerOriginX, siteLinkOriginY);
        ctx.textAlign = "left";

        // Draw claimed by text
        if (status === "closed" && options.claimedBy) {
            ctx.fillStyle = footerFontColor;
            ctx.font = RewardUtils.canvasFontString(footerFontSize, futuraLT);
            var claimedByText = "Claimed by: ";
            ctx.fillText(claimedByText, claimedByTextOriginX, claimedByTextOriginY);

            // Only display two claimers
            var claimers = formatter.getClaimerStrings();
            claimers.forEach(function (element, index) {
                ctx.fillText(element, claimedByPersonOriginX[index], claimedByPersonOriginY[index]);
            });
        }

        // Draw bounty status image
        var bountyStatusImage = new Image();
        bountyStatusImage.src = fs.readFileSync(RewardUtils.assetFile(bountyStatusImageFile));
        ctx.drawImage(bountyStatusImage, bountyStatusOriginX, bountyStatusOriginY, bountyStatusImage.width, bountyStatusImage.height);

        var cashLevel = RewardUtils.cashLevel(options.amount, options.currency);

        // Draw bounty cash image
        var bountyCashImageFile = CASH_IMAGE_FILES[cashLevel];
        var bountyCashImage = new Image();
        bountyCashImage.src = fs.readFileSync(RewardUtils.assetFile(bountyCashImageFile));
        ctx.drawImage(bountyCashImage, bountyCashOriginX, bountyCashOriginY, bountyCashImage.width, bountyCashImage.height);

        return canvas;
    }

    if (status === "claimed") {
        // When status is claimed
        // Setup styles
        var posterUserFontColor = DARK_GREY,
            claimedByFontColor = LIGHT_GREY;
        var posterUserFontSize = MEDIUM,
            claimedByFontSize = MEDIUM;

        //
        // Align elements
        //

        // Bounty claimed stamp
        var bountyClaimedStampOriginX = centerX - 124;
        var bountyClaimedStampOriginY = centerY - 137;

        // Sheriff icon
        var sheriffIconOriginX = centerX - 345;
        var sheriffIconOriginY = Math.floor((height - 323) / 2);

        // Poster user
        var posterUserOriginX = sheriffIconOriginX + 65;
        var posterUserOriginY = sheriffIconOriginY + 323;

        // Claimed by text
        var claimedByTextOriginX = centerX - 36;
        var claimedByTextOriginY = centerY + 98;
        var claimedByPersonOriginX = {
            0: claimedByTextOriginX + 142,
            1: claimedByTextOriginX + 142,
            2: claimedByTextOriginX + 142
        };
        var claimedByPersonOriginY = {
            0: claimedByTextOriginY,
            1: claimedByTextOriginY + 30,
            2: claimedByTextOriginY + 60
        };

        //
        // Draw elements
        //

        // Draw bounty claimed stamp
        var bountyClaimedStampImageFile = "bounty-claimed-stamp.png";
        var bountyClaimedStampImage = new Image();
        bountyClaimedStampImage.src = fs.readFileSync(RewardUtils.assetFile(bountyClaimedStampImageFile));
        ctx.drawImage(bountyClaimedStampImage, bountyClaimedStampOriginX, bountyClaimedStampOriginY, bountyClaimedStampImage.width, bountyClaimedStampImage.height);

        // Draw poster user
        ctx.fillStyle = posterUserFontColor;
        ctx.font = RewardUtils.canvasFontString(posterUserFontSize, futuraLT, "bold");
        var posterUser = formatter.getUserName();
        ctx.fillText(posterUser, posterUserOriginX, posterUserOriginY);

        // Draw claimed by text
        if (options.claimedBy) {
            ctx.font = RewardUtils.canvasFontString(claimedByFontSize, futuraLT, "bold");
            ctx.fillStyle = claimedByFontColor;
            var claimedByText = "Claimed by: ";
            ctx.fillText(claimedByText, claimedByTextOriginX, claimedByTextOriginY);

            // Only display two claimers
            var claimers = formatter.getClaimerStrings();
            claimers.forEach(function (element, index) {
                ctx.fillText(element, claimedByPersonOriginX[index], claimedByPersonOriginY[index]);
            });
        }

        // Draw sheriff icon
        var sheriffIconImageFile = "sheriff.png";
        var sheriffIconImage = new Image();
        sheriffIconImage.src = fs.readFileSync(RewardUtils.assetFile(sheriffIconImageFile));
        ctx.drawImage(sheriffIconImage, sheriffIconOriginX, sheriffIconOriginY, sheriffIconImage.width, sheriffIconImage.height);

        return canvas;
    }

    throw "Unknown bounty status";
};
<commit_msg>Change poster name text align to center, #21.<commit_after>var fs = Npm.require("fs"), path = Npm.require("path"),
    basepath = path.resolve(".") + "/packages/reward";

// Constants

var CASH_IMAGE_FILES = {
    0: "cash-coins.png",
    1: "cash-money-bag.png",
    2: "cash-many-money-bags.png",
    3: "cash-bars.png",
    4: "cash-jackpot.png"
};

var LIGHT_ORANGE = "#E0C39D",
    LIGHT_GREY = "#78665A",
    DARK_GREY = "#484640";

var SMALL = "19.5px",
    MEDIUM = "26px",
    LARGE = "39px";

// Helper Functions

var RewardFormatter = function (options) {
    this.options = options;
};

RewardFormatter.prototype.CONFIGS = {
    USER_NAME_MAX_CHARACTER: 10,
    CLAIMER_MAX_COUNT: 2,
    CLAIMER_USER_NAME_MAX_CHARACTER: 10
};

RewardFormatter.prototype._limitStringLength = function (string, maxCharacter) {
    if (string.length <= maxCharacter)
        return string;

    return string.substring(0, maxCharacter - 2) + "...";
};

RewardFormatter.prototype._formatCurrency = function (amount) {
    if (this.options.currency === "usd") {
        if (amount > 999999999) {
            return "$" + parseFloat((amount / 1000000000).toFixed(2)) + "B";
        }

        if (amount > 999999) {
            return "$" + parseFloat((amount / 1000000).toFixed(2)) + "M";
        }

        if (amount > 999) {
            return "$" + parseFloat((amount / 1000).toFixed(2)) + "K";
        }

        return "$" + parseFloat(amount.toFixed(2));
    } else {
        throw this.options.currency + " not implemented";
    }

};

RewardFormatter.prototype.getRewardAmount = function () {
    var rewardAmount = this._formatCurrency(this.options.amount);
    // TODO: convert unit

    return rewardAmount;
};

RewardFormatter.prototype.getUserName = function () {
    return this._limitStringLength(this.options.userName, this.CONFIGS.USER_NAME_MAX_CHARACTER);
};

/**
 * Generate reward claimer strings. Default only displays limited entries, others
 * will be replaced by "+X others".
 * @return {Array.String}
 */
RewardFormatter.prototype.getClaimerStrings = function () {
    var claimers = [];

    var i, claimer;
    var max = Math.min(this.options.claimedBy.length, this.CONFIGS.CLAIMER_MAX_COUNT);

    for (i = 0; i < max; i++) {
        claimer = this._limitStringLength(this.options.claimedBy[i].userName,
                  this.CONFIGS.CLAIMER_USER_NAME_MAX_CHARACTER) + " " +
                  this._formatCurrency(this.options.claimedBy[i].amount);

        claimers.push(claimer);
    }

    var othersCount = this.options.claimedBy.length - max;
    if (othersCount === 1)
        claimers.push("+1 other");
    else if (othersCount > 1)
        claimers.push("+" + othersCount + " others");

    return claimers;
};

//setup fonts

var woodshop = "Woodshop", futuraLT = "FuturaLT";
var Fonts = {
    "Woodshop": new Canvas.Font(woodshop, RewardUtils.assetFile("woodshop-regular.otf")),
    "FuturaLT": new Canvas.Font(futuraLT, RewardUtils.assetFile("futura-lt.ttf"))
};
Fonts.FuturaLT.addFace(RewardUtils.assetFile("futura-lt-heavy.otf"), "bold");

/**
 * generates the reward status comment image
 * @param {{status: string, amount: Number, currency: string, expiredDate: Date, userName: string,
 *          claimedBy: Array.<{userName: string, amount: Number}>}=} options
 * - status: "open", "closed", "reopened", "claimed"
 * - userName: user who posted the reward
 * - claimedBy: only used on "closed" or "claimed"
 * @return {Canvas}
 */
RewardUtils.statusComment = function (options) {
    var status = options.status;
    var formatter = new RewardFormatter(options);

    var width = 712, height = 368;
    var centerX = Math.floor(width / 2),
        centerY = Math.floor(height / 2);

    // Initialize canvas
    var canvas = new Canvas(width, height),
        ctx = canvas.getContext("2d");

    ctx.addFont(Fonts.FuturaLT);
    ctx.addFont(Fonts.Woodshop);

    // Draw background
    ctx.fillStyle = LIGHT_ORANGE;
    ctx.fillRect(0, 0, width, height);

    if (status === "open" || status === "closed" || status === "reopened") {
        var statusHeader;
        var bountyAmount;
        var bountyStatusImageFile;

        if (status === "open") {
            statusHeader = "BOUNTY NOW OPEN!";
            bountyAmount = "This bounty is posted for " + formatter.getRewardAmount();
            bountyStatusImageFile = "banner-bounty-open.png";
        } else if (status === "closed") {
            statusHeader = "BOUNTY CLOSED";
            bountyAmount = "This bounty was posted for " + formatter.getRewardAmount();
            bountyStatusImageFile = "banner-bounty-closed.png";
        } else if (status === "reopened") {
            statusHeader = "BOUNTY REOPENED";
            bountyAmount = "This bounty is posted for " + formatter.getRewardAmount();
            bountyStatusImageFile = "banner-bounty-reopened.png";
        }

        // Setup styles
        var headerFontColor = DARK_GREY,
            contentFontColor = LIGHT_GREY,
            footerFontColor = DARK_GREY,
            posterFontColor = status !== "closed" ? footerFontColor : LIGHT_GREY;

        var headerFontSize = LARGE,
            contentFontSize = MEDIUM,
            footerFontSize = MEDIUM,
            footerSmallerFontSize = SMALL,
            posterFontSize = status !== "closed" ? footerFontSize : SMALL;

        //
        // Align elements
        //

        // Bounty Status
        var bountyStatusOriginX = centerX - 332;
        var bountyStatusOriginY = Math.floor((height - 336) / 2);   // horizontally center (bounty status image valid height is 328)

        // Bounty Cash
        var bountyCashOriginX = bountyStatusOriginX + 147;
        var bountyCashOriginY = bountyStatusOriginY + 233;

        // Bounty Header
        var statusHeaderOriginX = centerX - 86;
        var statusHeaderOriginY = bountyStatusOriginY + 65;

        // Bounty Content
        var leftOffsetIndent = 5;
        var bountyContentOriginX = statusHeaderOriginX + leftOffsetIndent;
        var bountyAmountOriginY = statusHeaderOriginY + 53;
        var bountyExpirationOriginY = bountyAmountOriginY + 37;

        // Bounty Footer (align right)
        var footerOriginX = centerX + 319;
        var siteLinkOriginY = bountyStatusOriginY + 321;
        var posterUserOriginY = status !== "closed" ? siteLinkOriginY - 31 : siteLinkOriginY - 23;

        // Claimed by text (when closed)
        var claimedByTextOriginX = centerX - 35;
        var claimedByTextOriginY = bountyExpirationOriginY + 45;
        var claimedByPersonOriginX = {
            0: claimedByTextOriginX + 142,
            1: claimedByTextOriginX + 142,
            2: claimedByTextOriginX + 142
        };
        var claimedByPersonOriginY = {
            0: claimedByTextOriginY,
            1: claimedByTextOriginY + 30,
            2: claimedByTextOriginY + 60
        };

        //
        // Draw elements
        //

        // Draw Bounty status
        ctx.font = RewardUtils.canvasFontString(headerFontSize, woodshop);
        ctx.fillStyle = headerFontColor;
        ctx.fillText(statusHeader, statusHeaderOriginX, statusHeaderOriginY);

        // Draw bounty amount and expiration
        ctx.font = RewardUtils.canvasFontString(contentFontSize, futuraLT, "bold");
        ctx.fillStyle = contentFontColor;
        ctx.fillText(bountyAmount, bountyContentOriginX, bountyAmountOriginY);
        var bountyExpiration = "Expires: " + Tools.formatDate(options.expiredDate);
        ctx.fillText(bountyExpiration, bountyContentOriginX, bountyExpirationOriginY);

        // Draw codebounty plug and link (align right)
        ctx.textAlign = "right";
        ctx.fillStyle = posterFontColor;
        ctx.font = RewardUtils.canvasFontString(posterFontSize, futuraLT);
        var posterUser = "Posted by " + formatter.getUserName();
        ctx.fillText(posterUser, footerOriginX, posterUserOriginY);

        ctx.fillStyle = footerFontColor;
        ctx.font = RewardUtils.canvasFontString(footerSmallerFontSize, futuraLT);
        var siteLink = "codebounty.co";
        ctx.fillText(siteLink, footerOriginX, siteLinkOriginY);
        ctx.textAlign = "left";

        // Draw claimed by text
        if (status === "closed" && options.claimedBy) {
            ctx.fillStyle = footerFontColor;
            ctx.font = RewardUtils.canvasFontString(footerFontSize, futuraLT);
            var claimedByText = "Claimed by: ";
            ctx.fillText(claimedByText, claimedByTextOriginX, claimedByTextOriginY);

            // Only display two claimers
            var claimers = formatter.getClaimerStrings();
            claimers.forEach(function (element, index) {
                ctx.fillText(element, claimedByPersonOriginX[index], claimedByPersonOriginY[index]);
            });
        }

        // Draw bounty status image
        var bountyStatusImage = new Image();
        bountyStatusImage.src = fs.readFileSync(RewardUtils.assetFile(bountyStatusImageFile));
        ctx.drawImage(bountyStatusImage, bountyStatusOriginX, bountyStatusOriginY, bountyStatusImage.width, bountyStatusImage.height);

        var cashLevel = RewardUtils.cashLevel(options.amount, options.currency);

        // Draw bounty cash image
        var bountyCashImageFile = CASH_IMAGE_FILES[cashLevel];
        var bountyCashImage = new Image();
        bountyCashImage.src = fs.readFileSync(RewardUtils.assetFile(bountyCashImageFile));
        ctx.drawImage(bountyCashImage, bountyCashOriginX, bountyCashOriginY, bountyCashImage.width, bountyCashImage.height);

        return canvas;
    }

    if (status === "claimed") {
        // When status is claimed
        // Setup styles
        var posterUserFontColor = DARK_GREY,
            claimedByFontColor = LIGHT_GREY;
        var posterUserFontSize = MEDIUM,
            claimedByFontSize = MEDIUM;

        //
        // Align elements
        //

        // Bounty claimed stamp
        var bountyClaimedStampOriginX = centerX - 124;
        var bountyClaimedStampOriginY = centerY - 137;

        // Sheriff icon
        var sheriffIconOriginX = centerX - 345;
        var sheriffIconOriginY = Math.floor((height - 323) / 2);

        // Poster user (align center)
        var posterUserOriginX = sheriffIconOriginX + 160;
        var posterUserOriginY = sheriffIconOriginY + 323;

        // Claimed by text
        var claimedByTextOriginX = centerX - 36;
        var claimedByTextOriginY = centerY + 98;
        var claimedByPersonOriginX = {
            0: claimedByTextOriginX + 142,
            1: claimedByTextOriginX + 142,
            2: claimedByTextOriginX + 142
        };
        var claimedByPersonOriginY = {
            0: claimedByTextOriginY,
            1: claimedByTextOriginY + 30,
            2: claimedByTextOriginY + 60
        };

        //
        // Draw elements
        //

        // Draw bounty claimed stamp
        var bountyClaimedStampImageFile = "bounty-claimed-stamp.png";
        var bountyClaimedStampImage = new Image();
        bountyClaimedStampImage.src = fs.readFileSync(RewardUtils.assetFile(bountyClaimedStampImageFile));
        ctx.drawImage(bountyClaimedStampImage, bountyClaimedStampOriginX, bountyClaimedStampOriginY, bountyClaimedStampImage.width, bountyClaimedStampImage.height);

        // Draw poster user
        ctx.textAlign = "center";
        ctx.fillStyle = posterUserFontColor;
        ctx.font = RewardUtils.canvasFontString(posterUserFontSize, futuraLT, "bold");
        var posterUser = formatter.getUserName();
        ctx.fillText(posterUser, posterUserOriginX, posterUserOriginY);
        ctx.textAlign = "left";

        // Draw claimed by text
        if (options.claimedBy) {
            ctx.font = RewardUtils.canvasFontString(claimedByFontSize, futuraLT, "bold");
            ctx.fillStyle = claimedByFontColor;
            var claimedByText = "Claimed by: ";
            ctx.fillText(claimedByText, claimedByTextOriginX, claimedByTextOriginY);

            // Only display two claimers
            var claimers = formatter.getClaimerStrings();
            claimers.forEach(function (element, index) {
                ctx.fillText(element, claimedByPersonOriginX[index], claimedByPersonOriginY[index]);
            });
        }

        // Draw sheriff icon
        var sheriffIconImageFile = "sheriff.png";
        var sheriffIconImage = new Image();
        sheriffIconImage.src = fs.readFileSync(RewardUtils.assetFile(sheriffIconImageFile));
        ctx.drawImage(sheriffIconImage, sheriffIconOriginX, sheriffIconOriginY, sheriffIconImage.width, sheriffIconImage.height);

        return canvas;
    }

    throw "Unknown bounty status";
};
<|endoftext|>


<commit_before>angular.module('app.FriendsFactory', [])
  .factory('FriendsFactory', friendsFactory);

function friendsFactory($http, apiUrl) {

  function getFriends() {
    return $http.get(apiUrl + '/friends')
      .then(function(friends) {
        return friends.data;
      });
  }

  function getUsers() {
    return $http.get(apiUrl + '/allUsers')
      .then(function(users) {
        return users.data;
      });
  }

  return {
    getFriends: getFriends,
    getUsers: getUsers
  };
}
<commit_msg>refactor friendsFactory, rename in camelcase<commit_after>angular.module('app.FriendsFactory', [])
  .factory('friendsFactory', friendsFactory);

function friendsFactory($http, apiUrl) {

  function getFriends() {
    return $http.get(apiUrl + '/friends')
      .then(sendData);
  }

  function getUsers() {
    return $http.get(apiUrl + '/allUsers')
      .then(sendData);
  }

  function sendData(result) {
    return result.data;
  }

  return {
    getFriends: getFriends,
    getUsers: getUsers
  };
}
<|endoftext|>


<commit_before>/**
 * https://github.com/chrisweb
 *
 * Copyright 2014 weber chris
 * Released under the MIT license
 * https://chris.lu
 */

/**
 *
 * base view
 *
 * @param {type} Backbone
 * @param {type} _
 * @param {type} $
 * @param {type} Container
 *
 * @returns {unresolved}
 */
define([
    'backbone',
    'underscore',
    'jquery',
    'ribs.container'

], function(Backbone, _, $, Container) {

    'use strict';

    var defaultOptions = {
        removeModelOnClose: true, // Boolean: If true, remove model from its collection on view close.
        listClass: 'list',
        templateVariables: {},
        ModelView: null,
        ModelViewOptions: {}
    };

    var View = Backbone.View.extend({

        initialize: function(options) {

            this.options = $.extend(defaultOptions, options || {}) ;

            // if oninitialize exists
            if (this.onInitializeStart) {

                // execute it now
                this.onInitializeStart();

            }

            // collection children views, usefull when collection view gets
            // destroyed and we want to take some action on sub views
            this.collectionModelViews = {};

            // list of reference view by model. Usefull to delete all view
            // reference when a model is remove from the collection
            this.referenceModelView = {};

            // check if a template has been defined
            /*if (typeof this.template === 'undefined') {

                throw 'missing template, you need to define a template in your view';

            }*/

            var renderedTemplate;

            if (this.model !== undefined) {

                var templateKeyValues;

                // are there also templateVariables
                if (_.keys(this.options.templateVariables).length > 0) {

                    templateKeyValues = $.extend(this.options.templateVariables, this.getModelAsJson());

                } else {

                    templateKeyValues = this.getModelAsJson();

                }

                renderedTemplate = this.template(templateKeyValues);

            } else if (_.keys(this.options.templateVariables).length > 0) {

                renderedTemplate = this.template(this.options.templateVariables);

            } else {

                renderedTemplate = this.template();

            }

            // sizzle of jquery will throw an error if the view content is
            // not encaplsulated in an html element, for example if the
            // content is just a text
            try {

                var $renderedTemplate = $(renderedTemplate);

            } catch(error) {

                throw 'The view template should have at least one root element and not more then one';

            }

            if ($renderedTemplate.length === 1) {

                var rootElement = $renderedTemplate.first().html('');

                this.setElement(rootElement);

            } else {

                // unfortunatly we have to ensure that the view template has
                // a root html element and that it only has one ... this is
                // because backbone views by design have an $el attribute
                // containing an element in which the template will be rendered
                throw 'The view template should have at least one root element and not more then one';

            }

            if (this.collection !== undefined) {

                this.listenTo(this.collection, 'add', this.addModel);
                this.listenTo(this.collection, 'remove', this.removeModel);
                this.listenTo(this.collection, 'reset', this.reset);

            }

            if (this.model !== undefined) {

                this.listenTo(this.model, 'destroy', this.close);

            }

            // if oninitialize exists
            if (this.onInitialize) {

                // execute it now
                this.onInitialize();

            }

        },
        render: function() {

            // if onRender exists
            if (this.onRenderStart) {

                // execute it now
                this.onRenderStart();

            }

            this.htmlize();

            // if onRender exists
            if (this.onRender) {

                // execute it now
                this.onRender();

            }

            return this;

        },
        htmlize: function() {

            var renderedTemplate;

             // is there a collection?
            if (this.collection !== undefined) {

                // and also a model or templateVariables or nothing?
                if (this.model !== undefined) {

                    var templateKeyValues;

                    // are there also templateVariables
                    if (_.keys(this.options.templateVariables).length > 0) {

                        templateKeyValues = $.extend(this.options.templateVariables, this.getModelAsJson());

                    } else {

                        templateKeyValues = this.getModelAsJson();

                    }

                    renderedTemplate = this.template(templateKeyValues);

                } else if (_.keys(this.options.templateVariables).length > 0) {

                    renderedTemplate = this.template(this.options.templateVariables);

                } else {

                    renderedTemplate = this.template();

                }

                // for each model of the collection append a modelView to
                // collection dom
                var that = this;

                if (this.collection.models.length > 0) {

                    if (that.options.ModelView !== null) {

                        var ModelView = that.options.ModelView;

                    } else {

                        throw 'a collection view needs a ModelView passed on instantiation through the options';

                    }

                    var modelViewsAsHtml = [];

                    _.each(this.collection.models, function(model) {

                        var mergedModelViewOptions = $.extend(this.options.ModelViewOptions, { model: model, parentView: that });

                        var modelView = new ModelView(mergedModelViewOptions);

                        that.collectionModelViews[model.cid] = modelView;

                        var $html = modelView.create();

                        that.referenceModelView[model.cid] = {$html:$html};

                        modelViewsAsHtml.push($html);

                    });

                    var $renderedTemplateCache = $(renderedTemplate);

                    if ($renderedTemplateCache.hasClass(this.options.listClass)) {

                        $renderedTemplateCache.append(modelViewsAsHtml);

                    } else {

                        $renderedTemplateCache.find('.' + this.options.listClass).append(modelViewsAsHtml);

                    }

                    // collection view
                    renderedTemplate = $renderedTemplateCache[0];

                }

            } else if (this.model !== undefined) {

                var templateKeyValues;

                // are there also templateVariables
                if (_.keys(this.options.templateVariables).length > 0) {

                    templateKeyValues = $.extend(this.options.templateVariables, this.getModelAsJson());

                } else {

                    templateKeyValues = this.getModelAsJson();

                }

                // model view
                renderedTemplate = this.template(templateKeyValues);

            } else if (_.keys(this.options.templateVariables).length > 0) {

                // templateVariables view
                renderedTemplate = this.template(this.options.templateVariables);

            } else {

                // basic view
                renderedTemplate = this.template();

            }

            this.setElement($(renderedTemplate));

        },
        getModelAsJson: function() {

            var data;

            if (this.model) {

                data = this.model.toJSON();

            }

            return data;

        },
        getCollectionAsJson: function() {

            var data;

            if (this.collection) {

                data = this.collection.toJSON();

            }

            return data;

        },
        close: function() {

            if (this.collectionModelViews !== null) {

                _.each(this.collectionModelViews, function closeModelViews(modelView) {

                    modelView.close();

                });

                this.collectionModelViews = {};

            }

            // remove the view from dom and stop listening to events that were
            // added with listenTo or that were added to the events declaration
            this.remove();

            // unbind events triggered from within views using backbone events
            this.unbind();

            if (this.model !== undefined && this.options) {

                if (this.options.removeModelOnClose === true && !!this.model.collection === true) {

                    this.model.collection.remove(this.model);

                }

            }

            if (this.collection !== null) {

                // TODO: ...

            }

            // if there is a onClose function ...
            if (this.onClose) {

                // execute it now
                this.onClose();

            }

        },
        create: function() {

            return this.render().$el;

        },
        clear: function() {

            Container.clear(this.options.listClass);

            this.referenceModelView = {};

        },
        empty: function() {

            //Container.clear(this.options.listClass);

            if (this.collectionModelViews !== null) {

                _.each(this.collectionModelViews, function closeModelViews(modelView) {

                    modelView.close();

                });

                this.collectionModelViews = {};

            }

            this.referenceModelView = {};

        },
        reset: function(collection) {

            this.empty();

            _.each(collection.models, (function(newModel) {

                this.addModel(newModel);

            }).bind(this));

        },
        addModel: function(model) {

            if (this.options.ModelView !== null) {

                var ModelView = this.options.ModelView;

            } else {

                throw 'a collection view needs a ModelView passed on instantiation through the options';

            }

            var mergedModelViewOptions = $.extend(this.options.ModelViewOptions, { model: model, parentView: this });

            var modelView = new ModelView(mergedModelViewOptions);

            var $element = modelView.create();

            this.referenceModelView[model.cid] = {$html:$element, container: modelView};

            var $container = this.$el.find('.' + this.options.listClass);

            if ($container.size() > 0) {

                $container.append($element);

            } else if (($container = this.$el.filter('.' + this.options.listClass)).size()) {

                $container.append($element);

            }

            // TODO: use the container to manage subviews of a list
            //Container.add('.' + this.options.listClass, modelView);

            this.collectionModelViews[model.cid] = modelView;

        },
        removeModel: function removeModelFunction(model) {

            var view = this.referenceModelView[model.cid];

            if (view === undefined) {
                return view;
            }

            if (view.container !== undefined) {

                // TODO: use the container to manage subviews of a list
                //Container.remove('.' + this.options.listClass, view.container);

                view.container.close();

                //TODO: close ?

            }

            if (view.$html !== undefined) {

                view.$html.remove();

            }

            delete this.referenceModelView[model.cid];

            delete this.collectionModelViews[model.cid];

            return view;

        }

    });

    return View;

});
<commit_msg>added onCloseStart hook<commit_after>/**
 * https://github.com/chrisweb
 *
 * Copyright 2014 weber chris
 * Released under the MIT license
 * https://chris.lu
 */

/**
 *
 * base view
 *
 * @param {type} Backbone
 * @param {type} _
 * @param {type} $
 * @param {type} Container
 *
 * @returns {unresolved}
 */
define([
    'backbone',
    'underscore',
    'jquery',
    'ribs.container'

], function(Backbone, _, $, Container) {

    'use strict';

    var defaultOptions = {
        removeModelOnClose: true, // Boolean: If true, remove model from its collection on view close.
        listClass: 'list',
        templateVariables: {},
        ModelView: null,
        ModelViewOptions: {}
    };

    var View = Backbone.View.extend({

        initialize: function(options) {

            this.options = $.extend(defaultOptions, options || {}) ;

            // if oninitialize exists
            if (this.onInitializeStart) {

                // execute it now
                this.onInitializeStart();

            }

            // collection children views, usefull when collection view gets
            // destroyed and we want to take some action on sub views
            this.collectionModelViews = {};

            // list of reference view by model. Usefull to delete all view
            // reference when a model is remove from the collection
            this.referenceModelView = {};

            // check if a template has been defined
            /*if (typeof this.template === 'undefined') {

                throw 'missing template, you need to define a template in your view';

            }*/

            var renderedTemplate;

            if (this.model !== undefined) {

                var templateKeyValues;

                // are there also templateVariables
                if (_.keys(this.options.templateVariables).length > 0) {

                    templateKeyValues = $.extend(this.options.templateVariables, this.getModelAsJson());

                } else {

                    templateKeyValues = this.getModelAsJson();

                }

                renderedTemplate = this.template(templateKeyValues);

            } else if (_.keys(this.options.templateVariables).length > 0) {

                renderedTemplate = this.template(this.options.templateVariables);

            } else {

                renderedTemplate = this.template();

            }

            // sizzle of jquery will throw an error if the view content is
            // not encaplsulated in an html element, for example if the
            // content is just a text
            try {

                var $renderedTemplate = $(renderedTemplate);

            } catch(error) {

                throw 'The view template should have at least one root element and not more then one';

            }

            if ($renderedTemplate.length === 1) {

                var rootElement = $renderedTemplate.first().html('');

                this.setElement(rootElement);

            } else {

                // unfortunatly we have to ensure that the view template has
                // a root html element and that it only has one ... this is
                // because backbone views by design have an $el attribute
                // containing an element in which the template will be rendered
                throw 'The view template should have at least one root element and not more then one';

            }

            if (this.collection !== undefined) {

                this.listenTo(this.collection, 'add', this.addModel);
                this.listenTo(this.collection, 'remove', this.removeModel);
                this.listenTo(this.collection, 'reset', this.reset);

            }

            if (this.model !== undefined) {

                this.listenTo(this.model, 'destroy', this.close);

            }

            // if oninitialize exists
            if (this.onInitialize) {

                // execute it now
                this.onInitialize();

            }

        },
        render: function() {

            // if onRender exists
            if (this.onRenderStart) {

                // execute it now
                this.onRenderStart();

            }

            this.htmlize();

            // if onRender exists
            if (this.onRender) {

                // execute it now
                this.onRender();

            }

            return this;

        },
        htmlize: function() {

            var renderedTemplate;

             // is there a collection?
            if (this.collection !== undefined) {

                // and also a model or templateVariables or nothing?
                if (this.model !== undefined) {

                    var templateKeyValues;

                    // are there also templateVariables
                    if (_.keys(this.options.templateVariables).length > 0) {

                        templateKeyValues = $.extend(this.options.templateVariables, this.getModelAsJson());

                    } else {

                        templateKeyValues = this.getModelAsJson();

                    }

                    renderedTemplate = this.template(templateKeyValues);

                } else if (_.keys(this.options.templateVariables).length > 0) {

                    renderedTemplate = this.template(this.options.templateVariables);

                } else {

                    renderedTemplate = this.template();

                }

                // for each model of the collection append a modelView to
                // collection dom
                var that = this;

                if (this.collection.models.length > 0) {

                    if (that.options.ModelView !== null) {

                        var ModelView = that.options.ModelView;

                    } else {

                        throw 'a collection view needs a ModelView passed on instantiation through the options';

                    }

                    var modelViewsAsHtml = [];

                    _.each(this.collection.models, function(model) {

                        var mergedModelViewOptions = $.extend(this.options.ModelViewOptions, { model: model, parentView: that });

                        var modelView = new ModelView(mergedModelViewOptions);

                        that.collectionModelViews[model.cid] = modelView;

                        var $html = modelView.create();

                        that.referenceModelView[model.cid] = {$html:$html};

                        modelViewsAsHtml.push($html);

                    });

                    var $renderedTemplateCache = $(renderedTemplate);

                    if ($renderedTemplateCache.hasClass(this.options.listClass)) {

                        $renderedTemplateCache.append(modelViewsAsHtml);

                    } else {

                        $renderedTemplateCache.find('.' + this.options.listClass).append(modelViewsAsHtml);

                    }

                    // collection view
                    renderedTemplate = $renderedTemplateCache[0];

                }

            } else if (this.model !== undefined) {

                var templateKeyValues;

                // are there also templateVariables
                if (_.keys(this.options.templateVariables).length > 0) {

                    templateKeyValues = $.extend(this.options.templateVariables, this.getModelAsJson());

                } else {

                    templateKeyValues = this.getModelAsJson();

                }

                // model view
                renderedTemplate = this.template(templateKeyValues);

            } else if (_.keys(this.options.templateVariables).length > 0) {

                // templateVariables view
                renderedTemplate = this.template(this.options.templateVariables);

            } else {

                // basic view
                renderedTemplate = this.template();

            }

            this.setElement($(renderedTemplate));

        },
        getModelAsJson: function() {

            var data;

            if (this.model) {

                data = this.model.toJSON();

            }

            return data;

        },
        getCollectionAsJson: function() {

            var data;

            if (this.collection) {

                data = this.collection.toJSON();

            }

            return data;

        },
        close: function() {

            if (this.onCloseStart) {

                this.onCloseStart();

            }

            if (this.collectionModelViews !== null) {

                _.each(this.collectionModelViews, function closeModelViews(modelView) {

                    modelView.close();

                });

                this.collectionModelViews = {};

            }

            // remove the view from dom and stop listening to events that were
            // added with listenTo or that were added to the events declaration
            this.remove();

            // unbind events triggered from within views using backbone events
            this.unbind();

            if (this.model !== undefined && this.options) {

                if (this.options.removeModelOnClose === true && !!this.model.collection === true) {

                    this.model.collection.remove(this.model);

                }

            }

            if (this.collection !== null) {

                // TODO: ...

            }

            // if there is a onClose function ...
            if (this.onClose) {

                // execute it now
                this.onClose();

            }

        },
        create: function() {

            return this.render().$el;

        },
        clear: function() {

            Container.clear(this.options.listClass);

            this.referenceModelView = {};

        },
        empty: function() {

            //Container.clear(this.options.listClass);

            if (this.collectionModelViews !== null) {

                _.each(this.collectionModelViews, function closeModelViews(modelView) {

                    modelView.close();

                });

                this.collectionModelViews = {};

            }

            this.referenceModelView = {};

        },
        reset: function(collection) {

            this.empty();

            _.each(collection.models, (function(newModel) {

                this.addModel(newModel);

            }).bind(this));

        },
        addModel: function(model) {

            if (this.options.ModelView !== null) {

                var ModelView = this.options.ModelView;

            } else {

                throw 'a collection view needs a ModelView passed on instantiation through the options';

            }

            var mergedModelViewOptions = $.extend(this.options.ModelViewOptions, { model: model, parentView: this });

            var modelView = new ModelView(mergedModelViewOptions);

            var $element = modelView.create();

            this.referenceModelView[model.cid] = {$html:$element, container: modelView};

            var $container = this.$el.find('.' + this.options.listClass);

            if ($container.size() > 0) {

                $container.append($element);

            } else if (($container = this.$el.filter('.' + this.options.listClass)).size()) {

                $container.append($element);

            }

            // TODO: use the container to manage subviews of a list
            //Container.add('.' + this.options.listClass, modelView);

            this.collectionModelViews[model.cid] = modelView;

        },
        removeModel: function removeModelFunction(model) {

            var view = this.referenceModelView[model.cid];

            if (view === undefined) {
                return view;
            }

            if (view.container !== undefined) {

                // TODO: use the container to manage subviews of a list
                //Container.remove('.' + this.options.listClass, view.container);

                view.container.close();

                //TODO: close ?

            }

            if (view.$html !== undefined) {

                view.$html.remove();

            }

            delete this.referenceModelView[model.cid];

            delete this.collectionModelViews[model.cid];

            return view;

        }

    });

    return View;

});
<|endoftext|>


<commit_before>var constants = {
  canvasWidth : 1000,
  canvasHeight : 200
};

function View(game_state, board_type) {
  this.game = game_state;
  this.tileViews = [];
  this.tokenViews = [];

  this.renderer = PIXI.autoDetectRenderer(constants.canvasWidth, constants.canvasHeight,
                                          {backgroundColor : 0x1099bb});

  document.body.appendChild(this.renderer.view);

  // create the root of the scene graph
  this.stage = new PIXI.Container();
};

View.prototype.drawGridBoard = function() {
  var y_pos = 0;
  var x_pos = 0;
  if (!this.board.spaces[0]) return;
  for (var x = 0; x < this.board.spaces[0].length; x++) {
    x_pos = 0;
    for (var y = 0; y < this.board.spaces.length; y++) {
      var tileView = new PIXI.Graphics();
      var tile = this.board.spaces[x, y];
      tileView.lineStyle(1, 0, 1);
      tileView.drawRect(x_pos, y_pos, constants.canvasWidth / this.game.board.spaces.length, constants.canvasHeight);
      x_pos += constants.canvasWidth / this.game.board.spaces.length;
      tileView.beginFill(tile.color, 1);
      this.tileViews.push(tileView);
      this.stage.addChild(tileView);
    }
  }

  this.drawTokens();
  this.drawMessage();
  this.animate();
};

// todo
View.prototype.drawPathBoard = function() {

};

// todo
View.prototype.drawGraphBoard = function() {

};

View.prototype.drawToken = function(token) {

  // draw the token
  var tokenView = new PIXI.Graphics();
  tokenView.lineStyle(1, 0, 1);
  tokenView.beginFill(token.color, 1);

  // put it on the tile
  var tile = this.tileViews[token.pos.x, token.pos.y];
  // todo: shape of token
  tokenView.drawRect(10, 10, 20, 20);
  tile.addChild(tokenView);
  this.tokenViews.push({token: tokenView, tile: tile});
};

View.prototype.drawTokens = function() {
  for (var playerIdx in this.game.players) {
    var player = this.game.players[playerIdx];
    for (var tokenIdx in player.tokens) {
      var token = player.tokens[tokenIdx];
      if (!token.isDead) {
        this.updateToken(token.pos);
      }
    }
  }
};

View.prototype.updateToken = function(token) {
  var tokenView =
  var token = playerView.token;

  // remove the token from the previous tile
  var previousTile = playerView.tile;
  previousTile.removeChild(token);

  // add the token as a child to the new tile
  var currentTile = this.tileViews[position];
  currentTile.addChild(token);
  this.tokenViews[index] = {token: token, tile: currentTile};

  // calculate an offset for the token if there are multiple
  token.x = ((constants.canvasWidth / this.game.board.spaces.length) * position);
};

View.prototype.updateTokens = function() {
  for (var playerIdx in this.game.players) {
    var player = this.game.players[playerIdx];
    for (var tokenIdx in player.tokens) {
      var token = player.tokens[tokenIdx];
      if (!token.isDead) {
        this.updateToken(token.pos);
      }
    }
  }
};

View.prototype.drawMessage = function() {
  var container = new PIXI.Container();

  var button1 = new PIXI.Graphics();
  button1.y = 160;
  button1.beginFill(0x00FF00, 1);
  button1.drawRect(0, 0, 200, 40);
  container.addChild(button1);

  button1.interactive = true;
  var context = this;
  button1.click = function(mouseData){
    console.log("CLICK!");
    context.game.updateState(true);
  };

  this.button1Text = new PIXI.Text("Roll", {font: '30px Arial',
                                            align : 'center',
                                            wordWrap : true,
                                            strokeThickness : .25,
                                            wordWrapWidth : 150
                                           });
  this.button1Text.x = 50;
  button1.addChild(this.button1Text);
  this.stage.addChild(container);
};

View.prototype.animate = function() {
  this.updateTokens();
  requestAnimationFrame(this.animate.bind(this));
  this.renderer.render(this.stage);
};

module.exports = View;

<commit_msg>adding buggy abstracted view<commit_after>var c = require("ttConstants.js");
var GridBoard = require("gridboard");

function View(game, turnMap) {
  this.game = game;
  this.turnMap = turnMap;
  this.turnMap.updateState("start");
  this.tileViews = [];
  if (game.board instanceof GridBoard) {
    for (var i = 0; i < this.game.board.height, i++) {
      this.tileViews[i] = [];
    }
  }

  this.tokenViews = [];

  this.renderer = PIXI.autoDetectRenderer(c.canvasWidth, c.canvasHeight,
                                          {backgroundColor : 0x1099bb});

  document.body.appendChild(this.renderer.view);

  // create the root of the scene graph
  this.stage = new PIXI.Container();
};

View.prototype.drawGridBoard = function() {

  var board = new PIXI.Graphics();
  //board.lineStyle(1, 0x000000, 1);
  board.beginFill(0xC2E2BF, 1);
  board.drawRect(c.boardStartX, c.boardStartY, c.boardWidth, c.boardHeight);
  this.stage.addChild(board);

  var y_pos = 0;
  var x_pos = 0;
  if (!this.board.spaces[0]) return;
  for (var x = 0; x < this.board.spaces[0].length; x++) {
    x_pos = 0;
    for (var y = 0; y < this.board.spaces.length; y++) {
      var tileView = new PIXI.Graphics();
      var tile = this.board.getSpace(x, y);
      tileView.lineStyle(1, 0, 1);
      tileView.drawRect(x_pos, y_pos, c.canvasWidth / this.game.board.spaces.length, c.canvasHeight);
      x_pos += c.canvasWidth / this.game.board.spaces.length;
      tileView.beginFill(tile.color, 1);
      this.tileViews[x][y] = tileView;
      this.stage.addChild(tileView);
    }
  }

  this.drawTokens();
  this.drawMessage();
  this.animate();
};

// todo
View.prototype.drawPathBoard = function() {

};

// todo
View.prototype.drawGraphBoard = function() {

};

View.prototype.drawToken = function(token) {

  // draw the token
  var tokenView = new PIXI.Graphics();
  tokenView.lineStyle(1, 0, 1);
  tokenView.beginFill(token.color, 1);

  // put it on the tile
  var tileView = this.tileViews[token.pos.x, token.pos.y];
  tokenView.drawRect(10, 10, 20, 20); // todo: other shapes
  tileView.addChild(tokenView);

  this.tokenViews.push({tokenView: tokenView, tileView: tileView});
};

View.prototype.drawTokens = function() {
  for (var playerIdx in this.game.players) {
    var player = this.game.players[playerIdx];
    for (var tokenIdx in player.tokens) {
      var token = player.tokens[tokenIdx];
      if (!token.isDead) {
        this.updateToken(token.pos);
      }
    }
  }
};

View.prototype.updateToken = function(token) {
  var playerView =
  var token = playerView.token;

  // remove the token from the previous tile
  var previousTile = playerView.tile;
  previousTile.removeChild(token);

  // add the token as a child to the new tile
  var currentTile = this.tileViews[position];
  currentTile.addChild(token);
  this.tokenViews[index] = {token: token, tile: currentTile};

  // calculate an offset for the token if there are multiple
  token.x = ((c.canvasWidth / this.game.board.spaces.length) * position);
};

View.prototype.updateTokens = function() {
  for (var playerIdx in this.game.players) {
    var player = this.game.players[playerIdx];
    for (var tokenIdx in player.tokens) {
      var token = player.tokens[tokenIdx];
      if (!token.isDead) {
        this.updateToken(token.pos);
      }
    }
  }
};

View.prototype.removeToken = function() {

};

View.prototype.drawMessage = function() {
  var container = new PIXI.Container();

  var button1 = new PIXI.Graphics();
  button1.y = 160;
  button1.beginFill(0x00FF00, 1);
  button1.drawRect(0, 0, 200, 40);
  container.addChild(button1);

  button1.interactive = true;
  var context = this;
  button1.click = function(mouseData){
    console.log("CLICK!");
    context.game.updateState(true);
  };

  this.button1Text = new PIXI.Text("Roll", {font: '30px Arial',
                                            align : 'center',
                                            wordWrap : true,
                                            strokeThickness : .25,
                                            wordWrapWidth : 150
                                           });
  this.button1Text.x = 50;
  button1.addChild(this.button1Text);
  this.stage.addChild(container);
};

View.prototype.animate = function() {
  this.updateTokens();
  requestAnimationFrame(this.animate.bind(this));
  this.renderer.render(this.stage);
};

module.exports = View;

<|endoftext|>


<commit_before>/**
 * Calculates value-over-replacement for players by position
 *
 * Inputs:
 *   player names & estimated points
 *   position slots per fantasy team roster
 *   number of fantasy teams in the league
 * Output:
 *   Decorated input object with VORP added to each player
 */
(function(document, window, undefined) {
 'use strict';

window.vorp = function(pointEstimates, draftRoster, numTeams) {
    var POSITIONS = ["QB", "RB", "WR", "TE", "K", "DST"]

    /**
     * Calculates replacement value by estimating demand for each
     * position, and then averaging the projected value of the next
     * 10% of available players at the position
     */
    , getReplacementValue = function(position, inputEstimates) {
        var teamDemand = getTeamDemand(position)
        , totalDemand = teamDemand * numTeams

        , players = inputEstimates[position] || []
        , replacementStart = Math.min(Math.ceil(totalDemand),
                                      players.length - 1)
        , replacementRange = players.length * 0.1
        , replacementStop = replacementStart + replacementRange
        , replacementPlayers = players.slice(replacementStart, replacementStop)

        , replacementValue = averagePoints(replacementPlayers)
        ;
        return replacementValue;
    }

    /**
     * Calculates demand for starters @ a position. Assign equal demand for all
     * positions in a flex slot.
     */
    , getTeamDemand = function(position) {
        var demand = 0;
        $.each(draftRoster, function(_, slot) {
            var flexOptions = slot.split("/").length
            ;
            if (slot.indexOf(position) != -1) {
                demand += (1 / flexOptions);
            }
        });
        return demand;
    }

    /**
     * Returns a map of position -> replacement value
     */
    , calculateReplacementValues = function(inputEstimates) {
        var replacementValues = {};
        $.each(POSITIONS, function(_, pos) {
            replacementValues[pos] = getReplacementValue(pos, inputEstimates);
        });
        return replacementValues
    }

    , cleanInput = function(input) {
        var estimates = JSON.parse(JSON.stringify(input)) // :-P
        ;
        // convert player points to a float
        forEveryPlayer(estimates, function(player) {
            var pointsStr = player.points
            , pointsFloat = parseFloat(pointsStr)
            ;
            player.points = pointsFloat;
        });
        // now sort all the lists by point total, descending
        $.each(estimates, function(position, playerList) {
            playerList.sort(function(a,b) { return b.points - a.points; });
        });
        return estimates;
    }

    //
    // Now do the real work. Clean the input, calculate replacement values
    // and return a decorated version of the input object with vorp
    //
    , inputEstimates = cleanInput(pointEstimates)
    , replacementValues = calculateReplacementValues(inputEstimates)
    ;

    console.log("running VORP for " + numTeams + " teams, roster: " + draftRoster);
    console.log("replacementValues: " + JSON.stringify(replacementValues));

    forEveryPlayer(inputEstimates, function(player, pos_rank) {
        player.vorp = (player.points - replacementValues[player.pos]).toFixed(2);
        player.pos_rank = pos_rank + 1;
    });

    return inputEstimates;
};

})(document, window);
<commit_msg>minor cleanup to replacement values, made calculations more aggressive after last night's clown draft<commit_after>/**
 * Calculates value-over-replacement for players by position
 *
 * Inputs:
 *   player names & estimated points
 *   position slots per fantasy team roster
 *   number of fantasy teams in the league
 * Output:
 *   Decorated input object with VORP added to each player
 */
(function(document, window, undefined) {
 'use strict';

window.vorp = function(pointEstimates, draftRoster, numTeams) {
    var POSITIONS = ["QB", "RB", "WR", "TE", "K", "DST"]
    , REPLACEMENT_RANGE = 3

    /**
     * Calculates replacement value by estimating demand for each
     * position, and then averaging the projected value of the
     * next three available players at the position
     */
    , getReplacementValue = function(position, inputEstimates) {
        var teamDemand = getTeamDemand(position)
        , totalDemand = teamDemand * numTeams

        , players = inputEstimates[position] || []
        , replacementStart = Math.min(Math.ceil(totalDemand),
                                      players.length - 1)
        , replacementStop = replacementStart + REPLACEMENT_RANGE
        , replacementPlayers = players.slice(replacementStart, replacementStop)

        , replacementValue = averagePoints(replacementPlayers)
        ;
        log(position + " replacement range " + replacementStart + " - " + replacementStop);
        return replacementValue.toFixed(2);
    }

    /**
     * Calculates demand for starters @ a position. Assign equal demand for all
     * positions in a flex slot.
     */
    , getTeamDemand = function(position) {
        var demand = 0;
        $.each(draftRoster, function(_, slot) {
            var flexOptions = slot.split("/").length
            ;
            if (slot.indexOf(position) != -1) {
                demand += (1 / flexOptions);
            }
        });
        return demand;
    }

    /**
     * Returns a map of position -> replacement value
     */
    , calculateReplacementValues = function(inputEstimates) {
        var replacementValues = {};
        $.each(POSITIONS, function(_, pos) {
            replacementValues[pos] = getReplacementValue(pos, inputEstimates);
        });
        return replacementValues
    }

    , cleanInput = function(input) {
        var estimates = JSON.parse(JSON.stringify(input)) // :-P
        ;
        // convert player points to a float
        forEveryPlayer(estimates, function(player) {
            var pointsStr = player.points
            , pointsFloat = parseFloat(pointsStr)
            ;
            player.points = pointsFloat;
        });
        // now sort all the lists by point total, descending
        $.each(estimates, function(position, playerList) {
            playerList.sort(function(a,b) { return b.points - a.points; });
        });
        return estimates;
    }

    //
    // Now do the real work. Clean the input, calculate replacement values
    // and return a decorated version of the input object with vorp
    //
    , inputEstimates = cleanInput(pointEstimates)
    , replacementValues = calculateReplacementValues(inputEstimates)
    ;

    log("running VORP for " + numTeams + " teams, roster: " + draftRoster);
    log("replacementValues: " + JSON.stringify(replacementValues));

    forEveryPlayer(inputEstimates, function(player, pos_rank) {
        player.vorp = player.points - replacementValues[player.pos];
        player.pos_rank = pos_rank + 1;
    });

    return inputEstimates;
};

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


<commit_before>
var map = new maptalks.Map('map', {
  center: [121.48542888885189, 31.228541533313702],
  zoom: 14,
  baseLayer: new maptalks.TileLayer('base', {
    urlTemplate: '$(urlTemplate)',
    subdomains: $(subdomains)
  })
});

var marker = new maptalks.Marker([121.487542, 31.225812], {
  draggable: true,
  dragShadow: false
});
var polyline = new maptalks.LineString([
  [121.471234, 31.211879], [121.493355, 31.221321]
], {
  draggable: true,
  dragShadow: true,
  draggableAxis: 'x',
  symbol: {
    lineColor: 'orange',
    lineWidth: 2
  }
});
var polygon = new maptalks.Polygon([
  [121.468765, 31.243709],
  [121.483355, 31.242659],
  [121.483355, 31.223344],
  [121.478332, 31.220102],
  [121.468321, 31.234567]
], {
  draggable: true,
  dragShadow: true,
  symbol: {
    lineColor: 'ForestGreen',
    lineWidth: 2,
    polygonFill: '#abc',
    polygonOpacity: 0.7
  }
});

var geometries = [marker, polyline, polygon];

var layer = new maptalks.VectorLayer('vector')
    .addGeometry(geometries)
    .addTo(map);

var actionBar = new maptalks.control.Toolbar({
  items: [
    {
      item: 'Try drag them'
    }
  ]
}).addTo(map);
<commit_msg>change to dragOnAxis<commit_after>
var map = new maptalks.Map('map', {
  center: [121.48542888885189, 31.228541533313702],
  zoom: 14,
  baseLayer: new maptalks.TileLayer('base', {
    urlTemplate: '$(urlTemplate)',
    subdomains: $(subdomains)
  })
});

var marker = new maptalks.Marker([121.487542, 31.225812], {
  draggable: true,
  dragShadow: false
});
var polyline = new maptalks.LineString([
  [121.471234, 31.211879], [121.493355, 31.221321]
], {
  draggable: true,
  dragShadow: true,
  dragOnAxis: 'x',
  symbol: {
    lineColor: 'orange',
    lineWidth: 2
  }
});
var polygon = new maptalks.Polygon([
  [121.468765, 31.243709],
  [121.483355, 31.242659],
  [121.483355, 31.223344],
  [121.478332, 31.220102],
  [121.468321, 31.234567]
], {
  draggable: true,
  dragShadow: true,
  symbol: {
    lineColor: 'ForestGreen',
    lineWidth: 2,
    polygonFill: '#abc',
    polygonOpacity: 0.7
  }
});

var geometries = [marker, polyline, polygon];

var layer = new maptalks.VectorLayer('vector')
    .addGeometry(geometries)
    .addTo(map);

var actionBar = new maptalks.control.Toolbar({
  items: [
    {
      item: 'Try drag them'
    }
  ]
}).addTo(map);
<|endoftext|>


<commit_before>import {Activator} from 'widget/activation/activator'
import {Button} from 'widget/button'
import {Msg, msg} from 'translate'
import {NoData} from 'widget/noData'
import {Panel} from 'widget/panel/panel'
import {SuperButton} from 'widget/superButton'
import {activatable} from 'widget/activation/activatable'
import {compose} from 'compose'
import {connect} from 'store'
import {v4 as uuid} from 'uuid'
import Markdown from 'react-markdown'
import Notifications from 'widget/notifications'
import PropTypes from 'prop-types'
import React from 'react'
import UserMessage from './userMessage'
import _ from 'lodash'
import actionBuilder from 'action-builder'
import api from 'api'
import moment from 'moment'
import styles from './userMessages.module.css'

const mapStateToProps = state => {
    const currentUser = state.user.currentUser
    return {
        isAdmin: currentUser.roles && currentUser.roles.includes('application_admin'),
        userMessages: state.user.userMessages
    }
}

export const showUserMessages = () =>
    actionBuilder('USER_MESSAGES')
        .set('ui.userMessages', 'SHOW_MESSAGES')
        .dispatch()

export const closePanel = () =>
    actionBuilder('USER_MESSAGES')
        .del('ui.userMessages')
        .dispatch()

class _UserMessages extends React.Component {
    state = {
        selectedMessage: null
    }

    updateMessage(message) {
        const {id = uuid()} = message
        this.props.stream('REQUEST_UPDATE_USER_MESSAGE',
            api.user.updateMessage$({...message, id}),
            message => {
                actionBuilder('UPDATE_USER_MESSAGE')
                    .assign(['user.userMessages', {message: {id}}], {message, state: 'UNREAD'})
                    .dispatch()
                Notifications.success({message: id ? msg('userMessage.update.success') : msg('userMessage.publish.success')})
            },
            error => Notifications.error({message: msg('userMessage.update.error'), error})
        )
        this.editMessage(null)
    }

    removeMessage(message) {
        const {id} = message
        this.props.stream('REQUEST_REMOVE_USER_MESSAGE',
            api.user.removeMessage$(message),
            () => {
                actionBuilder('REMOVE_USER_MESSAGE')
                    .del(['user.userMessages', {message: {id}}])
                    .dispatch()
                Notifications.success({message: msg('userMessage.remove.success')})
            },
            error => Notifications.error({message: msg('userMessage.remove.error'), error})
        )
    }

    updateUserMessage(userMessage) {
        const id = userMessage.message.id
        const state = userMessage.state
        this.props.stream('REQUEST_UPDATE_USER_MESSAGE_STATE',
            api.user.updateMessageState$(userMessage),
            () => {
                actionBuilder('UPDATE_USER_MESSAGE_STATE', {id, state})
                    .assign(['user.userMessages', {message: {id}}], {state})
                    .dispatch()
                // Notifications.success({message: msg('userMessage.updateState.success')})
            },
            error => Notifications.error({message: msg('userMessage.updateState.error'), error})
        )
    }

    buttonHandler() {
        const {modal} = this.props
        !modal && showUserMessages()
    }

    readState(read) {
        return read
            ? 'READ'
            : 'UNREAD'
    }

    setReadState(userMessage) {
        this.updateUserMessage({
            ...userMessage,
            state: 'READ'
        })
    }

    toggleReadState(userMessage) {
        const nextState = state => {
            switch(state) {
            case 'READ':
                return 'UNREAD'
            case 'UNREAD':
                return 'READ'
            default:
                throw Error(`Unsupported message state "${state}"`)
            }
        }
        this.updateUserMessage({
            ...userMessage,
            state: nextState(userMessage.state)
        })
    }

    newMessage() {
        this.editMessage({})
    }

    editMessage(userMessage) {
        this.setState({
            selectedMessage: userMessage
        })
    }

    renderMessages() {
        const {userMessages} = this.props
        if (userMessages.length) {
            const sortedUserMessages = _.orderBy(userMessages, userMessage => moment(userMessage.message.creationTime) || moment(), 'desc')
            return (
                sortedUserMessages.map((userMessage, index) => this.renderMessage(userMessage, index))
            )
        } else {
            return (
                <NoData message={msg('userMessages.noMessages')}/>
            )
        }
    }

    renderStatusButton(userMessage) {
        const {state} = userMessage
        return (
            <Button
                key='status'
                chromeless
                shape='circle'
                size='large'
                icon={state === 'UNREAD' ? 'bell' : 'check'}
                additionalClassName={[styles.subject, styles[state]].join(' ')}
                onClick={() => this.toggleReadState(userMessage)}
                tooltip={msg(`userMessages.state.${state}`)}
                tooltipPlacement='top'
            />
        )
    }

    renderMessage(userMessage, index) {
        const {isAdmin} = this.props
        const message = userMessage.message
        const author = userMessage.message.username
        const creationTime = userMessage.message.creationTime
        return (
            <SuperButton
                key={index}
                title={<Msg id='userMessages.author' author={author}/>}
                description={userMessage.message.subject}
                timestamp={creationTime}
                editTooltip={msg('userMessages.edit')}
                removeMessage={msg('userMessages.removeConfirmation', {subject: message.subject})}
                removeTooltip={msg('userMessages.remove')}
                onExpandDelayed={() => this.setReadState(userMessage, 'READ')}
                onEdit={isAdmin ? () => this.editMessage(message) : null}
                onRemove={isAdmin ? () => this.removeMessage(message) : null}
                extraButtons={[
                    this.renderStatusButton(userMessage)
                ]}
                clickToExpand
            >
                <Markdown className={styles.contents} source={userMessage.message.contents}/>
            </SuperButton>
        )
    }

    renderMessagesPanel() {
        const {isAdmin, activatable: {deactivate}} = this.props
        const close = () => deactivate()
        const add = () => this.newMessage()
        return (
            <Panel
                className={styles.panel}
                type='modal'>
                <Panel.Header
                    icon='bell'
                    title={msg('userMessages.title')}/>
                <Panel.Content>
                    {this.renderMessages()}
                </Panel.Content>
                <Panel.Buttons onEnter={close} onEscape={close}>
                    <Panel.Buttons.Main>
                        <Panel.Buttons.Close onClick={close}/>
                    </Panel.Buttons.Main>
                    <Panel.Buttons.Extra>
                        <Panel.Buttons.Add
                            label={msg('userMessages.post')}
                            icon='pencil-alt'
                            onClick={add}
                            shown={isAdmin}/>
                    </Panel.Buttons.Extra>
                </Panel.Buttons>
            </Panel>
        )
    }

    renderMessagePanel(message) {
        return (
            <UserMessage
                message={message}
                onApply={message => this.updateMessage(message)}
                onCancel={() => this.editMessage()}/>
        )
    }

    render() {
        const {selectedMessage} = this.state
        return selectedMessage
            ? this.renderMessagePanel(selectedMessage)
            : this.renderMessagesPanel()
    }
}

const policy = () => ({_: 'disallow'})

const UserMessages = compose(
    _UserMessages,
    connect(mapStateToProps),
    activatable({id: 'userMessages', policy, alwaysAllow: true})
)

UserMessages.propTypes = {
    className: PropTypes.string
}

const _UserMessagesButton = ({className, userMessages}) => {
    const unread = _.filter(userMessages, {state: 'UNREAD'}).length
    return (
        <React.Fragment>
            <Activator id='userMessages'>
                {({active, activate}) =>
                    <Button
                        chromeless
                        look='transparent'
                        size='large'
                        air='less'
                        additionalClassName={[className, unread ? styles.unread : null].join(' ')}
                        icon='bell'
                        disabled={active}
                        onClick={() => activate()}
                        tooltip={msg('home.sections.user.messages')}
                        tooltipPlacement='top'
                        tooltipDisabled={active}/>
                }
            </Activator>
            <UserMessages/>
        </React.Fragment>
    )
}

export const UserMessagesButton = compose(
    _UserMessagesButton,
    connect(state => ({
        userMessages: state.user.userMessages
    }))
)
<commit_msg>Implemented UserMessages auto-popup when there are unread messages.<commit_after>import {Activator, activator} from 'widget/activation/activator'
import {Button} from 'widget/button'
import {Msg, msg} from 'translate'
import {NoData} from 'widget/noData'
import {Panel} from 'widget/panel/panel'
import {SuperButton} from 'widget/superButton'
import {activatable} from 'widget/activation/activatable'
import {compose} from 'compose'
import {connect} from 'store'
import {v4 as uuid} from 'uuid'
import Markdown from 'react-markdown'
import Notifications from 'widget/notifications'
import PropTypes from 'prop-types'
import React from 'react'
import UserMessage from './userMessage'
import _ from 'lodash'
import actionBuilder from 'action-builder'
import api from 'api'
import moment from 'moment'
import styles from './userMessages.module.css'

const mapStateToProps = state => {
    const currentUser = state.user.currentUser
    return {
        isAdmin: currentUser.roles && currentUser.roles.includes('application_admin'),
        userMessages: state.user.userMessages
    }
}

export const showUserMessages = () =>
    actionBuilder('USER_MESSAGES')
        .set('ui.userMessages', 'SHOW_MESSAGES')
        .dispatch()

export const closePanel = () =>
    actionBuilder('USER_MESSAGES')
        .del('ui.userMessages')
        .dispatch()

class _UserMessages extends React.Component {
    state = {
        selectedMessage: null
    }

    updateMessage(message) {
        const {id = uuid()} = message
        this.props.stream('REQUEST_UPDATE_USER_MESSAGE',
            api.user.updateMessage$({...message, id}),
            message => {
                actionBuilder('UPDATE_USER_MESSAGE')
                    .assign(['user.userMessages', {message: {id}}], {message, state: 'UNREAD'})
                    .dispatch()
                Notifications.success({message: id ? msg('userMessage.update.success') : msg('userMessage.publish.success')})
            },
            error => Notifications.error({message: msg('userMessage.update.error'), error})
        )
        this.editMessage(null)
    }

    removeMessage(message) {
        const {id} = message
        this.props.stream('REQUEST_REMOVE_USER_MESSAGE',
            api.user.removeMessage$(message),
            () => {
                actionBuilder('REMOVE_USER_MESSAGE')
                    .del(['user.userMessages', {message: {id}}])
                    .dispatch()
                Notifications.success({message: msg('userMessage.remove.success')})
            },
            error => Notifications.error({message: msg('userMessage.remove.error'), error})
        )
    }

    updateUserMessage(userMessage) {
        const id = userMessage.message.id
        const state = userMessage.state
        this.props.stream('REQUEST_UPDATE_USER_MESSAGE_STATE',
            api.user.updateMessageState$(userMessage),
            () => {
                actionBuilder('UPDATE_USER_MESSAGE_STATE', {id, state})
                    .assign(['user.userMessages', {message: {id}}], {state})
                    .dispatch()
                // Notifications.success({message: msg('userMessage.updateState.success')})
            },
            error => Notifications.error({message: msg('userMessage.updateState.error'), error})
        )
    }

    buttonHandler() {
        const {modal} = this.props
        !modal && showUserMessages()
    }

    readState(read) {
        return read
            ? 'READ'
            : 'UNREAD'
    }

    setReadState(userMessage) {
        this.updateUserMessage({
            ...userMessage,
            state: 'READ'
        })
    }

    toggleReadState(userMessage) {
        const nextState = state => {
            switch(state) {
            case 'READ':
                return 'UNREAD'
            case 'UNREAD':
                return 'READ'
            default:
                throw Error(`Unsupported message state "${state}"`)
            }
        }
        this.updateUserMessage({
            ...userMessage,
            state: nextState(userMessage.state)
        })
    }

    newMessage() {
        this.editMessage({})
    }

    editMessage(userMessage) {
        this.setState({
            selectedMessage: userMessage
        })
    }

    renderMessages() {
        const {userMessages} = this.props
        if (userMessages.length) {
            const sortedUserMessages = _.orderBy(userMessages, userMessage => moment(userMessage.message.creationTime) || moment(), 'desc')
            return (
                sortedUserMessages.map((userMessage, index) => this.renderMessage(userMessage, index))
            )
        } else {
            return (
                <NoData message={msg('userMessages.noMessages')}/>
            )
        }
    }

    renderStatusButton(userMessage) {
        const {state} = userMessage
        return (
            <Button
                key='status'
                chromeless
                shape='circle'
                size='large'
                icon={state === 'UNREAD' ? 'bell' : 'check'}
                additionalClassName={[styles.subject, styles[state]].join(' ')}
                onClick={() => this.toggleReadState(userMessage)}
                tooltip={msg(`userMessages.state.${state}`)}
                tooltipPlacement='top'
            />
        )
    }

    renderMessage(userMessage, index) {
        const {isAdmin} = this.props
        const message = userMessage.message
        const author = userMessage.message.username
        const creationTime = userMessage.message.creationTime
        return (
            <SuperButton
                key={index}
                title={<Msg id='userMessages.author' author={author}/>}
                description={userMessage.message.subject}
                timestamp={creationTime}
                editTooltip={msg('userMessages.edit')}
                removeMessage={msg('userMessages.removeConfirmation', {subject: message.subject})}
                removeTooltip={msg('userMessages.remove')}
                onExpandDelayed={() => this.setReadState(userMessage, 'READ')}
                onEdit={isAdmin ? () => this.editMessage(message) : null}
                onRemove={isAdmin ? () => this.removeMessage(message) : null}
                extraButtons={[
                    this.renderStatusButton(userMessage)
                ]}
                clickToExpand
            >
                <Markdown className={styles.contents} source={userMessage.message.contents}/>
            </SuperButton>
        )
    }

    renderMessagesPanel() {
        const {isAdmin, activatable: {deactivate}} = this.props
        const close = () => deactivate()
        const add = () => this.newMessage()
        return (
            <Panel
                className={styles.panel}
                type='modal'>
                <Panel.Header
                    icon='bell'
                    title={msg('userMessages.title')}/>
                <Panel.Content>
                    {this.renderMessages()}
                </Panel.Content>
                <Panel.Buttons onEnter={close} onEscape={close}>
                    <Panel.Buttons.Main>
                        <Panel.Buttons.Close onClick={close}/>
                    </Panel.Buttons.Main>
                    <Panel.Buttons.Extra>
                        <Panel.Buttons.Add
                            label={msg('userMessages.post')}
                            icon='pencil-alt'
                            onClick={add}
                            shown={isAdmin}/>
                    </Panel.Buttons.Extra>
                </Panel.Buttons>
            </Panel>
        )
    }

    renderMessagePanel(message) {
        return (
            <UserMessage
                message={message}
                onApply={message => this.updateMessage(message)}
                onCancel={() => this.editMessage()}/>
        )
    }

    render() {
        const {userMessages} = this.props
        const {selectedMessage} = this.state
        if (userMessages) {
            return selectedMessage
                ? this.renderMessagePanel(selectedMessage)
                : this.renderMessagesPanel()
        } else {
            return null
        }
    }
}

const policy = () => ({_: 'disallow'})

const UserMessages = compose(
    _UserMessages,
    connect(mapStateToProps),
    activatable({id: 'userMessages', policy, alwaysAllow: true})
)

UserMessages.propTypes = {
    className: PropTypes.string
}

class _UserMessagesButton extends React.Component {
    state = {
        shown: false
    }

    render() {
        const {className, unreadUserMessages} = this.props
        return (
            <React.Fragment>
                <UserMessages/>
                <Activator id='userMessages'>
                    {({active, activate}) =>
                        <Button
                            chromeless
                            look='transparent'
                            size='large'
                            air='less'
                            additionalClassName={[className, unreadUserMessages ? styles.unread : null].join(' ')}
                            icon='bell'
                            disabled={active}
                            onClick={() => activate()}
                            tooltip={msg('home.sections.user.messages')}
                            tooltipPlacement='top'
                            tooltipDisabled={active}/>
                    }
                </Activator>
            </React.Fragment>
        )
    }

    componentDidUpdate() {
        const {unreadUserMessages} = this.props
        const {shown} = this.state
        if (unreadUserMessages && !shown) {
            const {activator: {activatables: {userMessages}}} = this.props
            this.setState({shown: true}, () => userMessages.activate())
        }
    }
}

export const UserMessagesButton = compose(
    _UserMessagesButton,
    connect(state => ({
        unreadUserMessages: _.filter(state.user.userMessages, {state: 'UNREAD'}).length
    })),
    activator('userMessages')
)
<|endoftext|>


<commit_before>'use strict';

angular.module('umm3601ursamajorApp')
  .controller('StatuseditorCtrl', function ($scope, $http, Auth, $location, User, Modal) {
        if(Auth.isAdmin() || Auth.isChair()) {
        } else {
            $location.path('/');
        }
        $scope.users = User.query();
        $scope.isAdmin = Auth.isAdmin;
        $scope.isChair = Auth.isChair;
        $scope.statusArray = [];

        $http.get('/api/statuss').success(function(statusArray) {
            $scope.statusArray = statusArray;
        });

        $scope.getStatuses = function(){
            $http.get('/api/statuss').success(function(statusArray) {
                $scope.statusArray = statusArray;
            });
        };

        $scope.statusEditorColor = function(status){
            return {'border-left': '4px solid rgb(' + status.color.red + ','
                                                 + status.color.green + ','
                                                 + status.color.blue + ')'};
        };

        $scope.deleteSubmissionConfirm = function(item){
            Modal.confirm.delete($scope.deleteSubmission)(item.strict, item);
        };

        $scope.deleteStatus = function(item){
            console.log("Deleting status: " + item.strict);
            $http.delete('/api/statuss/' + item._id).success(function(){
                $scope.statusArray.splice($scope.statusArray.indexOf(item), 1);
            });
            var threshold = item.priority;
            for(var j = 0; j < status.length; j++) {
                if($scope.statusArray[j].priority != 1 || $scope.statusArray[j].priority != 15) {
                    if ($scope.statusArray[j].priority > threshold) {
                        $scope.statusArray[j].priority--;
                    }
                }
            }
        };

        $scope.findEmptyPriority = function(status){
            var count = 1;
            for(var j = 0; j < status.length; j++) {
                for (var i = 0; i < status.length; i++) {
                    if (status[i].priority == count) {
                        count++;
                    }
                }
            }
            return count;
        };

        $scope.addStatus = function() {
            $http.post('/api/statuss/',
            {   strict: "Default Status",
                color: {red: 0, green: 0, blue: 0, alpha: 1},
                emailSubject: "",
                emailBody: "",
                priority: $scope.findEmptyPriority($scope.statusArray)
            }).success(function (){
                    console.log("Succesfully added new status")
                    $scope.getStatuses();
                });
        };

        $scope.requiredStatus = function(status){
            return(status.priority == 1 || status.priority == 15);
        };

        $scope.submitChanges = function(status) {
            var r = confirm("Are you sure you want to edit this status?")
            var x = $scope.statusArray.indexOf(status);
            if (r == true){
                    $http.put('/api/statuss/' + $scope.statusArray[x]._id,
                        {
                            strict: $scope.statusArray[x].strict,
                            color: $scope.statusArray[x].color,
                            emailSubject: $scope.statusArray[x].emailSubject,
                            emailBody: $scope.statusArray[x].emailBody
                        }
                    ).success(function () {
                            $location.path('/admin');
                        })
                }
        };

        //OLD
//        $scope.submitChanges = function() {
//            var r = confirm("Are you sure you want to edit this status?")
//            if (r){
//                for(var x = 0; x < $scope.statusArray.length; x++){
//                    $http.put('/api/statuss/' + $scope.statusArray[x]._id,
//                        {
//                            strict: $scope.statusArray[x].strict,
//                            color: $scope.statusArray[x].color,
//                            emailSubject: $scope.statusArray[x].emailSubject,
//                            emailBody: $scope.statusArray[x].emailBody
//                        }
//                    ).success(function () {
//                            $location.path('/admin');
//                        })
//                }
//            }
//        };
    });
<commit_msg>Strict changes now update the submissions in the seed.<commit_after>'use strict';

angular.module('umm3601ursamajorApp')
  .controller('StatuseditorCtrl', function ($scope, $http, Auth, $location, User, Modal) {
        if(Auth.isAdmin() || Auth.isChair()) {
        } else {
            $location.path('/');
        }
        $scope.users = User.query();
        $scope.isAdmin = Auth.isAdmin;
        $scope.isChair = Auth.isChair;
        $scope.statusArray = [];
        $scope.submissions = [];

        $http.get('/api/statuss').success(function(statusArray) {
            $scope.statusArray = statusArray;
        });

        $http.get('/api/submissions').success(function(submissions) {
            $scope.submissions = submissions;
            socket.syncUpdates('submission', $scope.submissions);
        });

        $scope.getStatuses = function(){
            $http.get('/api/statuss').success(function(statusArray) {
                $scope.statusArray = statusArray;
            });
        };

        $scope.statusEditorColor = function(status){
            return {'border-left': '4px solid rgb(' + status.color.red   + ','
                                                    + status.color.green + ','
                                                    + status.color.blue  + ')'};
        };

        $scope.deleteSubmissionConfirm = function(item){
            Modal.confirm.delete($scope.deleteSubmission)(item.strict, item);
        };

        $scope.deleteStatus = function(item){
            console.log("Deleting status: " + item.strict);
            $http.delete('/api/statuss/' + item._id).success(function(){
                $scope.statusArray.splice($scope.statusArray.indexOf(item), 1);
            });
            var threshold = item.priority;
            for(var j = 0; j < status.length; j++) {
                if($scope.statusArray[j].priority != 1 || $scope.statusArray[j].priority != 15) {
                    if ($scope.statusArray[j].priority > threshold) {
                        $scope.statusArray[j].priority--;
                    }
                }
            }
        };

        $scope.findEmptyPriority = function(status){
            var count = 1;
            for(var j = 0; j < status.length; j++) {
                for (var i = 0; i < status.length; i++) {
                    if (status[i].priority == count) {
                        count++;
                    }
                }
            }
            return count;
        };

        $scope.addStatus = function() {
            $http.post('/api/statuss/',
            {   strict: "Default Status",
                color: {red: 0, green: 0, blue: 0, alpha: 1},
                emailSubject: "",
                emailBody: "",
                priority: $scope.findEmptyPriority($scope.statusArray)
            }).success(function (){
                    console.log("Succesfully added new status")
                    $scope.getStatuses();
                });
        };

        $scope.requiredStatus = function(status){
            return(status.priority == 1 || status.priority == 15);
        };

        $scope.submitChanges = function(status) {

            var x = $scope.statusArray.indexOf(status);
            var strict = "";
            var r = confirm("Are you sure you want to edit this status?");
            $http.get('/api/statuss/' + $scope.statusArray[x]._id).success(function(oldStatus) {
                console.log("this should come first");
                strict  = oldStatus.strict;
                if (r){
                    $http.patch('/api/statuss/' + $scope.statusArray[x]._id,
                        {
                            strict: $scope.statusArray[x].strict,
                            color: $scope.statusArray[x].color,
                            emailSubject: $scope.statusArray[x].emailSubject,
                            emailBody: $scope.statusArray[x].emailBody
                        }

                    ).success(function () {
                            console.log("this should come second");
                            for(var j = 0; j < $scope.submissions.length; j++){
                                if($scope.submissions[j].status.strict == strict){
                                    console.log("things were detected to be different");
                                    $scope.submissions[j].status.strict = $scope.statusArray[x].strict;
                                    $http.patch('/api/submissions/' + $scope.submissions[j]._id, {
                                        status: {strict: $scope.statusArray[x].strict, text: $scope.submissions[j].status.text}
                                    })
                                }
                            }

                            $location.path('/admin');
                        })
                }
            });

        };

        //OLD
//        $scope.submitChanges = function() {
//            var r = confirm("Are you sure you want to edit this status?")
//            if (r){
//                for(var x = 0; x < $scope.statusArray.length; x++){
//                    $http.put('/api/statuss/' + $scope.statusArray[x]._id,
//                        {
//                            strict: $scope.statusArray[x].strict,
//                            color: $scope.statusArray[x].color,
//                            emailSubject: $scope.statusArray[x].emailSubject,
//                            emailBody: $scope.statusArray[x].emailBody
//                        }
//                    ).success(function () {
//                            $location.path('/admin');
//                        })
//                }
//            }
//        };
    });
<|endoftext|>