<commit_before>/* If it doesn't look like there's a contest ID in the URL, show an alert, and go back one page. */
if (window.location.search.indexOf("?contest") === -1) {
	alert("Contest ID not found!");

	window.history.back();
}

/* If it doesn't look like there's an entry ID in the URL, show an alert, and go back one page. */
if (window.location.search.indexOf("&entry=") === -1) {
	alert("Entry ID not found!");

	window.history.back();
}

/* The ids of the buttons selected on judging tools */
var selectedBtn = {};
/* The score for this entry */
var scoreData = {};

/* Locate the contest ID in the URL, and store it for later use. */
var contestId = window.location.href.split("?contest=")[1].split("&")[0];

/* Locate the entry ID in the URL, and store it for later use. */
var entryId = window.location.href.split("&entry=")[1];

/* The dimensions of the program */
var dimens = {
	width: 400,
	height: 400
};
/* Set the <input> elements in <form>[name=dimensions] to dimens */
document.forms.dimensions.width.value = dimens.width;
document.forms.dimensions.height.value = dimens.height;
/* The base URL for the program preview Iframe. */
var baseURL = "https://www.khanacademy.org/computer-programming/entry/{ENTRYID}/embedded?buttons=no&editor=yes&author=no&embed=yes&width="+dimens.width+"&height="+dimens.height;

/* A couple elements that we'll be using later on */
var programPreview = document.querySelector(".program-preview");
//var judgingPane = document.querySelector(".judging-pane");
var currentScoreDiv = document.querySelector(".current-score");
var rubricsDiv = document.querySelector(".rubrics");

/* Print the contest ID that we found, to the console. */
console.log("Contest ID: " + contestId);

/* Print the entry ID that we found, to the console. */
console.log("Entry ID: " + entryId);

/* The number of lines of code of this project */
var linesOfCode;
/* Send an AJAX request to the KA API for this program. */
$.ajax({
	type: "GET",
	url: "https://www.khanacademy.org/api/internal/scratchpads/" + entryId,
	async: true,
	complete: function(response) {
		/* Calculate the number of lines of code in this entry. */
		linesOfCode = response.responseJSON.revision.code.split("\n").length;
		/* Insert it into where it should be in the document. */
		document.querySelector("#program-info").textContent = linesOfCode+" lines of code";

		loadEntry();
	}
});

function judgingButtonClick(k, kLower) {
	/* Click event handler for judging tool buttons */
	return function(event) {
		var id = event.target.id;
		/* Unselect previously selected button */
		if (selectedBtn[k] != null && selectedBtn[k] != id) {
			$("#"+selectedBtn[k]).removeClass("btn-success").addClass("btn-default");
		}
		/* Set selectedBtn[k] and scoreData[k] */
		selectedBtn[k] = id;
		scoreData[k] = parseInt(selectedBtn[k].replace(kLower+"SelectButton", ""), 10);
		/* Select the selected button */
		$("#"+id).removeClass("btn-default").addClass("btn-success");
	}
}

/* The <iframe> for this program */
var programIframe;
/* The user ID and permission level of a user: */
var userID, permLevel;
/* The rubrics and entry data: */
var rubrics, entryData;

function updateScoreData() {
	/* This function updates currentScoreDiv. */
	/* Empty currentScoreDiv except for the first element (the heading): */
	while (currentScoreDiv.childNodes.length > 1) currentScoreDiv.removeChild(currentScoreDiv.childNodes[currentScoreDiv.childNodes.length-1]);
	/* Go through rubrics in the intended order: */
	for (var _i = 0; _i < rubrics.Order.length; _i++) {
		/* Current Property: */
		var k = rubrics.Order[_i];
        if (Contest_Judging_System.indexOf(k) === -1) {
    		/* Name of Rubric */
    		var rubricName = k.replace(/_/gi, " ");
    		/* The current score in this rubric */
    		var curRubric = document.createElement("p");
    		/* If there are discrete options to this rubric: */
    		if (rubrics[k].hasOwnProperty("keys")) {
    			/* Set the textContent using .keys: */
    			curRubric.textContent = rubricName+": " +rubrics[k].keys[Math.round(entryData.scores.rubric[k] === undefined ? 1 : entryData.scores.rubric[k].avg)];
    		}
    		/* Otherwise, the rubric is numerical. */
    		else {
    			/* Set the current score using numbers */
    			curRubric.textContent = rubricName+": "+Math.round(entryData.scores.rubric[k] === undefined ? 1 : entryData.scores.rubric[k].avg)+" out of "+rubrics[k].max;
    		}
    		/* Append curRubric to currentScoreDiv: */
    		currentScoreDiv.appendChild(curRubric);
        }
	}
}

//Firebase authentication data and our Firebase user data:
var fbAuthenticationData, global_userData;
function loadEntry() {
    /* Log the user in: */
    Contest_Judging_System.logInAndGetUserData("popup", function(authData, userData) {
        fbAuthenticationData = authData;
        global_userData = userData;
        console.log(global_userData);
        /* Set permLevel: */
        permLevel = global_userData.permLevel;
        /* Fetch the data for this contest entry, and then use the data to build up the current page. */
        Contest_Judging_System.loadEntry(contestId, entryId, permLevel, function(entryDataLocal) {
            console.log("Entered loadEntry callback!");
            /* Set entryData: */
            entryData = entryDataLocal;

            /* Set the text of our "program-name" heading to the name of the current entry */
            document.querySelector("#program-name").textContent = entryData.name;

            /* The following stuff is broken in Firefox. Issue reported on Khan Academy live-editor repo. */
            programIframe = document.createElement("iframe");
            programIframe.src = baseURL.replace("{ENTRYID}", entryData.id);
            programIframe.width = "100%";
            programIframe.height = dimens.height;
            programIframe.scrolling = "no";
            programIframe.frameborder = 0;

            /* Wrap all this code in a callback to get the rubrics: */
            Contest_Judging_System.getAllRubrics(contestId, function(rubricsLocal) {
                /* Get the rubrics: */
                rubrics = rubricsLocal;
                console.log(JSON.stringify(rubrics));

                /* If the user can see the scores, update the scores: */
                if (entryData.hasOwnProperty("scores")) updateScoreData();
                /* Otherwise, hide the scores: */
                else currentScoreDiv.style.display = "none";

                /* If the user can see the scores... */
                if (entryData.hasOwnProperty("scores")) {
                    document.querySelector(".judgeOnly").style.display = "block";
                    /* ...Go through the rubrics in the order that we want: */
                    for (var _i = 0; _i < rubrics.Order.length; _i++) {
                        /* Current Property: */
                        var k = rubrics.Order[_i];
                        /* Name of Rubric */
                        var rubricName = k.replace(/_/gi, " ");
                        /* Lowercase Property */
                        var kLower = k.toLowerCase();
                        /* The container for all elems of this rubric */
                        var curGroup = document.createElement("div");
                        curGroup.id = kLower+"_group";
                        /* Create label for this rubric */
                        var curLabel = document.createElement("label");
                        curLabel.htmlFor = kLower;
                        curLabel.textContent = rubricName+": ";

                        /* If there are discrete options to this rubric: */
                        if (rubrics[k].hasOwnProperty("keys")) {
                            /* Container for curSelectBtnGroup */
                            var curSelect = document.createElement("div");
                            curSelect.id = kLower+"-btn-toolbar";
                            curSelect.className = "btn-toolbar";

                            /* Container for curSelectBtns */
                            var curSelectBtnGroup = document.createElement("div");
                            curSelectBtnGroup.className = "btn-group";
                            curSelectBtnGroup.role = "group";

                            /* All buttons */
                            var curSelectBtns = [ ];
                            /* Initialize scoreData[k] to the minimum: */
                            scoreData[k] = rubrics[k].min;
                            /* Create all buttons and push into curSelectBtns */
                            for (var i = rubrics[k].min; i <= rubrics[k].max; i++){
                                var curSelectButton = document.createElement("button");
                                curSelectButton.type = "button";
                                curSelectButton.id = (kLower+"SelectButton"+i.toString());
                                /* Intitialize selectedBtn[k] to the id of the minimum: */
                                if (i == rubrics[k].min) {
                                    selectedBtn[k] = curSelectButton.id;
                                    /* Also, select the button: */
                                    curSelectButton.className = "btn btn-sm btn-success";
                                }
                                /* Otherwise, give the button a default look: */
                                else curSelectButton.className = "btn btn-sm btn-default";
                                /* Remember to set the text using rubrics[k].keys and to add a click event using judgingButtonClick() above. */
                                curSelectButton.textContent = rubrics[k].keys[i];
                                $(curSelectButton).click(judgingButtonClick(k, kLower));
                                curSelectBtns.push(curSelectButton);
                            }

                            /* Append everything to whatever it needs to be appended to */
                            for (var i = 0; i < curSelectBtns.length; i++){
                                curSelectBtnGroup.appendChild(curSelectBtns[i]);
                            }
                            curSelect.appendChild(curSelectBtnGroup);
                            curGroup.appendChild(curLabel);
                            curGroup.appendChild(curSelect);
                        }
                        /* Otherwise, the rubric is numerical. */
                        else {
                            if (Contest_Judging_System.misc.rubricsToIgnore.indexOf(k) === -1) {
                                /* Edit label textContent */
                                curLabel.textContent += rubrics[k].min;

                                /* Initialize scoreData[k] to the minimum */
                                scoreData[k] = rubrics[k].min;
                                /* Slider */
                                var curSlider = document.createElement("div");
                                curSlider.className = "judgingSlider";
                                curSlider.role = "slider";
                                /* This is put in a function wrapper to save the value of curLabel, scoreData, rubricName, and k for the function inside the JSON object. */
                                (function(curLabel, scoreData, rubricName, k) {
                                    /* Use noUiSlider to create slider */
                                    noUiSlider.create(curSlider, {
                                        connect: "lower",
                                        start: rubrics[k].min,
                                        step: 1,
                                        range: {
                                            min: rubrics[k].min,
                                            max: rubrics[k].max
                                        }
                                    });
                                    curSlider.noUiSlider.on("update", function(values, handle) {
                                        /* Tell the score in curLabel when the slider changes. */
                                        curLabel.textContent = rubricName+": "+parseInt(values[handle]).toString();
                                        /* Set scoreData */
                                        scoreData[k] = parseInt(values[handle]);
                                    });
                                })(curLabel, scoreData, rubricName, k);

                                /* Append everything to whatever it needs to be appended to */
                                curGroup.appendChild(curLabel);
                                curGroup.appendChild(curSlider);
                            }
                        }

                        /* Add this judging tools to the rubrics div */
                        rubricsDiv.appendChild(curGroup);
                    }
                }

                /* Append our program iframe to the "program-preview" div. */
                programPreview.appendChild(programIframe);

                /* Set the widths of our sliders to 30% */
                $(".judgingSlider").width("30%");
            });
            console.log("Exiting loadEntry callback!");
        });
    });
}

/* Whenever we click the toggleCode button; toggle the code. */
$(".toggleCode").on("click", function() {
	var currentSrc = programIframe.src;

	if (currentSrc.indexOf("editor=yes") !== -1) {
		programIframe.src = currentSrc.replace("editor=yes", "editor=no");
	} else {
		programIframe.src = currentSrc.replace("editor=no", "editor=yes");
	}
});

$(".viewOnKA").on("click", function() {
	/* Prompt the user with a message warning them about information that could potentially bias them. */
	var promptMsg = prompt("By visiting the following page, you're exposing yourself to information that could bias your judging. If you agree to not let information you see on Khan Academy bias your judging, please type \"I agree\" in the box below to show that you read this message. Otherwise, close this prompt.");
	/* If the user types 'I agree' in the prompt, open the entry on Khan Academy. */
	if (promptMsg != null && promptMsg.toLowerCase().indexOf("i agree") > -1) {
		window.open("https://www.khanacademy.org/computer-programming/entry/" + entryId);
	}
});

$("#submitBtn").on("click", function() {
	/* Tell the user to fill out all criteria if they haven't filled out everything. */
	console.log(scoreData);

	/* Judge the entry if they have a permLevel of at least 4. */
	if (entryData.hasOwnProperty("scores")) Contest_Judging_System.judgeEntry(contestId, entryId, scoreData, permLevel, function(scoreData) {
		/* Update the score data when done: */
		entryData.scores.rubric = scoreData;
		updateScoreData();
	});
		/* Make sure permLevel has been set: */
		else if (!permLevel) alert("You haven't logged in yet!");
		else alert("You aren't in the allowed judges list!");
	});

/* When we click the "Set Dimensions" button, set the dimensions of the program. */
$("#setdimensions").on("click", function(event) {
	/* Prevent the form from redirecting */
	event.preventDefault();
	var width = parseInt(document.forms.dimensions.width.value), height = parseInt(document.forms.dimensions.height.value);
	if (isNaN(width) || isNaN(height)) {
		alert("Please enter in valid integers for the dimensions. Thanks!");
		return;
	}

	/* Edit the src attribute to set the width and height attributes */
	console.log(programIframe.src.replace("width="+dimens.width, "width="+width).replace("height="+dimens.height, "height="+height));
	programIframe.src = programIframe.src.replace("width="+dimens.width, "width="+width).replace("height="+dimens.height, "height="+height);

	/* Set dimens */
	dimens = {
		width: width,
		height: height
	};
	/* Adjust height */
	programIframe.height = dimens.height;
	programPreview.style.height = "auto";
	console.log(dimens.height, programIframe.style.height);
});
<commit_msg>Fixed a silly typo<commit_after>/* If it doesn't look like there's a contest ID in the URL, show an alert, and go back one page. */
if (window.location.search.indexOf("?contest") === -1) {
	alert("Contest ID not found!");

	window.history.back();
}

/* If it doesn't look like there's an entry ID in the URL, show an alert, and go back one page. */
if (window.location.search.indexOf("&entry=") === -1) {
	alert("Entry ID not found!");

	window.history.back();
}

/* The ids of the buttons selected on judging tools */
var selectedBtn = {};
/* The score for this entry */
var scoreData = {};

/* Locate the contest ID in the URL, and store it for later use. */
var contestId = window.location.href.split("?contest=")[1].split("&")[0];

/* Locate the entry ID in the URL, and store it for later use. */
var entryId = window.location.href.split("&entry=")[1];

/* The dimensions of the program */
var dimens = {
	width: 400,
	height: 400
};
/* Set the <input> elements in <form>[name=dimensions] to dimens */
document.forms.dimensions.width.value = dimens.width;
document.forms.dimensions.height.value = dimens.height;
/* The base URL for the program preview Iframe. */
var baseURL = "https://www.khanacademy.org/computer-programming/entry/{ENTRYID}/embedded?buttons=no&editor=yes&author=no&embed=yes&width="+dimens.width+"&height="+dimens.height;

/* A couple elements that we'll be using later on */
var programPreview = document.querySelector(".program-preview");
//var judgingPane = document.querySelector(".judging-pane");
var currentScoreDiv = document.querySelector(".current-score");
var rubricsDiv = document.querySelector(".rubrics");

/* Print the contest ID that we found, to the console. */
console.log("Contest ID: " + contestId);

/* Print the entry ID that we found, to the console. */
console.log("Entry ID: " + entryId);

/* The number of lines of code of this project */
var linesOfCode;
/* Send an AJAX request to the KA API for this program. */
$.ajax({
	type: "GET",
	url: "https://www.khanacademy.org/api/internal/scratchpads/" + entryId,
	async: true,
	complete: function(response) {
		/* Calculate the number of lines of code in this entry. */
		linesOfCode = response.responseJSON.revision.code.split("\n").length;
		/* Insert it into where it should be in the document. */
		document.querySelector("#program-info").textContent = linesOfCode+" lines of code";

		loadEntry();
	}
});

function judgingButtonClick(k, kLower) {
	/* Click event handler for judging tool buttons */
	return function(event) {
		var id = event.target.id;
		/* Unselect previously selected button */
		if (selectedBtn[k] != null && selectedBtn[k] != id) {
			$("#"+selectedBtn[k]).removeClass("btn-success").addClass("btn-default");
		}
		/* Set selectedBtn[k] and scoreData[k] */
		selectedBtn[k] = id;
		scoreData[k] = parseInt(selectedBtn[k].replace(kLower+"SelectButton", ""), 10);
		/* Select the selected button */
		$("#"+id).removeClass("btn-default").addClass("btn-success");
	}
}

/* The <iframe> for this program */
var programIframe;
/* The user ID and permission level of a user: */
var userID, permLevel;
/* The rubrics and entry data: */
var rubrics, entryData;

function updateScoreData() {
	/* This function updates currentScoreDiv. */
	/* Empty currentScoreDiv except for the first element (the heading): */
	while (currentScoreDiv.childNodes.length > 1) currentScoreDiv.removeChild(currentScoreDiv.childNodes[currentScoreDiv.childNodes.length-1]);
	/* Go through rubrics in the intended order: */
	for (var _i = 0; _i < rubrics.Order.length; _i++) {
		/* Current Property: */
		var k = rubrics.Order[_i];
        if (Contest_Judging_System.misc.rubricsToIgnore.indexOf(k) === -1) {
    		/* Name of Rubric */
    		var rubricName = k.replace(/_/gi, " ");
    		/* The current score in this rubric */
    		var curRubric = document.createElement("p");
    		/* If there are discrete options to this rubric: */
    		if (rubrics[k].hasOwnProperty("keys")) {
    			/* Set the textContent using .keys: */
    			curRubric.textContent = rubricName+": " +rubrics[k].keys[Math.round(entryData.scores.rubric[k] === undefined ? 1 : entryData.scores.rubric[k].avg)];
    		}
    		/* Otherwise, the rubric is numerical. */
    		else {
    			/* Set the current score using numbers */
    			curRubric.textContent = rubricName+": "+Math.round(entryData.scores.rubric[k] === undefined ? 1 : entryData.scores.rubric[k].avg)+" out of "+rubrics[k].max;
    		}
    		/* Append curRubric to currentScoreDiv: */
    		currentScoreDiv.appendChild(curRubric);
        }
	}
}

//Firebase authentication data and our Firebase user data:
var fbAuthenticationData, global_userData;
function loadEntry() {
    /* Log the user in: */
    Contest_Judging_System.logInAndGetUserData("popup", function(authData, userData) {
        fbAuthenticationData = authData;
        global_userData = userData;
        console.log(global_userData);
        /* Set permLevel: */
        permLevel = global_userData.permLevel;
        /* Fetch the data for this contest entry, and then use the data to build up the current page. */
        Contest_Judging_System.loadEntry(contestId, entryId, permLevel, function(entryDataLocal) {
            console.log("Entered loadEntry callback!");
            /* Set entryData: */
            entryData = entryDataLocal;

            /* Set the text of our "program-name" heading to the name of the current entry */
            document.querySelector("#program-name").textContent = entryData.name;

            /* The following stuff is broken in Firefox. Issue reported on Khan Academy live-editor repo. */
            programIframe = document.createElement("iframe");
            programIframe.src = baseURL.replace("{ENTRYID}", entryData.id);
            programIframe.width = "100%";
            programIframe.height = dimens.height;
            programIframe.scrolling = "no";
            programIframe.frameborder = 0;

            /* Wrap all this code in a callback to get the rubrics: */
            Contest_Judging_System.getAllRubrics(contestId, function(rubricsLocal) {
                /* Get the rubrics: */
                rubrics = rubricsLocal;
                console.log(JSON.stringify(rubrics));

                /* If the user can see the scores, update the scores: */
                if (entryData.hasOwnProperty("scores")) updateScoreData();
                /* Otherwise, hide the scores: */
                else currentScoreDiv.style.display = "none";

                /* If the user can see the scores... */
                if (entryData.hasOwnProperty("scores")) {
                    document.querySelector(".judgeOnly").style.display = "block";
                    /* ...Go through the rubrics in the order that we want: */
                    for (var _i = 0; _i < rubrics.Order.length; _i++) {
                        /* Current Property: */
                        var k = rubrics.Order[_i];
                        /* Name of Rubric */
                        var rubricName = k.replace(/_/gi, " ");
                        /* Lowercase Property */
                        var kLower = k.toLowerCase();
                        /* The container for all elems of this rubric */
                        var curGroup = document.createElement("div");
                        curGroup.id = kLower+"_group";
                        /* Create label for this rubric */
                        var curLabel = document.createElement("label");
                        curLabel.htmlFor = kLower;
                        curLabel.textContent = rubricName+": ";

                        /* If there are discrete options to this rubric: */
                        if (rubrics[k].hasOwnProperty("keys")) {
                            /* Container for curSelectBtnGroup */
                            var curSelect = document.createElement("div");
                            curSelect.id = kLower+"-btn-toolbar";
                            curSelect.className = "btn-toolbar";

                            /* Container for curSelectBtns */
                            var curSelectBtnGroup = document.createElement("div");
                            curSelectBtnGroup.className = "btn-group";
                            curSelectBtnGroup.role = "group";

                            /* All buttons */
                            var curSelectBtns = [ ];
                            /* Initialize scoreData[k] to the minimum: */
                            scoreData[k] = rubrics[k].min;
                            /* Create all buttons and push into curSelectBtns */
                            for (var i = rubrics[k].min; i <= rubrics[k].max; i++){
                                var curSelectButton = document.createElement("button");
                                curSelectButton.type = "button";
                                curSelectButton.id = (kLower+"SelectButton"+i.toString());
                                /* Intitialize selectedBtn[k] to the id of the minimum: */
                                if (i == rubrics[k].min) {
                                    selectedBtn[k] = curSelectButton.id;
                                    /* Also, select the button: */
                                    curSelectButton.className = "btn btn-sm btn-success";
                                }
                                /* Otherwise, give the button a default look: */
                                else curSelectButton.className = "btn btn-sm btn-default";
                                /* Remember to set the text using rubrics[k].keys and to add a click event using judgingButtonClick() above. */
                                curSelectButton.textContent = rubrics[k].keys[i];
                                $(curSelectButton).click(judgingButtonClick(k, kLower));
                                curSelectBtns.push(curSelectButton);
                            }

                            /* Append everything to whatever it needs to be appended to */
                            for (var i = 0; i < curSelectBtns.length; i++){
                                curSelectBtnGroup.appendChild(curSelectBtns[i]);
                            }
                            curSelect.appendChild(curSelectBtnGroup);
                            curGroup.appendChild(curLabel);
                            curGroup.appendChild(curSelect);
                        }
                        /* Otherwise, the rubric is numerical. */
                        else {
                            if (Contest_Judging_System.misc.rubricsToIgnore.indexOf(k) === -1) {
                                /* Edit label textContent */
                                curLabel.textContent += rubrics[k].min;

                                /* Initialize scoreData[k] to the minimum */
                                scoreData[k] = rubrics[k].min;
                                /* Slider */
                                var curSlider = document.createElement("div");
                                curSlider.className = "judgingSlider";
                                curSlider.role = "slider";
                                /* This is put in a function wrapper to save the value of curLabel, scoreData, rubricName, and k for the function inside the JSON object. */
                                (function(curLabel, scoreData, rubricName, k) {
                                    /* Use noUiSlider to create slider */
                                    noUiSlider.create(curSlider, {
                                        connect: "lower",
                                        start: rubrics[k].min,
                                        step: 1,
                                        range: {
                                            min: rubrics[k].min,
                                            max: rubrics[k].max
                                        }
                                    });
                                    curSlider.noUiSlider.on("update", function(values, handle) {
                                        /* Tell the score in curLabel when the slider changes. */
                                        curLabel.textContent = rubricName+": "+parseInt(values[handle]).toString();
                                        /* Set scoreData */
                                        scoreData[k] = parseInt(values[handle]);
                                    });
                                })(curLabel, scoreData, rubricName, k);

                                /* Append everything to whatever it needs to be appended to */
                                curGroup.appendChild(curLabel);
                                curGroup.appendChild(curSlider);
                            }
                        }

                        /* Add this judging tools to the rubrics div */
                        rubricsDiv.appendChild(curGroup);
                    }
                }

                /* Append our program iframe to the "program-preview" div. */
                programPreview.appendChild(programIframe);

                /* Set the widths of our sliders to 30% */
                $(".judgingSlider").width("30%");
            });
            console.log("Exiting loadEntry callback!");
        });
    });
}

/* Whenever we click the toggleCode button; toggle the code. */
$(".toggleCode").on("click", function() {
	var currentSrc = programIframe.src;

	if (currentSrc.indexOf("editor=yes") !== -1) {
		programIframe.src = currentSrc.replace("editor=yes", "editor=no");
	} else {
		programIframe.src = currentSrc.replace("editor=no", "editor=yes");
	}
});

$(".viewOnKA").on("click", function() {
	/* Prompt the user with a message warning them about information that could potentially bias them. */
	var promptMsg = prompt("By visiting the following page, you're exposing yourself to information that could bias your judging. If you agree to not let information you see on Khan Academy bias your judging, please type \"I agree\" in the box below to show that you read this message. Otherwise, close this prompt.");
	/* If the user types 'I agree' in the prompt, open the entry on Khan Academy. */
	if (promptMsg != null && promptMsg.toLowerCase().indexOf("i agree") > -1) {
		window.open("https://www.khanacademy.org/computer-programming/entry/" + entryId);
	}
});

$("#submitBtn").on("click", function() {
	/* Tell the user to fill out all criteria if they haven't filled out everything. */
	console.log(scoreData);

	/* Judge the entry if they have a permLevel of at least 4. */
	if (entryData.hasOwnProperty("scores")) Contest_Judging_System.judgeEntry(contestId, entryId, scoreData, permLevel, function(scoreData) {
		/* Update the score data when done: */
		entryData.scores.rubric = scoreData;
		updateScoreData();
	});
		/* Make sure permLevel has been set: */
		else if (!permLevel) alert("You haven't logged in yet!");
		else alert("You aren't in the allowed judges list!");
	});

/* When we click the "Set Dimensions" button, set the dimensions of the program. */
$("#setdimensions").on("click", function(event) {
	/* Prevent the form from redirecting */
	event.preventDefault();
	var width = parseInt(document.forms.dimensions.width.value), height = parseInt(document.forms.dimensions.height.value);
	if (isNaN(width) || isNaN(height)) {
		alert("Please enter in valid integers for the dimensions. Thanks!");
		return;
	}

	/* Edit the src attribute to set the width and height attributes */
	console.log(programIframe.src.replace("width="+dimens.width, "width="+width).replace("height="+dimens.height, "height="+height));
	programIframe.src = programIframe.src.replace("width="+dimens.width, "width="+width).replace("height="+dimens.height, "height="+height);

	/* Set dimens */
	dimens = {
		width: width,
		height: height
	};
	/* Adjust height */
	programIframe.height = dimens.height;
	programPreview.style.height = "auto";
	console.log(dimens.height, programIframe.style.height);
});
<|endoftext|>


<commit_before>pc.extend(pc, function () {

    function sortDrawCalls(drawCallA, drawCallB) {

        if (drawCallA.layer === drawCallB.layer) {
            if (drawCallA.drawOrder && drawCallB.drawOrder) {
                return drawCallA.drawOrder - drawCallB.drawOrder;
            }
        }

        return drawCallB.key - drawCallA.key;
    }

    /**
     * constructor
     * @name pc.Picker
     * @classdesc Picker object used to select mesh instances from screen coordinates.
     * @description Create a new instance of a Picker object
     * @param {pc.GraphicsDevice} device Graphics device used to manage internal graphics resources.
     * @param {Number} width The width of the pick buffer in pixels.
     * @param {Number} height The height of the pick buffer in pixels.
     * @property {Number} width Width of the pick buffer in pixels (read-only).
     * @property {Number} height Height of the pick buffer in pixels (read-only).
     * @property {pc.RenderTarget} renderTarget The render target used by the picker internally (read-only).
     */
    var Picker = function(device, width, height) {
        this.device = device;
        this.library = device.getProgramLibrary();

        this.pickColor = new Float32Array(4);

        this.scene = null;
        this.drawCalls = [ ];

        this.clearOptions = {
            color: [1, 1, 1, 1],
            depth: 1,
            flags: pc.CLEARFLAG_COLOR | pc.CLEARFLAG_DEPTH
        };
        this.resize(width, height);

        this._ignoreOpacityFor = null; // meshInstance
    };

    /**
     * @function
     * @name pc.Picker#getSelection
     * @description Return the list of mesh instances selected by the specified rectangle in the
     * previously prepared pick buffer.The rectangle using top-left coordinate system.
     * @param {Number} x The left edge of the rectangle
     * @param {Number} y The top edge of the rectangle
     * @param {Number} [width] The width of the rectangle
     * @param {Number} [height] The height of the rectangle
     * @returns {pc.MeshInstance[]} An array of mesh instances that are in the selection
     * @example
     * // Get the selection at the point (10,20)
     * var selection = picker.getSelection(10, 20);
     *
     * // Get all models in rectangle with corners at (10,20) and (20,40)
     * var selection = picker.getSelection(10, 20, 10, 20);
     */
    Picker.prototype.getSelection = function (x, y, width, height) {
        var device = this.device;

        if (typeof x === 'object') {
            // #ifdef DEBUG
            console.warn("Picker.getSelection:param 'rect' is deprecated, use 'x, y, width, height' instead.");
            // #endif

            var rect = x;
            x = rect.x;
            y = rect.y;
            width = rect.width;
            height = rect.height;
        } else {
            y = this._pickBufferTarget.height - (y + (height || 1));
        }

        width = width || 1;
        height = height || 1;

        // Cache active render target
        var prevRenderTarget = device.renderTarget;

        // Ready the device for rendering to the pick buffer
        device.setRenderTarget(this._pickBufferTarget);
        device.updateBegin();

        var pixels = new Uint8Array(4 * width * height);
        device.readPixels(x, y, width, height, pixels);

        device.updateEnd();

        // Restore render target
        device.setRenderTarget(prevRenderTarget);

        var selection = [];

        for (var i = 0; i < width * height; i++) {
            var r = pixels[4 * i + 0];
            var g = pixels[4 * i + 1];
            var b = pixels[4 * i + 2];
            var index = r << 16 | g << 8 | b;
            // White is 'no selection'
            if (index !== 0xffffff) {
                var selectedMeshInstance = this.drawCalls[index];
                if (selection.indexOf(selectedMeshInstance) === -1) {
                    selection.push(selectedMeshInstance);
                }
            }
        }

        return selection;
    };

    /**
     * @function
     * @name pc.Picker#prepare
     * @description Primes the pick buffer with a rendering of the specified models from the point of view
     * of the supplied camera. Once the pick buffer has been prepared, pc.Picker#getSelection can be
     * called multiple times on the same picker object. Therefore, if the models or camera do not change
     * in any way, pc.Picker#prepare does not need to be called again.
     * @param {pc.Camera} camera The camera used to render the scene, note this is the CameraNode, not an Entity
     * @param {pc.Scene} scene The scene containing the pickable mesh instances.
     */
    Picker.prototype.prepare = function (camera, scene) {
        var device = this.device;

        this.scene = scene;

        // Cache active render target
        var prevRenderTarget = device.renderTarget;

        // Ready the device for rendering to the pick buffer
        device.setRenderTarget(this._pickBufferTarget);
        device.updateBegin();
        device.setViewport(0, 0, this._pickBufferTarget.width, this._pickBufferTarget.height);
        device.setScissor(0, 0, this._pickBufferTarget.width, this._pickBufferTarget.height);
        device.clear(this.clearOptions);

        // Build mesh instance list (ideally done by visibility query)
        var i;
        var mesh, meshInstance, material;
        var type;
        var shader;
        var scope = device.scope;
        var modelMatrixId = scope.resolve('matrix_model');
        var boneTextureId = scope.resolve('texture_poseMap');
        var boneTextureSizeId = scope.resolve('texture_poseMapSize');
        var poseMatrixId = scope.resolve('matrix_pose[0]');
        var pickColorId = scope.resolve('uColor');
        var projId = scope.resolve('matrix_projection');
        var viewProjId = scope.resolve('matrix_viewProjection');
        var opacityMapId = scope.resolve('texture_opacityMap');
        var alphaTestId = scope.resolve('alpha_ref');
        var nineOuterScaleId = scope.resolve('outerScale');

        var wtm = camera._node.getWorldTransform();
        var projMat = camera.getProjectionMatrix();
        var viewMat = wtm.clone().invert();
        var viewProjMat = new pc.Mat4();
        viewProjMat.mul2(projMat, viewMat);

        projId.setValue(projMat.data);
        viewProjId.setValue(viewProjMat.data);

        // copy scene drawCalls
        this.drawCalls = scene.drawCalls.slice(0);
        // sort same as forward renderer
        this.drawCalls.sort(sortDrawCalls);

        for (i = 0; i < this.drawCalls.length; i++) {
            if (this.drawCalls[i].command) {
                this.drawCalls[i].command();
            } else {
                if (!this.drawCalls[i].pick) continue;
                meshInstance = this.drawCalls[i];
                mesh = meshInstance.mesh;
                if (! mesh) continue;
                material = meshInstance.material;

                type = mesh.primitive[pc.RENDERSTYLE_SOLID].type;
                var isSolid = (type === pc.PRIMITIVE_TRIANGLES) || (type === pc.PRIMITIVE_TRISTRIP) || (type === pc.PRIMITIVE_TRIFAN);
                var isPickable = (material instanceof pc.StandardMaterial) || (material instanceof pc.BasicMaterial);
                if (isSolid && isPickable) {

                    device.setBlending(false);
                    device.setCullMode(material.cull);
                    device.setDepthWrite(material.depthWrite);
                    device.setDepthTest(material.depthTest);

                    modelMatrixId.setValue(meshInstance.node.worldTransform.data);
                    if (meshInstance.skinInstance) {
                        if (device.supportsBoneTextures) {
                            boneTextureId.setValue(meshInstance.skinInstance.boneTexture);
                            var w = meshInstance.skinInstance.boneTexture.width;
                            var h = meshInstance.skinInstance.boneTexture.height;
                            boneTextureSizeId.setValue([w, h]);
                        } else {
                            poseMatrixId.setValue(meshInstance.skinInstance.matrixPalette);
                        }
                    }

                    if (material.opacityMap) {
                        opacityMapId.setValue(material.opacityMap);
                        alphaTestId.setValue(meshInstance===this._ignoreOpacityFor? 0 : material.alphaTest);
                    }

                    if (meshInstance.nineSlice) {
                        nineOuterScaleId.setValue(meshInstance.parameters.outerScale.data);
                    }

                    this.pickColor[0] = ((i >> 16) & 0xff) / 255;
                    this.pickColor[1] = ((i >> 8) & 0xff) / 255;
                    this.pickColor[2] = (i & 0xff) / 255;
                    this.pickColor[3] = 1;
                    pickColorId.setValue(this.pickColor);

                    shader = meshInstance._shader[pc.SHADER_PICK];
                    if (!shader) {
                        shader = this.library.getProgram('pick', {
                                skin: !!meshInstance.skinInstance,
                                screenSpace: meshInstance.screenSpace,
                                nineSlice: meshInstance.nineSlice,
                                opacityMap: !!material.opacityMap,
                                opacityChannel: material.opacityMap? (material.opacityMapChannel || 'r') : null
                            });
                        meshInstance._shader[pc.SHADER_PICK] = shader;
                    }
                    device.setShader(shader);

                    device.setVertexBuffer((meshInstance.morphInstance && meshInstance.morphInstance._vertexBuffer) ?
                        meshInstance.morphInstance._vertexBuffer : mesh.vertexBuffer, 0);
                    device.setIndexBuffer(mesh.indexBuffer[pc.RENDERSTYLE_SOLID]);
                    device.draw(mesh.primitive[pc.RENDERSTYLE_SOLID]);
                }
            }
        }

        device.setViewport(0, 0, device.width, device.height);
        device.setScissor(0, 0, device.width, device.height);
        device.updateEnd();

        // Restore render target
        device.setRenderTarget(prevRenderTarget);
    };

    /**
     * @function
     * @name pc.Picker#resize
     * @description Sets the resolution of the pick buffer. The pick buffer resolution does not need
     * to match the resolution of the corresponding frame buffer use for general rendering of the
     * 3D scene. However, the lower the resolution of the pick buffer, the less accurate the selection
     * results returned by pc.Picker#getSelection. On the other hand, smaller pick buffers will
     * yield greater performance, so there is a trade off.
     * @param {Number} width The width of the pick buffer in pixels.
     * @param {Number} height The height of the pick buffer in pixels.
     */
    Picker.prototype.resize = function (width, height) {
        var colorBuffer = new pc.Texture(this.device, {
            format: pc.PIXELFORMAT_R8_G8_B8_A8,
            width: width,
            height: height,
            mipmaps: false,
            minFilter: pc.FILTER_NEAREST,
            magFilter: pc.FILTER_NEAREST
        });
        this._pickBufferTarget = new pc.RenderTarget(this.device, colorBuffer, { depth: true });
    };

    Object.defineProperty(Picker.prototype, 'renderTarget', {
        get: function() { return this._pickBufferTarget; }
    });

    Object.defineProperty(Picker.prototype, 'width', {
        get: function() { return this._pickBufferTarget.width; }
    });

    Object.defineProperty(Picker.prototype, 'height', {
        get: function() { return this._pickBufferTarget.height; }
    });

    return {
        Picker: Picker
    };
}());
<commit_msg>Fix comment typo<commit_after>pc.extend(pc, function () {

    function sortDrawCalls(drawCallA, drawCallB) {

        if (drawCallA.layer === drawCallB.layer) {
            if (drawCallA.drawOrder && drawCallB.drawOrder) {
                return drawCallA.drawOrder - drawCallB.drawOrder;
            }
        }

        return drawCallB.key - drawCallA.key;
    }

    /**
     * @constructor
     * @name pc.Picker
     * @classdesc Picker object used to select mesh instances from screen coordinates.
     * @description Create a new instance of a Picker object
     * @param {pc.GraphicsDevice} device Graphics device used to manage internal graphics resources.
     * @param {Number} width The width of the pick buffer in pixels.
     * @param {Number} height The height of the pick buffer in pixels.
     * @property {Number} width Width of the pick buffer in pixels (read-only).
     * @property {Number} height Height of the pick buffer in pixels (read-only).
     * @property {pc.RenderTarget} renderTarget The render target used by the picker internally (read-only).
     */
    var Picker = function(device, width, height) {
        this.device = device;
        this.library = device.getProgramLibrary();

        this.pickColor = new Float32Array(4);

        this.scene = null;
        this.drawCalls = [ ];

        this.clearOptions = {
            color: [1, 1, 1, 1],
            depth: 1,
            flags: pc.CLEARFLAG_COLOR | pc.CLEARFLAG_DEPTH
        };
        this.resize(width, height);

        this._ignoreOpacityFor = null; // meshInstance
    };

    /**
     * @function
     * @name pc.Picker#getSelection
     * @description Return the list of mesh instances selected by the specified rectangle in the
     * previously prepared pick buffer.The rectangle using top-left coordinate system.
     * @param {Number} x The left edge of the rectangle
     * @param {Number} y The top edge of the rectangle
     * @param {Number} [width] The width of the rectangle
     * @param {Number} [height] The height of the rectangle
     * @returns {pc.MeshInstance[]} An array of mesh instances that are in the selection
     * @example
     * // Get the selection at the point (10,20)
     * var selection = picker.getSelection(10, 20);
     *
     * // Get all models in rectangle with corners at (10,20) and (20,40)
     * var selection = picker.getSelection(10, 20, 10, 20);
     */
    Picker.prototype.getSelection = function (x, y, width, height) {
        var device = this.device;

        if (typeof x === 'object') {
            // #ifdef DEBUG
            console.warn("Picker.getSelection:param 'rect' is deprecated, use 'x, y, width, height' instead.");
            // #endif

            var rect = x;
            x = rect.x;
            y = rect.y;
            width = rect.width;
            height = rect.height;
        } else {
            y = this._pickBufferTarget.height - (y + (height || 1));
        }

        width = width || 1;
        height = height || 1;

        // Cache active render target
        var prevRenderTarget = device.renderTarget;

        // Ready the device for rendering to the pick buffer
        device.setRenderTarget(this._pickBufferTarget);
        device.updateBegin();

        var pixels = new Uint8Array(4 * width * height);
        device.readPixels(x, y, width, height, pixels);

        device.updateEnd();

        // Restore render target
        device.setRenderTarget(prevRenderTarget);

        var selection = [];

        for (var i = 0; i < width * height; i++) {
            var r = pixels[4 * i + 0];
            var g = pixels[4 * i + 1];
            var b = pixels[4 * i + 2];
            var index = r << 16 | g << 8 | b;
            // White is 'no selection'
            if (index !== 0xffffff) {
                var selectedMeshInstance = this.drawCalls[index];
                if (selection.indexOf(selectedMeshInstance) === -1) {
                    selection.push(selectedMeshInstance);
                }
            }
        }

        return selection;
    };

    /**
     * @function
     * @name pc.Picker#prepare
     * @description Primes the pick buffer with a rendering of the specified models from the point of view
     * of the supplied camera. Once the pick buffer has been prepared, pc.Picker#getSelection can be
     * called multiple times on the same picker object. Therefore, if the models or camera do not change
     * in any way, pc.Picker#prepare does not need to be called again.
     * @param {pc.Camera} camera The camera used to render the scene, note this is the CameraNode, not an Entity
     * @param {pc.Scene} scene The scene containing the pickable mesh instances.
     */
    Picker.prototype.prepare = function (camera, scene) {
        var device = this.device;

        this.scene = scene;

        // Cache active render target
        var prevRenderTarget = device.renderTarget;

        // Ready the device for rendering to the pick buffer
        device.setRenderTarget(this._pickBufferTarget);
        device.updateBegin();
        device.setViewport(0, 0, this._pickBufferTarget.width, this._pickBufferTarget.height);
        device.setScissor(0, 0, this._pickBufferTarget.width, this._pickBufferTarget.height);
        device.clear(this.clearOptions);

        // Build mesh instance list (ideally done by visibility query)
        var i;
        var mesh, meshInstance, material;
        var type;
        var shader;
        var scope = device.scope;
        var modelMatrixId = scope.resolve('matrix_model');
        var boneTextureId = scope.resolve('texture_poseMap');
        var boneTextureSizeId = scope.resolve('texture_poseMapSize');
        var poseMatrixId = scope.resolve('matrix_pose[0]');
        var pickColorId = scope.resolve('uColor');
        var projId = scope.resolve('matrix_projection');
        var viewProjId = scope.resolve('matrix_viewProjection');
        var opacityMapId = scope.resolve('texture_opacityMap');
        var alphaTestId = scope.resolve('alpha_ref');
        var nineOuterScaleId = scope.resolve('outerScale');

        var wtm = camera._node.getWorldTransform();
        var projMat = camera.getProjectionMatrix();
        var viewMat = wtm.clone().invert();
        var viewProjMat = new pc.Mat4();
        viewProjMat.mul2(projMat, viewMat);

        projId.setValue(projMat.data);
        viewProjId.setValue(viewProjMat.data);

        // copy scene drawCalls
        this.drawCalls = scene.drawCalls.slice(0);
        // sort same as forward renderer
        this.drawCalls.sort(sortDrawCalls);

        for (i = 0; i < this.drawCalls.length; i++) {
            if (this.drawCalls[i].command) {
                this.drawCalls[i].command();
            } else {
                if (!this.drawCalls[i].pick) continue;
                meshInstance = this.drawCalls[i];
                mesh = meshInstance.mesh;
                if (! mesh) continue;
                material = meshInstance.material;

                type = mesh.primitive[pc.RENDERSTYLE_SOLID].type;
                var isSolid = (type === pc.PRIMITIVE_TRIANGLES) || (type === pc.PRIMITIVE_TRISTRIP) || (type === pc.PRIMITIVE_TRIFAN);
                var isPickable = (material instanceof pc.StandardMaterial) || (material instanceof pc.BasicMaterial);
                if (isSolid && isPickable) {

                    device.setBlending(false);
                    device.setCullMode(material.cull);
                    device.setDepthWrite(material.depthWrite);
                    device.setDepthTest(material.depthTest);

                    modelMatrixId.setValue(meshInstance.node.worldTransform.data);
                    if (meshInstance.skinInstance) {
                        if (device.supportsBoneTextures) {
                            boneTextureId.setValue(meshInstance.skinInstance.boneTexture);
                            var w = meshInstance.skinInstance.boneTexture.width;
                            var h = meshInstance.skinInstance.boneTexture.height;
                            boneTextureSizeId.setValue([w, h]);
                        } else {
                            poseMatrixId.setValue(meshInstance.skinInstance.matrixPalette);
                        }
                    }

                    if (material.opacityMap) {
                        opacityMapId.setValue(material.opacityMap);
                        alphaTestId.setValue(meshInstance===this._ignoreOpacityFor? 0 : material.alphaTest);
                    }

                    if (meshInstance.nineSlice) {
                        nineOuterScaleId.setValue(meshInstance.parameters.outerScale.data);
                    }

                    this.pickColor[0] = ((i >> 16) & 0xff) / 255;
                    this.pickColor[1] = ((i >> 8) & 0xff) / 255;
                    this.pickColor[2] = (i & 0xff) / 255;
                    this.pickColor[3] = 1;
                    pickColorId.setValue(this.pickColor);

                    shader = meshInstance._shader[pc.SHADER_PICK];
                    if (!shader) {
                        shader = this.library.getProgram('pick', {
                                skin: !!meshInstance.skinInstance,
                                screenSpace: meshInstance.screenSpace,
                                nineSlice: meshInstance.nineSlice,
                                opacityMap: !!material.opacityMap,
                                opacityChannel: material.opacityMap? (material.opacityMapChannel || 'r') : null
                            });
                        meshInstance._shader[pc.SHADER_PICK] = shader;
                    }
                    device.setShader(shader);

                    device.setVertexBuffer((meshInstance.morphInstance && meshInstance.morphInstance._vertexBuffer) ?
                        meshInstance.morphInstance._vertexBuffer : mesh.vertexBuffer, 0);
                    device.setIndexBuffer(mesh.indexBuffer[pc.RENDERSTYLE_SOLID]);
                    device.draw(mesh.primitive[pc.RENDERSTYLE_SOLID]);
                }
            }
        }

        device.setViewport(0, 0, device.width, device.height);
        device.setScissor(0, 0, device.width, device.height);
        device.updateEnd();

        // Restore render target
        device.setRenderTarget(prevRenderTarget);
    };

    /**
     * @function
     * @name pc.Picker#resize
     * @description Sets the resolution of the pick buffer. The pick buffer resolution does not need
     * to match the resolution of the corresponding frame buffer use for general rendering of the
     * 3D scene. However, the lower the resolution of the pick buffer, the less accurate the selection
     * results returned by pc.Picker#getSelection. On the other hand, smaller pick buffers will
     * yield greater performance, so there is a trade off.
     * @param {Number} width The width of the pick buffer in pixels.
     * @param {Number} height The height of the pick buffer in pixels.
     */
    Picker.prototype.resize = function (width, height) {
        var colorBuffer = new pc.Texture(this.device, {
            format: pc.PIXELFORMAT_R8_G8_B8_A8,
            width: width,
            height: height,
            mipmaps: false,
            minFilter: pc.FILTER_NEAREST,
            magFilter: pc.FILTER_NEAREST
        });
        this._pickBufferTarget = new pc.RenderTarget(this.device, colorBuffer, { depth: true });
    };

    Object.defineProperty(Picker.prototype, 'renderTarget', {
        get: function() { return this._pickBufferTarget; }
    });

    Object.defineProperty(Picker.prototype, 'width', {
        get: function() { return this._pickBufferTarget.width; }
    });

    Object.defineProperty(Picker.prototype, 'height', {
        get: function() { return this._pickBufferTarget.height; }
    });

    return {
        Picker: Picker
    };
}());
<|endoftext|>


<commit_before>(function (window) {

    var easingFn = {
        quadratic: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
        circular: 'cubic-bezier(0.1, 0.57, 0.1, 1)'
    };

    // helpers
    var math = {
        distance: function (start, end) {
            return Math.round(end - start);
        },
        velocity: function (distance, duration) {
            return Math.abs(distance) / duration; // px/ms
        },
        direction: function (delta) {
            return delta < 0 ? -1 : delta > 0 ? 1 : 0;
        },
        inertia: function (current, direction, velocity, lower, upper, deceleration) {
            var destination, duration,
                t, a = deceleration || 0.0006;

            t = velocity / a; // a = v / t
            destination = current + (velocity * (t * t) * 0.5 * a * direction);

            if (destination < lower || destination > upper) {
                destination = destination < lower ? lower :
                    destination > upper ? upper :
                    destination;
                t = Math.sqrt(Math.abs((destination - current) / (velocity * 0.5 * a * direction)));
            }

            return {
                destination: Math.round(destination),
                duration: parseFloat(t)
            };
        }
    };

    function triggerEvent(elem, type, data) {
        var ev = new CustomEvent(type, {
            detail: data || {},
            bubbles: true,
            cancelable: true
        });

        elem.dispatchEvent(ev);
    }

    function Sv(el, options) {
        var opt = options || {};

        this.view = typeof el === 'string' ? document.querySelector(el) : el;
        this.scroller = this.view.children[0];

        // TODO merge options
        this.options = {
            start: opt.start || [0, 0],

            scrollX: opt.scrollX || false,
            scrollY: opt.scrollY || true,

            inertia: opt.inertia || true,

            bounce: opt.bounce || true,
            bounceTime: opt.bounceTime || 600
        };

        // define initial state
        this._enabled = true;
        this._curPointer = false;

        // event handler
        this._handleStart = this._handleStart.bind(this);
        this._handleMove = this._handleMove.bind(this);
        this._handleEnd = this._handleEnd.bind(this);
        this._handleInertiaEnd = this._handleInertiaEnd.bind(this);
        this._handleBounceTransitionEnd = this._handleBounceTransitionEnd.bind(this);

        this.view.addEventListener('pointerdown', this._handleStart, false);

        // inital scroll
        this.scrollTo(this.options.start[0], this.options.start[1]);
    }

    Sv.prototype = {
        version: '0.0.0',

        enable: function (cond) {
            this._enabled = cond === false ? cond : true;
        },

        cancel: function (pointerId) {
            if (pointerId && this._curPointer && this._curPointer !== pointerId) {
                return;
            }

            this._curPointer = null;
        },

        scrollTo: function (x, y, time, easing) {
            var ease = easing || easingFn.circular;

            this.scroller.style['transitionTimingFunction'] =
            this.scroller.style['webkitTransitionTimingFunction'] = ease;
            this.scroller.style['transitionDuration'] =
            this.scroller.style['webkitTransitionDuration'] = (time || 0) + 'ms';

            this._transform(x, y);
        },

        _handleStart: function (ev) {
            if (!this._enabled || this._curPointer) {
                return;
            }

            var timestamp = new Date().getTime();

            this._curPointer = ev.pointerId; // lock for pointer
            this._hasMoved = false;

            this._getBoundaries();

            // make sure it is not moving anymore
            this._forceTransitionEnd();

            // previous point (based on pointer) for delta calculation
            this._lastPoint = {
                timestamp: timestamp,
                x: ev.pageX,
                y: ev.pageY
            };

            // last key frame for velocity caluclation
            this._lastKeyFrame = {
                timestamp: timestamp,
                x: this.x,
                y: this.y
            };

            // bind events
            document.addEventListener('pointerup', this._handleEnd, false);
            document.addEventListener('pointercancel', this._handleEnd, false);
            document.addEventListener('pointermove', this._handleMove, false);
        },

        _handleMove: function (ev) {
            if (!this._enabled || this._curPointer !== ev.pointerId) {
                return;
            }

            var deltaX = this.options.scrollX ? math.distance(this._lastPoint.x, ev.pageX) : 0,
                deltaY = this.options.scrollY ? math.distance(this._lastPoint.y, ev.pageY) : 0,
                timestamp = new Date().getTime(),
                newX, newY;

            this._lastPoint = {
                timestamp: timestamp,
                x: ev.pageX,
                y: ev.pageY
            };

            newX = this.x + deltaX;
            newY = this.y + deltaY;

            // TODO add WIGGLE_THRESHOLD
            // We need to move at least 10 pixels for the scrolling to initiate
            //if (Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10) {
                //return;
            //}

            // consider boundaries
            newX = newX > 0 ? // upper
                this.options.bounce ? this.x + deltaX / 3 :
                0 :
                newX;
            newY = newY > 0 ? // upper
                this.options.bounce ? this.y + deltaY / 3 :
                0 :
                newY;
            newX = newX < this._boundaries[0] ? // lower
                this.options.bounce ? this.x + deltaX / 3 :
                this._boundaries[0] :
                newX;
            newY = newY < this._boundaries[1] ? // lower
                this.options.bounce ? this.y + deltaY / 3 :
                this._boundaries[1] :
                newY;

            // initial move
            if (!this._hasMoved) {
                this._hasMoved = true;
                triggerEvent(this.view, 'scrollstart', {
                    pointerId: this._curPointer,
                    x: this.x,
                    y: this.y
                });
            }

            // save new keyframe every 300ms
            if (timestamp - this._lastKeyFrame.timestamp > 300) {
                this._lastKeyFrame = {
                    timestamp: timestamp,
                    x: newX,
                    y: newY
                }
            }

            this.scrollTo(newX, newY);

            triggerEvent(this.view, 'scroll', {
                pointerId: this._curPointer,
                x: newX,
                y: newY
            });
        },

        _handleEnd: function (ev) {
            if (!this._enabled ||
                this._curPointer !== ev.pointerId) {
                return;
            }

            var duration = new Date().getTime() - this._lastKeyFrame.timestamp,
                deltaX = this.options.scrollX ? math.distance(this._lastPoint.x, ev.pageX) : 0,
                deltaY = this.options.scrollY ? math.distance(this._lastPoint.y, ev.pageY) : 0,
                distance, velocity, scrollSpace, inertia, time,
                newX = this.x + deltaX,
                newY = this.y + deltaY;

            // reset state
            this._curPointer = null;

            document.removeEventListener('pointerup', this._handleEnd, false);
            document.removeEventListener('pointercancel', this._handleEnd, false);
            document.removeEventListener('pointermove', this._handleMove, false);

            if (!this._hasMoved) { // has never scrolled
                return
            }

            // set last position
            this.scrollTo(newX, newY);

            // start momentum animation if needed
            if (this.options.inertia && duration < 300) {
                distance = [
                    math.distance(this._lastKeyFrame.x, this.x),
                    math.distance(this._lastKeyFrame.y, this.y)
                ];

                direction = [
                    math.direction(distance[0]),
                    math.direction(distance[1])
                ];

                velocity = [
                    math.velocity(distance[0], duration),
                    math.velocity(distance[1], duration)
                ];

                scrollSpace = [
                    this.options.bounce ? this._boundaries[0] - this.view.clientWidth / 2 : this._boundaries[0],
                    this.options.bounce ? this._boundaries[1] - this.view.clientHeight / 2 : this._boundaries[1],
                    this.options.bounce ? 0 + this.view.clientWidth / 2 : 0,
                    this.options.bounce ? 0 + this.view.clientHeight / 2 : 0
                ];

                inertia = [
                    math.inertia(this.x, direction[0], velocity[0], scrollSpace[0], scrollSpace[2]),
                    math.inertia(this.y, direction[1], velocity[1], scrollSpace[1], scrollSpace[3])
                ];

                newX = inertia[0].destination;
                newY = inertia[1].destination;
                time = Math.max(inertia[0].duration, inertia[1].duration);
            }

            if (this.x <= 0 && this.x >= this._boundaries[0] && // not already in bouncing state
                this.y <= 0 && this.y >= this._boundaries[1] &&
                (newX != this.x || newY != this.y)) {
                this.scroller.addEventListener('transitionEnd', this._handleInertiaEnd, false);
                this.scroller.addEventListener('webkitTransitionEnd', this._handleInertiaEnd, false);

                this._observePosition();

                this.scrollTo(newX, newY, time,
                        newX > 0 || newX < this._boundaries[0] ||
                        newY > 0 || newY < this._boundaries[1] ? easingFn.quadratic : null);
            } else {
                this._startBounceTransition();
            }
        },

        _handleInertiaEnd: function (ev) {
            this.scroller.removeEventListener('transitionEnd', this._handleInertiaEnd, false);
            this.scroller.removeEventListener('webkitTransitionEnd', this._handleInertiaEnd, false);

            this._observePosition(false);
            this._startBounceTransition();
        },

        _startBounceTransition: function () {
            var newX = this.x > 0 ? 0 :
                this.x < this._boundaries[0] ? this._boundaries[0] :
                this.x,
                newY = this.y > 0 ? 0 :
                this.y < this._boundaries[1] ? this._boundaries[1] :
                this.y;

            if (this.options.bounce) {
                this.scroller.addEventListener('transitionEnd', this._handleBounceTransitionEnd, false);
                this.scroller.addEventListener('webkitTransitionEnd', this._handleBounceTransitionEnd, false);
                this._observePosition();

                this.scrollTo(newX, newY, this.options.bounceTime);
            } else {
                triggerEvent(this.view, 'scrollend', {
                    pointerId: this._curPointer
                });
            }
        },

        _handleBounceTransitionEnd: function (ev) {
            this._observePosition(false);
            this.scroller.removeEventListener('transitionEnd', this._handleBounceTransitionEnd, false);
            this.scroller.removeEventListener('webkitTransitionEnd', this._handleBounceTransitionEnd, false);

            triggerEvent(this.view, 'scrollend', {
                pointerId: this._curPointer
            });
        },

        _forceTransitionEnd: function () {
            this._observePosition(false);
            this.scroller.removeEventListener('transitionEnd', this._handleInertiaEnd, false);
            this.scroller.removeEventListener('webkitTransitionEnd', this._handleInertiaEnd, false);
            this.scroller.removeEventListener('transitionEnd', this._handleBounceTransitionEnd, false);
            this.scroller.removeEventListener('webkitTransitionEnd', this._handleBounceTransitionEnd, false);
            this._transform.apply(this, this._getTransformPosition() || [0, 0]);
        },

        _transform: function (x, y) {
            this.x = Math.round(x);
            this.y = Math.round(y);

            var transform = 'translate(' + this.x + 'px,' + this.y + 'px)',
                translateZ = ' translateZ(0)';

            this.scroller.style['transform'] =
            this.scroller.style['webkitTransform'] = transform + translateZ;
        },

        _getTransformPosition: function () {
            var style = window.getComputedStyle(this.scroller, null),
                match;

            match = (style['transform'] || style['webkitTransform'])
                .match(/matrix\( *([^, ]*) *, *([^, ]*) *, *([^, ]*) *, *([^, ]*) *, *([^, ]*) *, *([^, ]*) *\)/);

            if (match) {
                return [Math.round(match[5]), Math.round(match[6])];
            }

            return null;
        },

        _getBoundaries: function () {
            return this._boundaries = [ // negative numbers because we scroll negative
                -this.view.scrollWidth + this.view.clientWidth,
                -this.view.scrollHeight + this.view.clientHeight
            ];
        },

        _observePosition: function (cond) {
            var obeservationLoop, lastPos = this._getTransformPosition();

            if (cond === false) {
                (window.cancelAnimationFrame || window.webkitCancelAnimationFrame)(this._observeId);
            } else {
                obeservationLoop = function () {
                    var pos = this._getTransformPosition();

                    if (lastPos[0] !== pos[0] || lastPos[1] !== pos[1]) {
                        triggerEvent(this.view, 'scroll', {
                            pointerId: this._curPointer,
                            x: pos[0],
                            y: pos[1]
                        });
                    }
                    lastPos = pos;

                    this._observeId = (window.requestAnimationFrame || window.webkitRequestAnimationFrame)(obeservationLoop.bind(this));
                };

                this._observeId = (window.requestAnimationFrame || window.webkitRequestAnimationFrame)(obeservationLoop.bind(this))
            }

        },

    };

    window.ScrollView = Sv;

}(this));
<commit_msg>provide global<commit_after>(function (global) {

    var easingFn = {
        quadratic: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
        circular: 'cubic-bezier(0.1, 0.57, 0.1, 1)'
    };

    // helpers
    var math = {
        distance: function (start, end) {
            return Math.round(end - start);
        },
        velocity: function (distance, duration) {
            return Math.abs(distance) / duration; // px/ms
        },
        direction: function (delta) {
            return delta < 0 ? -1 : delta > 0 ? 1 : 0;
        },
        inertia: function (current, direction, velocity, lower, upper, deceleration) {
            var destination, duration,
                t, a = deceleration || 0.0006;

            t = velocity / a; // a = v / t
            destination = current + (velocity * (t * t) * 0.5 * a * direction);

            if (destination < lower || destination > upper) {
                destination = destination < lower ? lower :
                    destination > upper ? upper :
                    destination;
                t = Math.sqrt(Math.abs((destination - current) / (velocity * 0.5 * a * direction)));
            }

            return {
                destination: Math.round(destination),
                duration: parseFloat(t)
            };
        }
    };

    function triggerEvent(elem, type, data) {
        var ev = new CustomEvent(type, {
            detail: data || {},
            bubbles: true,
            cancelable: true
        });

        elem.dispatchEvent(ev);
    }

    function Sv(el, options) {
        var opt = options || {};

        this.view = typeof el === 'string' ? document.querySelector(el) : el;
        this.scroller = this.view.children[0];

        // TODO merge options
        this.options = {
            start: opt.start || [0, 0],

            scrollX: opt.scrollX || false,
            scrollY: opt.scrollY || true,

            inertia: opt.inertia || true,

            bounce: opt.bounce || true,
            bounceTime: opt.bounceTime || 600
        };

        // define initial state
        this._enabled = true;
        this._curPointer = false;

        // event handler
        this._handleStart = this._handleStart.bind(this);
        this._handleMove = this._handleMove.bind(this);
        this._handleEnd = this._handleEnd.bind(this);
        this._handleInertiaEnd = this._handleInertiaEnd.bind(this);
        this._handleBounceTransitionEnd = this._handleBounceTransitionEnd.bind(this);

        this.view.addEventListener('pointerdown', this._handleStart, false);

        // inital scroll
        this.scrollTo(this.options.start[0], this.options.start[1]);
    }

    Sv.prototype = {
        version: '0.0.0',

        enable: function (cond) {
            this._enabled = cond === false ? cond : true;
        },

        cancel: function (pointerId) {
            if (pointerId && this._curPointer && this._curPointer !== pointerId) {
                return;
            }

            this._curPointer = null;
        },

        scrollTo: function (x, y, time, easing) {
            var ease = easing || easingFn.circular;

            this.scroller.style['transitionTimingFunction'] =
            this.scroller.style['webkitTransitionTimingFunction'] = ease;
            this.scroller.style['transitionDuration'] =
            this.scroller.style['webkitTransitionDuration'] = (time || 0) + 'ms';

            this._transform(x, y);
        },

        _handleStart: function (ev) {
            if (!this._enabled || this._curPointer) {
                return;
            }

            var timestamp = new Date().getTime();

            this._curPointer = ev.pointerId; // lock for pointer
            this._hasMoved = false;

            this._getBoundaries();

            // make sure it is not moving anymore
            this._forceTransitionEnd();

            // previous point (based on pointer) for delta calculation
            this._lastPoint = {
                timestamp: timestamp,
                x: ev.pageX,
                y: ev.pageY
            };

            // last key frame for velocity caluclation
            this._lastKeyFrame = {
                timestamp: timestamp,
                x: this.x,
                y: this.y
            };

            // bind events
            document.addEventListener('pointerup', this._handleEnd, false);
            document.addEventListener('pointercancel', this._handleEnd, false);
            document.addEventListener('pointermove', this._handleMove, false);
        },

        _handleMove: function (ev) {
            if (!this._enabled || this._curPointer !== ev.pointerId) {
                return;
            }

            var deltaX = this.options.scrollX ? math.distance(this._lastPoint.x, ev.pageX) : 0,
                deltaY = this.options.scrollY ? math.distance(this._lastPoint.y, ev.pageY) : 0,
                timestamp = new Date().getTime(),
                newX, newY;

            this._lastPoint = {
                timestamp: timestamp,
                x: ev.pageX,
                y: ev.pageY
            };

            newX = this.x + deltaX;
            newY = this.y + deltaY;

            // TODO add WIGGLE_THRESHOLD
            // We need to move at least 10 pixels for the scrolling to initiate
            //if (Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10) {
                //return;
            //}

            // consider boundaries
            newX = newX > 0 ? // upper
                this.options.bounce ? this.x + deltaX / 3 :
                0 :
                newX;
            newY = newY > 0 ? // upper
                this.options.bounce ? this.y + deltaY / 3 :
                0 :
                newY;
            newX = newX < this._boundaries[0] ? // lower
                this.options.bounce ? this.x + deltaX / 3 :
                this._boundaries[0] :
                newX;
            newY = newY < this._boundaries[1] ? // lower
                this.options.bounce ? this.y + deltaY / 3 :
                this._boundaries[1] :
                newY;

            // initial move
            if (!this._hasMoved) {
                this._hasMoved = true;
                triggerEvent(this.view, 'scrollstart', {
                    pointerId: this._curPointer,
                    x: this.x,
                    y: this.y
                });
            }

            // save new keyframe every 300ms
            if (timestamp - this._lastKeyFrame.timestamp > 300) {
                this._lastKeyFrame = {
                    timestamp: timestamp,
                    x: newX,
                    y: newY
                }
            }

            this.scrollTo(newX, newY);

            triggerEvent(this.view, 'scroll', {
                pointerId: this._curPointer,
                x: newX,
                y: newY
            });
        },

        _handleEnd: function (ev) {
            if (!this._enabled ||
                this._curPointer !== ev.pointerId) {
                return;
            }

            var duration = new Date().getTime() - this._lastKeyFrame.timestamp,
                deltaX = this.options.scrollX ? math.distance(this._lastPoint.x, ev.pageX) : 0,
                deltaY = this.options.scrollY ? math.distance(this._lastPoint.y, ev.pageY) : 0,
                distance, velocity, scrollSpace, inertia, time,
                newX = this.x + deltaX,
                newY = this.y + deltaY;

            // reset state
            this._curPointer = null;

            document.removeEventListener('pointerup', this._handleEnd, false);
            document.removeEventListener('pointercancel', this._handleEnd, false);
            document.removeEventListener('pointermove', this._handleMove, false);

            if (!this._hasMoved) { // has never scrolled
                return
            }

            // set last position
            this.scrollTo(newX, newY);

            // start momentum animation if needed
            if (this.options.inertia && duration < 300) {
                distance = [
                    math.distance(this._lastKeyFrame.x, this.x),
                    math.distance(this._lastKeyFrame.y, this.y)
                ];

                direction = [
                    math.direction(distance[0]),
                    math.direction(distance[1])
                ];

                velocity = [
                    math.velocity(distance[0], duration),
                    math.velocity(distance[1], duration)
                ];

                scrollSpace = [
                    this.options.bounce ? this._boundaries[0] - this.view.clientWidth / 2 : this._boundaries[0],
                    this.options.bounce ? this._boundaries[1] - this.view.clientHeight / 2 : this._boundaries[1],
                    this.options.bounce ? 0 + this.view.clientWidth / 2 : 0,
                    this.options.bounce ? 0 + this.view.clientHeight / 2 : 0
                ];

                inertia = [
                    math.inertia(this.x, direction[0], velocity[0], scrollSpace[0], scrollSpace[2]),
                    math.inertia(this.y, direction[1], velocity[1], scrollSpace[1], scrollSpace[3])
                ];

                newX = inertia[0].destination;
                newY = inertia[1].destination;
                time = Math.max(inertia[0].duration, inertia[1].duration);
            }

            if (this.x <= 0 && this.x >= this._boundaries[0] && // not already in bouncing state
                this.y <= 0 && this.y >= this._boundaries[1] &&
                (newX != this.x || newY != this.y)) {
                this.scroller.addEventListener('transitionEnd', this._handleInertiaEnd, false);
                this.scroller.addEventListener('webkitTransitionEnd', this._handleInertiaEnd, false);

                this._observePosition();

                this.scrollTo(newX, newY, time,
                        newX > 0 || newX < this._boundaries[0] ||
                        newY > 0 || newY < this._boundaries[1] ? easingFn.quadratic : null);
            } else {
                this._startBounceTransition();
            }
        },

        _handleInertiaEnd: function (ev) {
            this.scroller.removeEventListener('transitionEnd', this._handleInertiaEnd, false);
            this.scroller.removeEventListener('webkitTransitionEnd', this._handleInertiaEnd, false);

            this._observePosition(false);
            this._startBounceTransition();
        },

        _startBounceTransition: function () {
            var newX = this.x > 0 ? 0 :
                this.x < this._boundaries[0] ? this._boundaries[0] :
                this.x,
                newY = this.y > 0 ? 0 :
                this.y < this._boundaries[1] ? this._boundaries[1] :
                this.y;

            if (this.options.bounce) {
                this.scroller.addEventListener('transitionEnd', this._handleBounceTransitionEnd, false);
                this.scroller.addEventListener('webkitTransitionEnd', this._handleBounceTransitionEnd, false);
                this._observePosition();

                this.scrollTo(newX, newY, this.options.bounceTime);
            } else {
                triggerEvent(this.view, 'scrollend', {
                    pointerId: this._curPointer
                });
            }
        },

        _handleBounceTransitionEnd: function (ev) {
            this._observePosition(false);
            this.scroller.removeEventListener('transitionEnd', this._handleBounceTransitionEnd, false);
            this.scroller.removeEventListener('webkitTransitionEnd', this._handleBounceTransitionEnd, false);

            triggerEvent(this.view, 'scrollend', {
                pointerId: this._curPointer
            });
        },

        _forceTransitionEnd: function () {
            this._observePosition(false);
            this.scroller.removeEventListener('transitionEnd', this._handleInertiaEnd, false);
            this.scroller.removeEventListener('webkitTransitionEnd', this._handleInertiaEnd, false);
            this.scroller.removeEventListener('transitionEnd', this._handleBounceTransitionEnd, false);
            this.scroller.removeEventListener('webkitTransitionEnd', this._handleBounceTransitionEnd, false);
            this._transform.apply(this, this._getTransformPosition() || [0, 0]);
        },

        _transform: function (x, y) {
            this.x = Math.round(x);
            this.y = Math.round(y);

            var transform = 'translate(' + this.x + 'px,' + this.y + 'px)',
                translateZ = ' translateZ(0)';

            this.scroller.style['transform'] =
            this.scroller.style['webkitTransform'] = transform + translateZ;
        },

        _getTransformPosition: function () {
            var style = window.getComputedStyle(this.scroller, null),
                match;

            match = (style['transform'] || style['webkitTransform'])
                .match(/matrix\( *([^, ]*) *, *([^, ]*) *, *([^, ]*) *, *([^, ]*) *, *([^, ]*) *, *([^, ]*) *\)/);

            if (match) {
                return [Math.round(match[5]), Math.round(match[6])];
            }

            return null;
        },

        _getBoundaries: function () {
            return this._boundaries = [ // negative numbers because we scroll negative
                -this.view.scrollWidth + this.view.clientWidth,
                -this.view.scrollHeight + this.view.clientHeight
            ];
        },

        _observePosition: function (cond) {
            var obeservationLoop, lastPos = this._getTransformPosition();

            if (cond === false) {
                (window.cancelAnimationFrame || window.webkitCancelAnimationFrame)(this._observeId);
            } else {
                obeservationLoop = function () {
                    var pos = this._getTransformPosition();

                    if (lastPos[0] !== pos[0] || lastPos[1] !== pos[1]) {
                        triggerEvent(this.view, 'scroll', {
                            pointerId: this._curPointer,
                            x: pos[0],
                            y: pos[1]
                        });
                    }
                    lastPos = pos;

                    this._observeId = (window.requestAnimationFrame || window.webkitRequestAnimationFrame)(obeservationLoop.bind(this));
                };

                this._observeId = (window.requestAnimationFrame || window.webkitRequestAnimationFrame)(obeservationLoop.bind(this))
            }

        },

    };

    global.ScrollView = Sv;

}(window.polyTouch || window));
<|endoftext|>


<commit_before>/* eslint-disable max-len, no-console */
import 'newrelic'
import 'babel-polyfill'
import 'isomorphic-fetch'
import { values } from 'lodash'

function handleZlibError(error) {
  if (error.code === 'Z_BUF_ERROR') {
    console.error(error)
  } else {
    console.log(error.stack)
    throw error
  }
}
process.on('uncaughtException', handleZlibError)

import Honeybadger from 'honeybadger'
import express from 'express'
import morgan from 'morgan'
import librato from 'librato-node'
import path from 'path'
import fs from 'fs'
import React from 'react'
import Helmet from 'react-helmet'
import { renderToString } from 'react-dom/server'
import { createMemoryHistory, match, RouterContext } from 'react-router'
import { Provider } from 'react-redux'
import { createElloStore } from './store'
import { updateStrings as updateTimeAgoStrings } from './vendor/time_ago_in_words'
import { addOauthRoute, currentToken } from '../oauth'
import createRoutes from './routes'
import { replace, syncHistoryWithStore } from 'react-router-redux'
import { serverRoot } from './sagas'

// load env vars first
require('dotenv').load({ silent: process.env.NODE_ENV === 'production' })
global.ENV = require('../env')
updateTimeAgoStrings({ about: '' })

const app = express()

// Honeybadger "before everything" middleware
app.use(Honeybadger.requestHandler);

// Log requests with Morgan
app.use(morgan('combined'))

// Send stats to Librato
librato.configure({ email: process.env.LIBRATO_EMAIL,
                    token: process.env.LIBRATO_TOKEN })
librato.start()
app.use(librato.middleware())

librato.on('error', (err) => {
  console.log('ELLO LIBRATO ERROR', err)
})

const indexStr = fs.readFileSync(path.join(__dirname, '../public/index.html'), 'utf-8')

// Wire up OAuth route
addOauthRoute(app)

// Assets
app.use(express.static('public', { maxAge: '1y', index: false }))
app.use('/static', express.static('public/static', { maxAge: '1y' }))

// Return promises for initial loads
function preRender(renderProps, store, sagaTask) {
  const promises = renderProps.components.map(component => ((component && component.preRender) ? component.preRender(store, renderProps) : null)).filter(component => !!component)

  promises.push(sagaTask.done)
  return Promise.all(promises)
}

function renderFromServer(req, res) {
  currentToken().then((token) => {
    const memoryHistory = createMemoryHistory(req.originalUrl)
    const store = createElloStore(memoryHistory, {
      authentication: {
        accessToken: token.token.access_token,
        isLoggedIn: false,
      },
    })
    const routes = createRoutes(store)
    const history = syncHistoryWithStore(memoryHistory, store)
    const sagaTask = store.runSaga(serverRoot)

    match({ history, routes, location: req.url }, (error, redirectLocation, renderProps) => {
      // populate the router store object for initial render
      if (error) {
        console.log('ELLO MATCH ERROR', error)
      } else if (redirectLocation) {
        console.log('ELLO HANDLE REDIRECT', redirectLocation)
        res.redirect(redirectLocation.pathname)
        return
      } else if (!renderProps) {
        console.log('NO RENDER PROPS')
        return
      }

      store.dispatch(replace(renderProps.location.pathname))

      const InitialComponent = (
        <Provider store={store}>
          <RouterContext {...renderProps} />
        </Provider>
      )

      preRender(renderProps, store, sagaTask).then(() => {
        const componentHTML = renderToString(InitialComponent)
        const head = Helmet.rewind()
        const state = store.getState()
        const initialStateTag = `<script id="initial-state">window.__INITIAL_STATE__ = ${JSON.stringify(state)}</script>`
        // Add helmet's stuff after the last statically rendered meta tag
        const html = indexStr.replace(
          'content="ie=edge">',
          `content="ie=edge">${head.title.toString()} ${head.meta.toString()} ${head.link.toString()}`
        ).replace('

', `<div id="root">${componentHTML}</div>${initialStateTag}`)
        res.send(html)
      }).catch((err) => {
        // this will give you a js error like:
        // `window is not defined`
        console.log('ELLO CATCH ERROR', err)
        Honeybadger.notify(err);
        res.status(500).end()
      })
      renderToString(InitialComponent)
      store.close()
    })
  })
}

const loggedInPaths = {
  following: /^\/following/,
  invitations: /^\/invitations/,
  settings: /^\/settings/,
  starred: /^\/starred/,
  notifications: /^\/notifications/,
}

export function canPrerenderRequest(req) {
  if (req.get('X-Skip-Prerender') === 'true') {
    return false
  }
  return values(loggedInPaths).every((regex) =>
    !req.url.match(regex)
  )
}

app.use((req, res) => {
  res.setHeader('Cache-Control', 'public, max-age=60');
  res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
  if (canPrerenderRequest(req)) {
    console.log('Serving pre-rendered markup for path', req.url)
    renderFromServer(req, res)
  } else {
    console.log('Serving static markup for path', req.url)
    res.send(indexStr)
  }
})

// Honeybadger "after everything" middleware
app.use(Honeybadger.errorHandler);

export default app

<commit_msg>Don't pre render authentications paths.<commit_after>/* eslint-disable max-len, no-console */
import 'newrelic'
import 'babel-polyfill'
import 'isomorphic-fetch'
import { values } from 'lodash'

function handleZlibError(error) {
  if (error.code === 'Z_BUF_ERROR') {
    console.error(error)
  } else {
    console.log(error.stack)
    throw error
  }
}
process.on('uncaughtException', handleZlibError)

import Honeybadger from 'honeybadger'
import express from 'express'
import morgan from 'morgan'
import librato from 'librato-node'
import path from 'path'
import fs from 'fs'
import React from 'react'
import Helmet from 'react-helmet'
import { renderToString } from 'react-dom/server'
import { createMemoryHistory, match, RouterContext } from 'react-router'
import { Provider } from 'react-redux'
import { createElloStore } from './store'
import { updateStrings as updateTimeAgoStrings } from './vendor/time_ago_in_words'
import { addOauthRoute, currentToken } from '../oauth'
import createRoutes from './routes'
import { replace, syncHistoryWithStore } from 'react-router-redux'
import { serverRoot } from './sagas'

// load env vars first
require('dotenv').load({ silent: process.env.NODE_ENV === 'production' })
global.ENV = require('../env')
updateTimeAgoStrings({ about: '' })

const app = express()

// Honeybadger "before everything" middleware
app.use(Honeybadger.requestHandler);

// Log requests with Morgan
app.use(morgan('combined'))

// Send stats to Librato
librato.configure({ email: process.env.LIBRATO_EMAIL,
                    token: process.env.LIBRATO_TOKEN })
librato.start()
app.use(librato.middleware())

librato.on('error', (err) => {
  console.log('ELLO LIBRATO ERROR', err)
})

const indexStr = fs.readFileSync(path.join(__dirname, '../public/index.html'), 'utf-8')

// Wire up OAuth route
addOauthRoute(app)

// Assets
app.use(express.static('public', { maxAge: '1y', index: false }))
app.use('/static', express.static('public/static', { maxAge: '1y' }))

// Return promises for initial loads
function preRender(renderProps, store, sagaTask) {
  const promises = renderProps.components.map(component => ((component && component.preRender) ? component.preRender(store, renderProps) : null)).filter(component => !!component)

  promises.push(sagaTask.done)
  return Promise.all(promises)
}

function renderFromServer(req, res) {
  currentToken().then((token) => {
    const memoryHistory = createMemoryHistory(req.originalUrl)
    const store = createElloStore(memoryHistory, {
      authentication: {
        accessToken: token.token.access_token,
        isLoggedIn: false,
      },
    })
    const routes = createRoutes(store)
    const history = syncHistoryWithStore(memoryHistory, store)
    const sagaTask = store.runSaga(serverRoot)

    match({ history, routes, location: req.url }, (error, redirectLocation, renderProps) => {
      // populate the router store object for initial render
      if (error) {
        console.log('ELLO MATCH ERROR', error)
      } else if (redirectLocation) {
        console.log('ELLO HANDLE REDIRECT', redirectLocation)
        res.redirect(redirectLocation.pathname)
        return
      } else if (!renderProps) {
        console.log('NO RENDER PROPS')
        return
      }

      store.dispatch(replace(renderProps.location.pathname))

      const InitialComponent = (
        <Provider store={store}>
          <RouterContext {...renderProps} />
        </Provider>
      )

      preRender(renderProps, store, sagaTask).then(() => {
        const componentHTML = renderToString(InitialComponent)
        const head = Helmet.rewind()
        const state = store.getState()
        const initialStateTag = `<script id="initial-state">window.__INITIAL_STATE__ = ${JSON.stringify(state)}</script>`
        // Add helmet's stuff after the last statically rendered meta tag
        const html = indexStr.replace(
          'content="ie=edge">',
          `content="ie=edge">${head.title.toString()} ${head.meta.toString()} ${head.link.toString()}`
        ).replace('

', `<div id="root">${componentHTML}</div>${initialStateTag}`)
        res.send(html)
      }).catch((err) => {
        // this will give you a js error like:
        // `window is not defined`
        console.log('ELLO CATCH ERROR', err)
        Honeybadger.notify(err);
        res.status(500).end()
      })
      renderToString(InitialComponent)
      store.close()
    })
  })
}

const noPreRenderPaths = {
  following: /^\/following/,
  forgotPassword: /^\/forgot-password/,
  enter: /^\/enter/,
  invitations: /^\/invitations/,
  join: /^\/join/,
  settings: /^\/settings/,
  signup: /^\/signup/,
  starred: /^\/starred/,
  notifications: /^\/notifications/,
}

export function canPrerenderRequest(req) {
  if (req.get('X-Skip-Prerender') === 'true') {
    return false
  }
  return values(noPreRenderPaths).every((regex) =>
    !req.url.match(regex)
  )
}

app.use((req, res) => {
  res.setHeader('Cache-Control', 'public, max-age=60');
  res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
  if (canPrerenderRequest(req)) {
    console.log('Serving pre-rendered markup for path', req.url)
    renderFromServer(req, res)
  } else {
    console.log('Serving static markup for path', req.url)
    res.send(indexStr)
  }
})

// Honeybadger "after everything" middleware
app.use(Honeybadger.errorHandler);

export default app

<|endoftext|>


<commit_before>import express from 'express'
import shrinkRay from 'shrink-ray'
import { cacheControl, strictTransportSecurity } from './middleware'
import { root } from './routes'

// likely our proxy will handle compression, cache-control, etc. these are healthy defaults
const app = express()
app.disable('x-powered-by')
app.use(shrinkRay())
app.use(strictTransportSecurity())
app.use(express.static('build/public', { maxAge: '365d' }))
app.use(cacheControl())
app.use('/sw.js', express.static('build/public/sw.js'))
app.use('*', root)

export default app
<commit_msg>set max age<commit_after>import express from 'express'
import shrinkRay from 'shrink-ray'
import { cacheControl, strictTransportSecurity } from './middleware'
import { root } from './routes'

// likely our proxy will handle compression, cache-control, etc. these are healthy defaults
const app = express()
app.disable('x-powered-by')
app.use(shrinkRay())
app.use(strictTransportSecurity())
app.use(express.static('build/public', { maxAge: '1y' }))
app.use(cacheControl())
app.use('/sw.js', express.static('build/public/sw.js'))
app.use('*', root)

export default app
<|endoftext|>