<commit_before>(function () {
    'use strict';
    /**
     * 'Share' widget for GNU social
     * http://code.chromic.org/project/view/2/
     *
     * We make a few assumptions about the target instance:
     *   1) The API root is in the default location
     *   2) Fancy URLs are enabled
     *   3) CORS is allowed
     *   4) The Bookmark plugin is enabled
     *
     * If 1), 3) or 4) are wrong, we fall back to a regular
     * notice (instead of a bookmark notice)
     *
     * If 2) is wrong the user will be directed to a 404 :(
     */

    // TODO: input sanitation [1], [2]
    // TODO: server-side fallback if JS is disabled

    var createForm,
        bindClicks,
        frm,
        shareAsNotice,
        shareAsBookmark,
        extractURLParams,
        shareURL,
        shareTitle;

    shareAsNotice = function (title, url, domain) {
        window.location.href = 'http://' + domain + '/notice/new?status_textarea=' + title + ' ' + url; // [1]
    };

    shareAsBookmark = function (title, url, domain) {
        window.location.href = 'http://' + domain + '/main/bookmark/new?url=' + url + '&title=' + title; // [2]
    };

    extractURLParams = function (queryStr) {
        var parts = queryStr.substr(1).split('&'),
            i, len, keyVal, params = {};

        for (i = 0, len = parts.length; i < len; i += 1) {
            keyVal = parts[i].split('=');
            params[keyVal[0]] = keyVal[1];
        }

        return params;
    };

    // Create the form that we'll re-use throughout the page
    createForm = function () {
        var err = document.createElement('div');
        err.setAttribute('class', 'gs-share-err');
        err.setAttribute('tabindex', '-1');
        err.textContent = 'The account id provided is invalid';

        frm = document.createElement('form');

        frm.setAttribute('class', 'gs-share-form');
        frm.setAttribute('tabindex', '-1');
        frm.setAttribute('aria-hidden', 'true');

        frm.innerHTML = 'Your account ID:' +
            '<input type="text" id="gs-account" placeholder="user@example.org" />
' +
            '<input type="checkbox" checked id="gs-bookmark" /> Share as a bookmark
' +
            '<input type="submit" />';
        frm.insertBefore(err, frm.firstChild);

        // Submit handler
        frm.addEventListener('submit', function (e) {
            e.preventDefault();

            var accountParts = document.getElementById('gs-account').value.split('@'),
                username, domain, xhr, bookmarkURL;

            if (accountParts.length === 2) {
                err.setAttribute('aria-hidden', 'true');

                username = accountParts[0];
                domain = accountParts[1];
                bookmarkURL = 'http://' + domain + '/api/bookmarks/' + username + '.json';

                // Try bookmark
                if (document.getElementById('gs-bookmark').checked) {
                    xhr = new XMLHttpRequest();

                    xhr.onreadystatechange = function () {
                        if (xhr.readyState === 4) {
                            if (xhr.status === 200) { // Success
                                shareAsBookmark(shareTitle, shareURL, domain);
                            } else { // Failure, fallback to regular notice
                                shareAsNotice(shareTitle, shareURL, domain);
                            }
                        }
                    };

                    xhr.open('GET', bookmarkURL, true);
                    xhr.send();
                } else { // Regular notice
                    shareAsNotice(shareTitle, shareURL, domain);
                }
            } else { // Invalid account id
                err.setAttribute('aria-hidden', 'false');
                err.focus();
            }
        });

        // Keydown handler
        frm.addEventListener('keydown', function (e) {
            if (e.keyCode === 27) { // Escape key closes the dialog
                frm.parentElement.getElementsByClassName('js-gs-share')[0].focus();
                frm.setAttribute('aria-hidden', 'true');
            }
        });

        document.body.appendChild(frm);
    };

    bindClicks = function () {
        document.addEventListener('click', function (e) {
            var target = e.target,
                urlParams;

            // Don't do anything on right/middle click or if ctrl or shift was pressed while left-clicking
            if (!e.button && !e.ctrlKey && !e.shiftKey) {
                if (target.className.match(/js-gs-share/)) {
                    e.preventDefault();

                    // Check for submission information in href first
                    if (target.search !== undefined) {
                        urlParams = extractURLParams(target.search);
                        shareURL   = urlParams.url;
                        shareTitle = urlParams.title;
                    } else { // If it's not there, try data-* attributes. If not, use current document url and title
                        shareURL   = target.getAttribute('data-url') || window.location.href;
                        shareTitle = target.getAttribute('data-title') || document.title;
                    }

                    // Move form after the clicked link
                    target.parentNode.appendChild(frm);

                    // Show form
                    frm.setAttribute('aria-hidden', 'false');

                    // Focus on form
                    frm.focus();
                } else if (!frm.contains(target)) {
                    frm.setAttribute('aria-hidden', 'true');
                }
            }
        });
    };

    createForm();
    bindClicks();
}());
<commit_msg>Adds missing 'aria-hidden' attr on error message<commit_after>(function () {
    'use strict';
    /**
     * 'Share' widget for GNU social
     * http://code.chromic.org/project/view/2/
     *
     * We make a few assumptions about the target instance:
     *   1) The API root is in the default location
     *   2) Fancy URLs are enabled
     *   3) CORS is allowed
     *   4) The Bookmark plugin is enabled
     *
     * If 1), 3) or 4) are wrong, we fall back to a regular
     * notice (instead of a bookmark notice)
     *
     * If 2) is wrong the user will be directed to a 404 :(
     */

    // TODO: input sanitation [1], [2]
    // TODO: server-side fallback if JS is disabled

    var createForm,
        bindClicks,
        frm,
        shareAsNotice,
        shareAsBookmark,
        extractURLParams,
        shareURL,
        shareTitle;

    shareAsNotice = function (title, url, domain) {
        window.location.href = 'http://' + domain + '/notice/new?status_textarea=' + title + ' ' + url; // [1]
    };

    shareAsBookmark = function (title, url, domain) {
        window.location.href = 'http://' + domain + '/main/bookmark/new?url=' + url + '&title=' + title; // [2]
    };

    extractURLParams = function (queryStr) {
        var parts = queryStr.substr(1).split('&'),
            i, len, keyVal, params = {};

        for (i = 0, len = parts.length; i < len; i += 1) {
            keyVal = parts[i].split('=');
            params[keyVal[0]] = keyVal[1];
        }

        return params;
    };

    // Create the form that we'll re-use throughout the page
    createForm = function () {
        var err = document.createElement('div');
        err.setAttribute('class', 'gs-share-err');
        err.setAttribute('tabindex', '-1');
        err.setAttribute('aria-hidden', 'true');
        err.textContent = 'The account id provided is invalid';

        frm = document.createElement('form');

        frm.setAttribute('class', 'gs-share-form');
        frm.setAttribute('tabindex', '-1');
        frm.setAttribute('aria-hidden', 'true');

        frm.innerHTML = 'Your account ID:' +
            '<input type="text" id="gs-account" placeholder="user@example.org" />
' +
            '<input type="checkbox" checked id="gs-bookmark" /> Share as a bookmark
' +
            '<input type="submit" />';
        frm.insertBefore(err, frm.firstChild);

        // Submit handler
        frm.addEventListener('submit', function (e) {
            e.preventDefault();

            var accountParts = document.getElementById('gs-account').value.split('@'),
                username, domain, xhr, bookmarkURL;

            if (accountParts.length === 2) {
                err.setAttribute('aria-hidden', 'true');

                username = accountParts[0];
                domain = accountParts[1];
                bookmarkURL = 'http://' + domain + '/api/bookmarks/' + username + '.json';

                // Try bookmark
                if (document.getElementById('gs-bookmark').checked) {
                    xhr = new XMLHttpRequest();

                    xhr.onreadystatechange = function () {
                        if (xhr.readyState === 4) {
                            if (xhr.status === 200) { // Success
                                shareAsBookmark(shareTitle, shareURL, domain);
                            } else { // Failure, fallback to regular notice
                                shareAsNotice(shareTitle, shareURL, domain);
                            }
                        }
                    };

                    xhr.open('GET', bookmarkURL, true);
                    xhr.send();
                } else { // Regular notice
                    shareAsNotice(shareTitle, shareURL, domain);
                }
            } else { // Invalid account id
                err.setAttribute('aria-hidden', 'false');
                err.focus();
            }
        });

        // Keydown handler
        frm.addEventListener('keydown', function (e) {
            if (e.keyCode === 27) { // Escape key closes the dialog
                frm.parentElement.getElementsByClassName('js-gs-share')[0].focus();
                frm.setAttribute('aria-hidden', 'true');
            }
        });

        document.body.appendChild(frm);
    };

    bindClicks = function () {
        document.addEventListener('click', function (e) {
            var target = e.target,
                urlParams;

            // Don't do anything on right/middle click or if ctrl or shift was pressed while left-clicking
            if (!e.button && !e.ctrlKey && !e.shiftKey) {
                if (target.className.match(/js-gs-share/)) {
                    e.preventDefault();

                    // Check for submission information in href first
                    if (target.search !== undefined) {
                        urlParams = extractURLParams(target.search);
                        shareURL   = urlParams.url;
                        shareTitle = urlParams.title;
                    } else { // If it's not there, try data-* attributes. If not, use current document url and title
                        shareURL   = target.getAttribute('data-url') || window.location.href;
                        shareTitle = target.getAttribute('data-title') || document.title;
                    }

                    // Move form after the clicked link
                    target.parentNode.appendChild(frm);

                    // Show form
                    frm.setAttribute('aria-hidden', 'false');

                    // Focus on form
                    frm.focus();
                } else if (!frm.contains(target)) {
                    frm.setAttribute('aria-hidden', 'true');
                }
            }
        });
    };

    createForm();
    bindClicks();
}());
<|endoftext|>


<commit_before>if (typeof _DONATION_PAGE_APP_ === 'undefined') { var _DONATION_PAGE_APP_ = {}; }

var _stripePaymentsBaseUrl = (typeof _stripePaymentsBaseUrl === 'undefined') ? '' : _stripePaymentsBaseUrl;

var _DonationPage_handleDonate;

// Polyfill startsWith
if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(searchString, position) {
    position = position || 0;
    return this.indexOf(searchString, position) === position;
  };
}

function init(publishableKey, organizationDisplayName, applyPayEnabledConfigured) {
	"use strict";
	var app = _DONATION_PAGE_APP_;

	//noinspection JSUnresolvedVariable
	Stripe.setPublishableKey(publishableKey);
	var collectOccupationEnabled = (typeof app.collectOccupationEnabled === 'undefined') ? true : app.collectOccupationEnabled;
	var collectOccupationThreshold = (typeof app.collectOccupationThreshold === 'undefined') ? 100 : app.collectOccupationThreshold;
	var donationLimit = (typeof app.donationLimit === 'undefined') ? -1 : app.donationLimit;
	var payDuesPage = (typeof app.payDuesPage === 'undefined') ? false : app.payDuesPage;
	var pagePurpose = (typeof app.pagePurpose === 'undefined') ? "donation" : app.pagePurpose;
	var clientLoggingEnabled = (typeof app.clientLoggingEnabled === 'undefined') ? true : app.clientLoggingEnabled;

	var paypalEnabled = (typeof app.paypalEnabled === 'undefined') ? false : app.paypalEnabled;

	var submittedAmount = 0;
	var submittedAmountStr = "";
	var applePayEnabled = false;
	var occupation = "";

	function log(logObject) {
		setTimeout(function() {
			if (clientLoggingEnabled) {
				$.ajax({
					type: 'POST',
					url: _stripePaymentsBaseUrl + 'log',
					data: JSON.stringify(logObject),
					contentType: "application/json",
					dataType: 'json'
				}).fail(function(jqXHR, textStatus, errorThrown) {
					if (window.console) {
						console.log(textStatus + ": " + errorThrown);
						console.log("Failed to log: ");
						console.log(logObject);
					}
				});
			}
			if (window.console) {
				console.log(logObject);
			}
		}, 10);
	}

	log('Initializing...');

	function isEmpty(str) {
	    return (!str || 0 === str.length);
	}

	function doSuccess(amount, email) {
		var loc = "successfulPayment?amount=" + amount;
		if (email) {
			loc += "&email=" + email;
		}
		window.location = loc;
	}

	function errorDialog(errorMessage) {
		hideProcessingPayment();
		$("#errorMessage").text(errorMessage);
		$('#errorDialog').modal();
		hideMultiPay();
		$('.donation-selection').show();
	}

	var handler = StripeCheckout.configure({
		key: publishableKey,
		image: 'https://stripe.com/img/documentation/checkout/marketplace.png',
		locale: 'auto',
		currency: 'usd',
		billingAddress: true,
		allowRememberMe: true,
		token: function(token) {
			// You can access the token ID with `token.id`.
			// Get the token ID to your server-side code for use.
			var param = {
				id: token.id,
				currency: 'usd',
				amount: submittedAmount,
				description: token.email,
				occupation: occupation,
				collectOccupationEnabled: collectOccupationEnabled,
				pagePurpose: pagePurpose,
				token: token
			};
			// console.log(param);
			$('#processingPaymentDialog').modal();
			$.ajax({
				type: 'POST',
				url: _stripePaymentsBaseUrl + 'submitPayment',
				data: JSON.stringify(param),
				contentType: "application/json",
				dataType: 'json'
			}).done(function(data) {
				hideProcessingPayment();
				// console.log(data);
				if (!data.error) {
					doSuccess(data.amount, data.email);
				} else {
					errorDialog(data.errorMessage);
				}
			}).fail(function(jqXHR, textStatus, errorThrown) {
				hideProcessingPayment();
				errorDialog(textStatus + ": " + errorThrown);
			}).always(function() {
				hideProcessingPayment();
			});
		}
	});

	function beginApplePay() {
		log('Beginning Apple Pay...');
		hideMultiPay();
		var shippingContactFields = ['email', 'name', 'postalAddress'];
		/* Uncomment this if we don't want to require address for paying dues
		if (payDuesPage) {
			shippingContactFields = ['email', 'name'];
		}
		*/
		var paymentRequest = {
			requiredShippingContactFields: shippingContactFields,
			countryCode: 'US',
			currencyCode: 'USD',
			total: {
				label: organizationDisplayName,
				amount: submittedAmountStr
			}
		};
		log('Payment request:');
		log(paymentRequest);
		var session = Stripe.applePay.buildSession(paymentRequest,
			function(result, completion) {
				log('Apple Pay result:');
				log(result);
				$('#processingPaymentDialog').modal();
				var param = {
					id: result.token.id,
					amount: submittedAmount,
					currency: 'usd',
					description: result.shippingContact.emailAddress,
					occupation: occupation,
					collectOccupationEnabled: collectOccupationEnabled,
					pagePurpose: pagePurpose,
					applePayResult: result,
					logData: JSON.stringify(result)
				};
				$.ajax({
						type: 'POST',
						url: _stripePaymentsBaseUrl + 'submitPayment',
						data: JSON.stringify(param),
						contentType: "application/json",
						dataType: 'json'
				}).done(function(data) {
					// You can now redirect the user to a receipt page, etc.
					if (!data.error) {
						completion(ApplePaySession.STATUS_SUCCESS);
						doSuccess(data.amount, data.email);
					} else {
						completion(ApplePaySession.STATUS_FAILURE);
						errorDialog(data.errorMessage);
					}
				}).fail(function(x, textStatus, errorThrown) {
					completion(ApplePaySession.STATUS_FAILURE);
					errorDialog(textStatus + ": " + errorThrown);
				}).always(function() {
					hideProcessingPayment();
				});

			}, function(error) {
				hideProcessingPayment();
				// console.log(error.message);
				errorDialog(error.message);
			});

		session.begin();
	}

	function doCreditCardDonate() {
		hideMultiPay();
		handler.open({
			name: organizationDisplayName,
			description: 'Donate $' + submittedAmountStr,
			amount: submittedAmount
		});
		log('Doing credit card donate');
	}

	function hideMultiPay() {
		$('#multi-pay-options').modal('hide');
	}

	function hideProcessingPayment() {
		$('#processingPaymentDialog').modal('hide');
	}

	function handleDonate(amount, amountStr) {
		// Open Checkout with further options:
		submittedAmount = amount;
		submittedAmountStr = amountStr;

		occupation = $('#occupationInput').val();
		var shouldReturn = false;
		log('Selected amount: $' + submittedAmountStr);

		if (collectOccupationEnabled && submittedAmount > (collectOccupationThreshold * 100) && isEmpty(occupation)) {
			$('#alertOccupationText').text("Please provide your occupation");
			$('#alertOccupation').removeClass("hidden");
			shouldReturn = true;
			log('Occupation not provided');
		} else {
			$('#alertOccupation').addClass("hidden");
			if (!isEmpty(occupation)) {
				log("Occupation: " + occupation);
			}
		}
		if (donationLimit > 0 && submittedAmount > (donationLimit * 100)) {
			var donationLimitErrorText = "Donation exceeds limit of $" + donationLimit + ".";
			$('#alertCustomDonationText').text(donationLimitErrorText);
			$('#alertCustomDonation').removeClass("hidden");
			shouldReturn = true;
			log(donationLimitErrorText);
		} else {
			$('#alertCustomDonation').addClass("hidden");
		}
		if (shouldReturn) {
			return;
		}

		if (!applePayEnabled && !paypalEnabled) {
			doCreditCardDonate();
		} else {
			$('#donationAmountAlert').text("Amount: $" + submittedAmountStr);
			//$('.donation-selection').hide();
			$('#multi-pay-options').modal();
			log('Showing multiple pay options');
		}
	}

	_DonationPage_handleDonate = handleDonate;

	$('#cancel-button').click(function() {
		$('.donation-selection').show();
		hideMultiPay();
	});

	$('#credit-pay-button').click(doCreditCardDonate);

	$('#apple-pay-button').click(beginApplePay);

	$('#donateButton_custom').click(function(e) {
		var amount = document.getElementById("donationAmount").value;
		if (amount) {
			handleDonate(parseInt(amount) * 100, amount);
		}
		e.preventDefault();
	});

	// Close Checkout on page navigation:
	window.addEventListener('popstate', function() {
		handler.close();
	});

	if (applyPayEnabledConfigured) {
		// Apple Pay
		Stripe.applePay.checkAvailability(function(available) {
			if (available) {
				applePayEnabled = true;
				log("Apple Pay enabled");
				//document.getElementById('apple-pay-button').style.display = 'block';
			} else {
				$('.apple-pay-container').hide();
				log("Apple Pay not available");
			}
		});
	} else {
		$('.apple-pay-container').hide();
	}

	if (paypalEnabled) {
		paypal.Button.render({
			// Set your environment
			env: app.paypalSandbox ? 'sandbox' : 'production',

			style: {
				label: 'checkout', // checkout || credit
				size: 'medium',    // tiny | small | medium
				shape: 'pill',     // pill | rect
				color: 'blue'      // gold | blue | silver
			},

			payment: function() {
				return paypal.request.post(_stripePaymentsBaseUrl + 'paypalCreatePayment', {amount: submittedAmountStr})
					.then(function(res) {
						return res.payToken;
					});
			},

			onAuthorize: function(data, actions) {
				return paypal.request.post(_stripePaymentsBaseUrl + 'paypalExecutePayment', {
					payToken: data.paymentID,
					payerId: data.payerID,
					occupation: occupation,
					pagePurpose: pagePurpose
				}).then(function(data) {
					log("Paypal execute successful, redirecting to success page.");
					doSuccess(submittedAmount, data.payer.payer_info.email);
					//document.querySelector('#paypal-button-container').innerText = 'Payment Complete!';
				});
				/*.error(function(jqXHR, textStatus, errorThrown) {
				 if (console) {
				 console.log(jqXHR);
				 console.log(textStatus);
				 console.log(errorThrown);
				 }
				 }); */
			}
		}, '#paypal-button-container');
	}

	log('Initialization complete');
}
$(document).ready(function() {
	$.ajax({
		type: 'GET',
		url: _stripePaymentsBaseUrl + 'getConfig',
		contentType: "application/json",
		dataType: 'json',
		success: function(data) {
			//console.log(data);
			if (data.publishableKey.startsWith("pk_test")) {
				console.log("TEST MODE - No money will be transferred");
				$('#alertTop').addClass('alert').addClass('alert-danger').text('! TEST MODE - No money will be transferred !');
			}
			init(data.publishableKey, data.organizationDisplayName, data.applyPayEnabled);
		},
		error: function(x, textStatus, errorThrown) {
			alert("Initial page load failed");
			console.log("textStatus: " + textStatus);
			console.log("errorThrown: " + errorThrown);
		}
	});
});
<commit_msg>Include page purpose in the completion URL<commit_after>if (typeof _DONATION_PAGE_APP_ === 'undefined') { var _DONATION_PAGE_APP_ = {}; }

var _stripePaymentsBaseUrl = (typeof _stripePaymentsBaseUrl === 'undefined') ? '' : _stripePaymentsBaseUrl;

var _DonationPage_handleDonate;

// Polyfill startsWith
if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(searchString, position) {
    position = position || 0;
    return this.indexOf(searchString, position) === position;
  };
}

function init(publishableKey, organizationDisplayName, applyPayEnabledConfigured) {
	"use strict";
	var app = _DONATION_PAGE_APP_;

	//noinspection JSUnresolvedVariable
	Stripe.setPublishableKey(publishableKey);
	var collectOccupationEnabled = (typeof app.collectOccupationEnabled === 'undefined') ? true : app.collectOccupationEnabled;
	var collectOccupationThreshold = (typeof app.collectOccupationThreshold === 'undefined') ? 100 : app.collectOccupationThreshold;
	var donationLimit = (typeof app.donationLimit === 'undefined') ? -1 : app.donationLimit;
	var payDuesPage = (typeof app.payDuesPage === 'undefined') ? false : app.payDuesPage;
	var pagePurpose = (typeof app.pagePurpose === 'undefined') ? "donation" : app.pagePurpose;
	var clientLoggingEnabled = (typeof app.clientLoggingEnabled === 'undefined') ? true : app.clientLoggingEnabled;

	var paypalEnabled = (typeof app.paypalEnabled === 'undefined') ? false : app.paypalEnabled;

	var submittedAmount = 0;
	var submittedAmountStr = "";
	var applePayEnabled = false;
	var occupation = "";

	function log(logObject) {
		setTimeout(function() {
			if (clientLoggingEnabled) {
				$.ajax({
					type: 'POST',
					url: _stripePaymentsBaseUrl + 'log',
					data: JSON.stringify(logObject),
					contentType: "application/json",
					dataType: 'json'
				}).fail(function(jqXHR, textStatus, errorThrown) {
					if (window.console) {
						console.log(textStatus + ": " + errorThrown);
						console.log("Failed to log: ");
						console.log(logObject);
					}
				});
			}
			if (window.console) {
				console.log(logObject);
			}
		}, 10);
	}

	log('Initializing...');

	function isEmpty(str) {
	    return (!str || 0 === str.length);
	}

	function doSuccess(amount, email) {
		var loc = "successfulPayment?amount=" + amount;
		if (email) {
			loc += "&email=" + email;
		}
		if (pagePurpose) {
			loc += "&purpose=" + pagePurpose;
		}
		window.location = loc;
	}

	function errorDialog(errorMessage) {
		hideProcessingPayment();
		$("#errorMessage").text(errorMessage);
		$('#errorDialog').modal();
		hideMultiPay();
		$('.donation-selection').show();
	}

	var handler = StripeCheckout.configure({
		key: publishableKey,
		image: 'https://stripe.com/img/documentation/checkout/marketplace.png',
		locale: 'auto',
		currency: 'usd',
		billingAddress: true,
		allowRememberMe: true,
		token: function(token) {
			// You can access the token ID with `token.id`.
			// Get the token ID to your server-side code for use.
			var param = {
				id: token.id,
				currency: 'usd',
				amount: submittedAmount,
				description: token.email,
				occupation: occupation,
				collectOccupationEnabled: collectOccupationEnabled,
				pagePurpose: pagePurpose,
				token: token
			};
			// console.log(param);
			$('#processingPaymentDialog').modal();
			$.ajax({
				type: 'POST',
				url: _stripePaymentsBaseUrl + 'submitPayment',
				data: JSON.stringify(param),
				contentType: "application/json",
				dataType: 'json'
			}).done(function(data) {
				hideProcessingPayment();
				// console.log(data);
				if (!data.error) {
					doSuccess(data.amount, data.email);
				} else {
					errorDialog(data.errorMessage);
				}
			}).fail(function(jqXHR, textStatus, errorThrown) {
				hideProcessingPayment();
				errorDialog(textStatus + ": " + errorThrown);
			}).always(function() {
				hideProcessingPayment();
			});
		}
	});

	function beginApplePay() {
		log('Beginning Apple Pay...');
		hideMultiPay();
		var shippingContactFields = ['email', 'name', 'postalAddress'];
		/* Uncomment this if we don't want to require address for paying dues
		if (payDuesPage) {
			shippingContactFields = ['email', 'name'];
		}
		*/
		var paymentRequest = {
			requiredShippingContactFields: shippingContactFields,
			countryCode: 'US',
			currencyCode: 'USD',
			total: {
				label: organizationDisplayName,
				amount: submittedAmountStr
			}
		};
		log('Payment request:');
		log(paymentRequest);
		var session = Stripe.applePay.buildSession(paymentRequest,
			function(result, completion) {
				log('Apple Pay result:');
				log(result);
				$('#processingPaymentDialog').modal();
				var param = {
					id: result.token.id,
					amount: submittedAmount,
					currency: 'usd',
					description: result.shippingContact.emailAddress,
					occupation: occupation,
					collectOccupationEnabled: collectOccupationEnabled,
					pagePurpose: pagePurpose,
					applePayResult: result,
					logData: JSON.stringify(result)
				};
				$.ajax({
						type: 'POST',
						url: _stripePaymentsBaseUrl + 'submitPayment',
						data: JSON.stringify(param),
						contentType: "application/json",
						dataType: 'json'
				}).done(function(data) {
					// You can now redirect the user to a receipt page, etc.
					if (!data.error) {
						completion(ApplePaySession.STATUS_SUCCESS);
						doSuccess(data.amount, data.email);
					} else {
						completion(ApplePaySession.STATUS_FAILURE);
						errorDialog(data.errorMessage);
					}
				}).fail(function(x, textStatus, errorThrown) {
					completion(ApplePaySession.STATUS_FAILURE);
					errorDialog(textStatus + ": " + errorThrown);
				}).always(function() {
					hideProcessingPayment();
				});

			}, function(error) {
				hideProcessingPayment();
				// console.log(error.message);
				errorDialog(error.message);
			});

		session.begin();
	}

	function doCreditCardDonate() {
		hideMultiPay();
		handler.open({
			name: organizationDisplayName,
			description: 'Donate $' + submittedAmountStr,
			amount: submittedAmount
		});
		log('Doing credit card donate');
	}

	function hideMultiPay() {
		$('#multi-pay-options').modal('hide');
	}

	function hideProcessingPayment() {
		$('#processingPaymentDialog').modal('hide');
	}

	function handleDonate(amount, amountStr) {
		// Open Checkout with further options:
		submittedAmount = amount;
		submittedAmountStr = amountStr;

		occupation = $('#occupationInput').val();
		var shouldReturn = false;
		log('Selected amount: $' + submittedAmountStr);

		if (collectOccupationEnabled && submittedAmount > (collectOccupationThreshold * 100) && isEmpty(occupation)) {
			$('#alertOccupationText').text("Please provide your occupation");
			$('#alertOccupation').removeClass("hidden");
			shouldReturn = true;
			log('Occupation not provided');
		} else {
			$('#alertOccupation').addClass("hidden");
			if (!isEmpty(occupation)) {
				log("Occupation: " + occupation);
			}
		}
		if (donationLimit > 0 && submittedAmount > (donationLimit * 100)) {
			var donationLimitErrorText = "Donation exceeds limit of $" + donationLimit + ".";
			$('#alertCustomDonationText').text(donationLimitErrorText);
			$('#alertCustomDonation').removeClass("hidden");
			shouldReturn = true;
			log(donationLimitErrorText);
		} else {
			$('#alertCustomDonation').addClass("hidden");
		}
		if (shouldReturn) {
			return;
		}

		if (!applePayEnabled && !paypalEnabled) {
			doCreditCardDonate();
		} else {
			$('#donationAmountAlert').text("Amount: $" + submittedAmountStr);
			//$('.donation-selection').hide();
			$('#multi-pay-options').modal();
			log('Showing multiple pay options');
		}
	}

	_DonationPage_handleDonate = handleDonate;

	$('#cancel-button').click(function() {
		$('.donation-selection').show();
		hideMultiPay();
	});

	$('#credit-pay-button').click(doCreditCardDonate);

	$('#apple-pay-button').click(beginApplePay);

	$('#donateButton_custom').click(function(e) {
		var amount = document.getElementById("donationAmount").value;
		if (amount) {
			handleDonate(parseInt(amount) * 100, amount);
		}
		e.preventDefault();
	});

	// Close Checkout on page navigation:
	window.addEventListener('popstate', function() {
		handler.close();
	});

	if (applyPayEnabledConfigured) {
		// Apple Pay
		Stripe.applePay.checkAvailability(function(available) {
			if (available) {
				applePayEnabled = true;
				log("Apple Pay enabled");
				//document.getElementById('apple-pay-button').style.display = 'block';
			} else {
				$('.apple-pay-container').hide();
				log("Apple Pay not available");
			}
		});
	} else {
		$('.apple-pay-container').hide();
	}

	if (paypalEnabled) {
		paypal.Button.render({
			// Set your environment
			env: app.paypalSandbox ? 'sandbox' : 'production',

			style: {
				label: 'checkout', // checkout || credit
				size: 'medium',    // tiny | small | medium
				shape: 'pill',     // pill | rect
				color: 'blue'      // gold | blue | silver
			},

			payment: function() {
				return paypal.request.post(_stripePaymentsBaseUrl + 'paypalCreatePayment', {amount: submittedAmountStr})
					.then(function(res) {
						return res.payToken;
					});
			},

			onAuthorize: function(data, actions) {
				return paypal.request.post(_stripePaymentsBaseUrl + 'paypalExecutePayment', {
					payToken: data.paymentID,
					payerId: data.payerID,
					occupation: occupation,
					pagePurpose: pagePurpose
				}).then(function(data) {
					log("Paypal execute successful, redirecting to success page.");
					doSuccess(submittedAmount, data.payer.payer_info.email);
					//document.querySelector('#paypal-button-container').innerText = 'Payment Complete!';
				});
				/*.error(function(jqXHR, textStatus, errorThrown) {
				 if (console) {
				 console.log(jqXHR);
				 console.log(textStatus);
				 console.log(errorThrown);
				 }
				 }); */
			}
		}, '#paypal-button-container');
	}

	log('Initialization complete');
}
$(document).ready(function() {
	$.ajax({
		type: 'GET',
		url: _stripePaymentsBaseUrl + 'getConfig',
		contentType: "application/json",
		dataType: 'json',
		success: function(data) {
			//console.log(data);
			if (data.publishableKey.startsWith("pk_test")) {
				console.log("TEST MODE - No money will be transferred");
				$('#alertTop').addClass('alert').addClass('alert-danger').text('! TEST MODE - No money will be transferred !');
			}
			init(data.publishableKey, data.organizationDisplayName, data.applyPayEnabled);
		},
		error: function(x, textStatus, errorThrown) {
			alert("Initial page load failed");
			console.log("textStatus: " + textStatus);
			console.log("errorThrown: " + errorThrown);
		}
	});
});
<|endoftext|>


<commit_before>define([
	'app/modules/dbControllers',
	'app/services/messageService'
], function (db) {
	'use strict';

	db.controller('LoginPanelCtrl', ['$scope', '$http', 'MessageService', function ($scope, $http, messageService) {
		$scope.method = 1;

		$scope.switchMethod = function (method) {
			$scope.method = method;
		};
	}]);
});
<commit_msg>stupid bug<commit_after><|endoftext|>


<commit_before>import Button from './Button';
import { ConfirmModal, LoginModal } from './modals';
import DataTable from './DataTable';

export {
  Button,
  ConfirmModal,
  DataTable,
  LoginModal,
};
<commit_msg>Added SearchBar. Sorting fetish incl.<commit_after>import { ConfirmModal, LoginModal } from './modals';
import Button from './Button';
import DataTable from './DataTable';
import SearchBar from './SearchBar';

export {
  Button,
  ConfirmModal,
  DataTable,
  LoginModal,
  SearchBar,
};
<|endoftext|>


<commit_before>function f (state) {
  if (typeof(state) === 'undefined'){
    state = {}
  }
  return `

## Store: How we store your data

    Stored Data
    Is it stored?

    Are your data stored on the device?
    ${state['security-storeOndevice'] == 'yes' ? 'Yes' : '' } ${state['security-storeOndevice'] == 'no' ? 'No' : '' }

    Are your data stored outside the device at our company or through a third party?
    ${state['security-storeOffdevice'] == 'yes' ? 'Yes' : '' } ${state['security-storeOffdevice'] == 'no' ? 'No' : '' }

## Encryption: How we encrypt your data

    Encrypted Data
    Is it encrypted?

    Does the app or technology use encryption to encrypt your data in the device or app?
    ${state['security-encryptDevice'] == 'default' ? 'Yes, by default' : '' } ${state['security-encryptDevice'] == 'no' ? 'No' : '' } ${state['security-encryptDevice'] == 'na' ? 'N/A' : '' } ${state['security-encryptDevice'] == 'steps' ? 'Yes, when you take certain steps (click to learn how)' : '' }

    Does the app or technology use encryption to encrypt your data when stored on our company servers or with an outside cloud computing services provider?
    ${state['security-encryptServer'] == 'default' ? 'Yes, by default' : '' } ${state['security-encryptServer'] == 'no' ? 'No' : '' } ${state['security-encryptServer'] == 'na' ? 'N/A' : '' } ${state['security-encryptServer'] == 'steps' ? 'Yes, when you take certain steps (click to learn how)' : '' }

    Does the app or technology use encryption to encrypt your data while it is transmitted?
    ${state['security-encryptTransmit'] == 'default' ? 'Yes, by default' : '' } ${state['security-encryptTransmit'] == 'no' ? 'No' : '' } ${state['security-encryptTransmit'] == 'na' ? 'N/A' : '' } ${state['security-encryptTransmit'] == 'steps' ? 'Yes, when you take certain steps (click to learn how)' : '' }

## Privacy: How this technology accesses other data

    Other Data
    Is it accessed?

    Will this technology or app request access to other device data or applications, such as your phone’s camera, photos, or contacts?
    ${typeof state['security-privacyappsAccess'] !== 'undefined' && state['security-privacyappsAccess'] !== '' ? (state['security-privacyappsAccess'] == 'permissioned' ? `
    Yes, only with your permission. It connects to...
    	` : 'No	') : '	' }

    Does this technology or app allow you to share the collected data with your social media accounts, like Facebook?

`
}
module.exports = {
  f: f
}

let data = {
	"formData": {
		"security-encryptDevice": "",
		"security-encryptServer": "",
		"security-encryptTransmit": "",
		"security-privacyappsAccess": "",
		"security-privacyappsDevices-camera": false,
		"security-privacyappsDevices-photo": false,
		"security-privacyappsDevices-contacts": false,
		"security-privacyappsDevices-location": false,
		"security-privacyappsDevices-microphone": false,
		"security-privacyappsDevices-health": false,
		"security-privacyappsDevices-other-text": "",
		"security-privacyappsSetting": "",
		"security-privacyappsSocial": ""
	},
	"canSubmit": false
}
<commit_msg>add devices for privacy in table<commit_after>function f (state) {
  if (typeof(state) === 'undefined'){
    state = {}
  }
  return `

## Store: How we store your data

    Stored Data
    Is it stored?

    Are your data stored on the device?
    ${state['security-storeOndevice'] == 'yes' ? 'Yes' : '' } ${state['security-storeOndevice'] == 'no' ? 'No' : '' }

    Are your data stored outside the device at our company or through a third party?
    ${state['security-storeOffdevice'] == 'yes' ? 'Yes' : '' } ${state['security-storeOffdevice'] == 'no' ? 'No' : '' }

## Encryption: How we encrypt your data

    Encrypted Data
    Is it encrypted?

    Does the app or technology use encryption to encrypt your data in the device or app?
    ${state['security-encryptDevice'] == 'default' ? 'Yes, by default' : '' } ${state['security-encryptDevice'] == 'no' ? 'No' : '' } ${state['security-encryptDevice'] == 'na' ? 'N/A' : '' } ${state['security-encryptDevice'] == 'steps' ? 'Yes, when you take certain steps (click to learn how)' : '' }

    Does the app or technology use encryption to encrypt your data when stored on our company servers or with an outside cloud computing services provider?
    ${state['security-encryptServer'] == 'default' ? 'Yes, by default' : '' } ${state['security-encryptServer'] == 'no' ? 'No' : '' } ${state['security-encryptServer'] == 'na' ? 'N/A' : '' } ${state['security-encryptServer'] == 'steps' ? 'Yes, when you take certain steps (click to learn how)' : '' }

    Does the app or technology use encryption to encrypt your data while it is transmitted?
    ${state['security-encryptTransmit'] == 'default' ? 'Yes, by default' : '' } ${state['security-encryptTransmit'] == 'no' ? 'No' : '' } ${state['security-encryptTransmit'] == 'na' ? 'N/A' : '' } ${state['security-encryptTransmit'] == 'steps' ? 'Yes, when you take certain steps (click to learn how)' : '' }

## Privacy: How this technology accesses other data

    Other Data
    Is it accessed?

    Will this technology or app request access to other device data or applications, such as your phone’s camera, photos, or contacts?
${typeof state['security-privacyappsAccess'] !== 'undefined' && state['security-privacyappsAccess'] !== '' ? (state['security-privacyappsAccess'] == 'permissioned' ? `

  Yes, only with your permission. It connects to...
${state['security-privacyappsDevices-camera'] ? '
Camera
' : ''} ${state['security-privacyappsDevices-photo'] ? '
Photo
' : ''} ${state['security-privacyappsDevices-contacts'] ? '
Contacts
' : ''} ${state['security-privacyappsDevices-location'] ? '
Location
' : ''} ${state['security-privacyappsDevices-microphone'] ? '
Microphone
' : ''} ${state['security-privacyappsDevices-health'] ? '
Health monitoring devices
' : ''}  ${state['security-privacyappsDevices-other-text'] ? `<li>${state['security-privacyappsDevices-other-text']}</li>` : ''}

	` : 'No	') : '	' }

    Does this technology or app allow you to share the collected data with your social media accounts, like Facebook?

`
}
module.exports = {
  f: f
}

let data = {
	"formData": {
		"security-encryptDevice": "",
		"security-encryptServer": "",
		"security-encryptTransmit": "",
		"security-privacyappsAccess": "",
		"security-privacyappsDevices-camera": false,
		"security-privacyappsDevices-photo": false,
		"security-privacyappsDevices-contacts": false,
		"security-privacyappsDevices-location": false,
		"security-privacyappsDevices-microphone": false,
		"security-privacyappsDevices-health": false,
		"security-privacyappsDevices-other-text": "",
		"security-privacyappsSetting": "",
		"security-privacyappsSocial": ""
	},
	"canSubmit": false
}
<|endoftext|>


<commit_before>var IPython = require('./ipython.js')
var fs = require('fs');
var Q = require('q');

var sync = require('inode').sync;

var startup_python = fs.readFileSync(__dirname+'/../startup.py', 'utf8');
var deferred_callback_router = require('./callbacks.js').deferred_callback_router;

var Kernel = function(base_url, notebook_id, config) {
  if (config) {
    var context = config.context;
  }

  if (!context) {
    context = "undefined" != typeof window ? window : global;
  }
  this.context = context;

  this.base_url = base_url;
  this.notebook_id = notebook_id;
  this.kernel = null;
  this.kernel_ready = false;
  this.command_buffer = [];

  this.setup_iopub();

  // browserify won't import sync
  if (sync) {
    sync.syncrify(this, 'start');
    sync.syncrify(this, 'execute');
    sync.syncrify(this, 'pull');
  }
}

Kernel.prototype.start = function() {
  var deferred = Q.defer();
  var self = this;
  this.kernel = new IPython.Kernel(this.base_url, 'kernels');
  this.kernel.start(this.notebook_id);
  this.check_kernel(function() {
    self.execute(startup_python).then(function() {
      deferred.resolve(self);
    });
  });

  return deferred.promise;
};

Kernel.prototype.check_kernel = function(callback) {
  var self = this;
  if (!self.kernel.shell_channel) {
    setTimeout(function() { self.check_kernel(callback); }, 100);
  }
  else {
    if (callback) {
      callback();
    }
    self.kernel_ready = true;
  }
}

Kernel.prototype._execute = function(code, deferred) {
  var self = this;
  this.kernel.execute(code, deferred_callback_router(self, deferred), {'silent':false});
}

Kernel.prototype.execute = function(code, callbacks) {
  // always push to buffer and htne try to execute.
  // took out immediate execution so everything goes through the same path
  var deferred = Q.defer();
  this.command_buffer.push([code, deferred]);
  this.execute_buffer();
  return deferred.promise;
}

Kernel.prototype.execute_buffer = function() {
  var self = this;
  if (!(self.kernel_ready)) {
    setTimeout(function() { self.execute_buffer(); }, 300);
    return;
  }
  if (self.command_buffer.length > 0) {
    var command = self.command_buffer.shift();
    var code = command[0];
    var deferred = command[1];
    self._execute(code, deferred);
  }
}

Kernel.prototype.pull = function(name) {
  var code = "to_json("+name+")";
  var pull_deferred = Q.defer();
  var deferred = this.execute(code);
  deferred.then(function(data) {
    var content = data.content;
    var context = data.context;
    var json = content['data']['application/json'];
    if (json) {
        var jsobj = JSON.parse(json);
        context[name] = jsobj;
        pull_deferred.resolve(jsobj);
    }
    pull_deferred.reject(new Error('did not get json back'));
  });
  pull_deferred.promise.pause_repl = true;
  return pull_deferred.promise;
}

Kernel.prototype.__repr__ = function() {
  return ' Bridge \nbase_url='+this.base_url+"\nnotebook_id="+this.notebook_id;
}

Kernel.prototype.setup_iopub = function() {
  // Kernel events
  var $ = IPython.$;
  var self = this;
  $([IPython.events]).on('status_idle.Kernel',function () {
  });
  $([IPython.events]).on('status_busy.Kernel',function () {
  });
};

module.exports = Kernel;
<commit_msg>added a timeout to Kernel.start<commit_after>var IPython = require('./ipython.js')
var fs = require('fs');
var Q = require('q');

var sync = require('inode').sync;

var startup_python = fs.readFileSync(__dirname+'/../startup.py', 'utf8');
var deferred_callback_router = require('./callbacks.js').deferred_callback_router;

var Kernel = function(base_url, notebook_id, config) {
  if (config) {
    var context = config.context;
  }

  if (!context) {
    context = "undefined" != typeof window ? window : global;
  }
  this.context = context;

  this.base_url = base_url;
  this.notebook_id = notebook_id;
  this.kernel = null;
  this.kernel_ready = false;
  this.command_buffer = [];

  this.setup_iopub();

  // browserify won't import sync
  if (sync) {
    sync.syncrify(this, 'start');
    sync.syncrify(this, 'execute');
    sync.syncrify(this, 'pull');
  }
}

Kernel.prototype.start = function() {
  var deferred = Q.defer();
  var self = this;
  this.kernel = new IPython.Kernel(this.base_url, 'kernels');
  this.kernel.start(this.notebook_id);
  this.check_kernel(function() {
    self.execute(startup_python).then(function() {
      deferred.resolve(self);
    });
  });

  return deferred.promise.timeout(500, "Kernel Start Timed Out");
};

Kernel.prototype.check_kernel = function(callback) {
  var self = this;
  if (!self.kernel.shell_channel) {
    setTimeout(function() { self.check_kernel(callback); }, 100);
  }
  else {
    if (callback) {
      callback();
    }
    self.kernel_ready = true;
  }
}

Kernel.prototype._execute = function(code, deferred) {
  var self = this;
  this.kernel.execute(code, deferred_callback_router(self, deferred), {'silent':false});
}

Kernel.prototype.execute = function(code, callbacks) {
  // always push to buffer and htne try to execute.
  // took out immediate execution so everything goes through the same path
  var deferred = Q.defer();
  this.command_buffer.push([code, deferred]);
  this.execute_buffer();
  return deferred.promise;
}

Kernel.prototype.execute_buffer = function() {
  var self = this;
  if (!(self.kernel_ready)) {
    setTimeout(function() { self.execute_buffer(); }, 300);
    return;
  }
  if (self.command_buffer.length > 0) {
    var command = self.command_buffer.shift();
    var code = command[0];
    var deferred = command[1];
    self._execute(code, deferred);
  }
}

Kernel.prototype.pull = function(name) {
  var code = "to_json("+name+")";
  var pull_deferred = Q.defer();
  var deferred = this.execute(code);
  deferred.then(function(data) {
    var content = data.content;
    var context = data.context;
    var json = content['data']['application/json'];
    if (json) {
        var jsobj = JSON.parse(json);
        context[name] = jsobj;
        pull_deferred.resolve(jsobj);
    }
    pull_deferred.reject(new Error('did not get json back'));
  });
  pull_deferred.promise.pause_repl = true;
  return pull_deferred.promise;
}

Kernel.prototype.__repr__ = function() {
  return ' Bridge \nbase_url='+this.base_url+"\nnotebook_id="+this.notebook_id;
}

Kernel.prototype.setup_iopub = function() {
  // Kernel events
  var $ = IPython.$;
  var self = this;
  $([IPython.events]).on('status_idle.Kernel',function () {
  });
  $([IPython.events]).on('status_busy.Kernel',function () {
  });
};

module.exports = Kernel;
<|endoftext|>


<commit_before>    /* The main function of the game.  Requires SnakeWorldOptions and Snake.*/
    function SnakeGame(canvas) {
        this.canvas = canvas;
        this.context = canvas.getContext("2d");
        this.snake = new Snake(this);
        this.food = this.getRandomCell();
        this.resetGameLoop();
        var inst = this;
        this.canvas.onkeydown = function(e) {
            inst.processKeyPress(e.keyCode);
        };
        this.canvas.focus();
        this.clearCanvas();
    }

    SnakeGame.prototype.getRandomCell = function() {
        return new Cell(
            Math.round(Math.random() * (this.width() - SWOptions.cellWidth) / SWOptions.cellWidth),
            Math.round(Math.random() * (this.height() - SWOptions.cellWidth) / SWOptions.cellWidth)
        );
    };

    SnakeGame.prototype.width = function() {
        return this.canvas.width;
    };

    SnakeGame.prototype.height = function() {
        return this.canvas.height;
    };

    SnakeGame.prototype.numCells = function() {
        return (this.height() / SWOptions.cellWidth) * (this.width() / SWOptions.cellWidth);
    };

    SnakeGame.prototype.percentOccupied = function() {
        var snakeLength = this.snake.body.length;
        var numCells = this.numCells();
        return snakeLength / numCells;
    };

    SnakeGame.prototype.createFood = function() {
        this.food = this.getRandomCell();
    };

    SnakeGame.prototype.clearCanvas = function() {
        this.context.fillStyle = SWOptions.background;
        this.context.fillRect(0, 0, this.width(), this.height());
        this.context.strokeStyle = SWOptions.border;
        this.context.strokeRect(0, 0, this.width(), this.height());
    };

    SnakeGame.prototype.checkOutsideBounds = function(cell) {
        return (cell.x > (this.width() / SWOptions.cellWidth) - 1 || //
            cell.y > (this.height() / SWOptions.cellWidth) - 1);
    };

    SnakeGame.prototype.paint = function() {
        this.clearCanvas();

        if (this.checkOutsideBounds(this.food)) {
            this.createFood();
        }

        var newHead = this.snake.getNewHead();
        if (newHead.equals(this.food)) {
            this.createFood();
        } else {
            this.snake.body.pop();
        }

        this.snake.body.unshift(newHead);

        // Paint Body
        for (var len = this.snake.body.length, i = len - 1; i >= 0; i--) {
            var cell = this.snake.body[i];
            var position = ((len - i) / len);
            var color = SWOptions.getTheme().getHslColor(position);
            if (i === 0) {
                this.paintCell(cell, color, 'triangle');
            } else {
                this.paintCell(cell, color);
            }
        }
        // Paint Head
        //this.paintCell(this.snake.body[0], this.snake.body.length, 'triangle');

        this.paintCell(this.food, 0, 'food');
        this.writeText();
    };

    SnakeGame.prototype.writeText = function() {
        if (!SWOptions.printInfo) {
            return;
        }
        var stats = { // Will be printed out in reverse order.
            "size:  ": this.snake.body.length,
            "turn:  ": SWOptions.randomness,
            "speed: ": SWOptions.speed,
            "wonk:  ": SWOptions.sizeVariation,
            "theme: ": SWOptions.currentTheme,
        };
        var boxHeight = ((Object.keys(stats).length + 1) * 10);
        this.context.fillStyle = "blue";
        this.context.fillRect(0, this.height() - boxHeight, 50, boxHeight);

        this.context.fillStyle = "white";
        var height = this.height() - 5;
        for (var stat in stats) {
            if (stats.hasOwnProperty(stat)) {
                this.context.fillText(stat + stats[stat], 5, height);
                height -= 10;
            }
        }
    };

    SnakeGame.prototype.paintCell = function(cell, color, style) {
        if (typeof color == 'undefined') {
            color = 0;
        }
        if (typeof style == 'undefined') {
            style = SWOptions.getStyle();
        }
        var theme = SWOptions.getTheme();
        this.context.fillStyle = (typeof color == 'string') ? color : theme[color % theme.length];

        if (style == 'random') {
            style = SWOptions.getRandomStyle();
        }
        switch (style) {
            case 'circle':
                this.circle(cell);
                break;
            case 'square':
                this.square(cell);
                break;
            case 'triangle':
                this.triangle(cell);
                break;
            case 'food':
                this.paintFood(cell);
                break;
            default:
                console.log('unknown style');
                break;
        }
    };

    SnakeGame.prototype.square = function(cell) {
        this.context.fillRect(
            cell.x * SWOptions.cellWidth + SWOptions.getSizeMod(),
            cell.y * SWOptions.cellWidth + SWOptions.getSizeMod(),
            SWOptions.cellWidth + SWOptions.getSizeMod(),
            SWOptions.cellWidth + SWOptions.getSizeMod());
    };

    SnakeGame.prototype.circle = function(cell) {
        this.context.beginPath();
        this.context.arc(
            cell.x * SWOptions.cellWidth + SWOptions.cellWidth / 2,
            cell.y * SWOptions.cellWidth + SWOptions.cellWidth / 2,
            SWOptions.cellWidth / 2 + SWOptions.getSizeMod(),
            0,
            2 * Math.PI);
        this.context.fill();
        this.context.closePath();
    };

    SnakeGame.prototype.triangle = function(cell) {
        var loc, ptOne, ptTwo, ptThree;
        loc = {
            x: cell.x * SWOptions.cellWidth,
            y: cell.y * SWOptions.cellWidth,
        };
        switch (SWOptions.dirs[this.snake.currentDirection]) {
            case 'right':
                ptOne = cell.pointOne();
                ptTwo = cell.pointSix();
                ptThree = cell.pointSeven();
                break;
            case 'down':
                ptOne = cell.pointThree();
                ptTwo = cell.pointEight();
                ptThree = cell.pointOne();
                break;
            case 'left':
                ptOne = cell.pointNine();
                ptTwo = cell.pointFour();
                ptThree = cell.pointThree();
                break;
            case 'up':
                ptOne = cell.pointSeven();
                ptTwo = cell.pointTwo();
                ptThree = cell.pointNine();
                break;
        }
        this.context.beginPath();
        this.context.moveTo(ptOne.x, ptOne.y);
        this.context.lineTo(ptTwo.x, ptTwo.y);
        this.context.lineTo(ptThree.x, ptThree.y);
        this.context.lineTo(ptOne.x, ptOne.y);
        this.context.fill();
        this.context.closePath();
    };

    SnakeGame.prototype.paintFood = function(cell) {
        this.circle(cell);
    };

    SnakeGame.prototype.pause = function() {
        var inst = this;
        this.gameLoop = this.gameLoop ? clearInterval(this.gameLoop) : setInterval(function() {
            inst.paint();
        }, SWOptions.getSpeedInterval());
    };

    SnakeGame.prototype.resetGameLoop = function() {
        if (typeof this.gameLoop != "undefined" && this.gameLoop !== null) {
            clearInterval(this.gameLoop);
        }
        var inst = this;
        this.gameLoop = setInterval(function() {
            inst.paint();
        }, SWOptions.getSpeedInterval());
    };

    SnakeGame.prototype.processKeyPress = function(keyCode) {
        switch (keyCode) {
            case 32:
            case "randomjump":
                this.snake.body[0] = this.getRandomCell();
                break;
            case 37:
            case "left":
                this.snake.move(0);
                break;
            case 38:
            case "up":
                this.snake.move(1);
                break;
            case 39:
            case "right":
                this.snake.move(2);
                break;
            case 40:
            case "down":
                this.snake.move(3);
                break;
            case 66:
            case "B":
                SWOptions.modRandomness(1);
                break;
            case 67:
            case "c":
                SWOptions.printInfo = !SWOptions.printInfo;
                break;
            case 74:
            case "j":
                SWOptions.modSizeVariation(1);
                break;
            case 75:
            case "k":
                SWOptions.modSizeVariation(-1);
                break;
            case 77:
            case "m":
                SWOptions.cycleTheme();
                break;
            case 78:
            case "n":
                SWOptions.cycleStyle();
                break;
            case 80:
            case "pause":
                this.pause();
                break;
        }
    };<commit_msg>Change Vars rendering<commit_after>    /* The main function of the game.  Requires SnakeWorldOptions and Snake.*/
    function SnakeGame(canvas) {
        this.canvas = canvas;
        this.context = canvas.getContext("2d");
        this.snake = new Snake(this);
        this.food = this.getRandomCell();
        this.resetGameLoop();
        var inst = this;
        this.canvas.onkeydown = function(e) {
            inst.processKeyPress(e.keyCode);
        };
        this.canvas.focus();
        this.clearCanvas();
    }

    SnakeGame.prototype.getRandomCell = function() {
        return new Cell(
            Math.round(Math.random() * (this.width() - SWOptions.cellWidth) / SWOptions.cellWidth),
            Math.round(Math.random() * (this.height() - SWOptions.cellWidth) / SWOptions.cellWidth)
        );
    };

    SnakeGame.prototype.width = function() {
        return this.canvas.width;
    };

    SnakeGame.prototype.height = function() {
        return this.canvas.height;
    };

    SnakeGame.prototype.numCells = function() {
        return (this.height() / SWOptions.cellWidth) * (this.width() / SWOptions.cellWidth);
    };

    SnakeGame.prototype.percentOccupied = function() {
        var snakeLength = this.snake.body.length;
        var numCells = this.numCells();
        return snakeLength / numCells;
    };

    SnakeGame.prototype.createFood = function() {
        this.food = this.getRandomCell();
    };

    SnakeGame.prototype.clearCanvas = function() {
        this.context.fillStyle = SWOptions.background;
        this.context.fillRect(0, 0, this.width(), this.height());
        this.context.strokeStyle = SWOptions.border;
        this.context.strokeRect(0, 0, this.width(), this.height());
    };

    SnakeGame.prototype.checkOutsideBounds = function(cell) {
        return (cell.x > (this.width() / SWOptions.cellWidth) - 1 || //
            cell.y > (this.height() / SWOptions.cellWidth) - 1);
    };

    SnakeGame.prototype.paint = function() {
        this.clearCanvas();

        if (this.checkOutsideBounds(this.food)) {
            this.createFood();
        }

        var newHead = this.snake.getNewHead();
        if (newHead.equals(this.food)) {
            this.createFood();
        } else {
            this.snake.body.pop();
        }

        this.snake.body.unshift(newHead);

        // Paint Body
        for (var len = this.snake.body.length, i = len - 1; i >= 0; i--) {
            var cell = this.snake.body[i];
            var position = ((len - i) / len);
            var color = SWOptions.getTheme().getHslColor(position);
            if (i === 0) {
                this.paintCell(cell, color, 'triangle');
            } else {
                this.paintCell(cell, color);
            }
        }
        // Paint Head
        //this.paintCell(this.snake.body[0], this.snake.body.length, 'triangle');

        this.paintCell(this.food, 0, 'food');
        this.writeText();
    };

    SnakeGame.prototype.writeText = function() {
        if (!SWOptions.printInfo) {
            return;
        }
        var stats = { // Will be printed out in reverse order.
            "render: ": SWOptions.getStyle(),
            "size:   ": this.snake.body.length,
            "turn:   ": SWOptions.randomness,
            "speed:  ": SWOptions.speed,
            "wonk:   ": SWOptions.sizeVariation,
            "theme:  ": SWOptions.currentTheme,
        };
        var boxHeight = ((Object.keys(stats).length + 1) * 10);
        this.context.fillStyle = "blue";
        this.context.fillRect(0, this.height() - boxHeight, 120, boxHeight);

        this.context.fillStyle = "white";
        this.context.font = "12px Lucida Console";
        var height = this.height() - 5;
        for (var stat in stats) {
            if (stats.hasOwnProperty(stat)) {
                this.context.fillText(stat + stats[stat], 5, height);
                height -= 10;
            }
        }
    };

    SnakeGame.prototype.paintCell = function(cell, color, style) {
        if (typeof color == 'undefined') {
            color = 0;
        }
        if (typeof style == 'undefined') {
            style = SWOptions.getStyle();
        }
        var theme = SWOptions.getTheme();
        this.context.fillStyle = (typeof color == 'string') ? color : theme[color % theme.length];

        if (style == 'random') {
            style = SWOptions.getRandomStyle();
        }
        switch (style) {
            case 'circle':
                this.circle(cell);
                break;
            case 'square':
                this.square(cell);
                break;
            case 'triangle':
                this.triangle(cell);
                break;
            case 'food':
                this.paintFood(cell);
                break;
            default:
                console.log('unknown style');
                break;
        }
    };

    SnakeGame.prototype.square = function(cell) {
        this.context.fillRect(
            cell.x * SWOptions.cellWidth + SWOptions.getSizeMod(),
            cell.y * SWOptions.cellWidth + SWOptions.getSizeMod(),
            SWOptions.cellWidth + SWOptions.getSizeMod(),
            SWOptions.cellWidth + SWOptions.getSizeMod());
    };

    SnakeGame.prototype.circle = function(cell) {
        this.context.beginPath();
        this.context.arc(
            cell.x * SWOptions.cellWidth + SWOptions.cellWidth / 2,
            cell.y * SWOptions.cellWidth + SWOptions.cellWidth / 2,
            SWOptions.cellWidth / 2 + SWOptions.getSizeMod(),
            0,
            2 * Math.PI);
        this.context.fill();
        this.context.closePath();
    };

    SnakeGame.prototype.triangle = function(cell) {
        var loc, ptOne, ptTwo, ptThree;
        loc = {
            x: cell.x * SWOptions.cellWidth,
            y: cell.y * SWOptions.cellWidth,
        };
        switch (SWOptions.dirs[this.snake.currentDirection]) {
            case 'right':
                ptOne = cell.pointOne();
                ptTwo = cell.pointSix();
                ptThree = cell.pointSeven();
                break;
            case 'down':
                ptOne = cell.pointThree();
                ptTwo = cell.pointEight();
                ptThree = cell.pointOne();
                break;
            case 'left':
                ptOne = cell.pointNine();
                ptTwo = cell.pointFour();
                ptThree = cell.pointThree();
                break;
            case 'up':
                ptOne = cell.pointSeven();
                ptTwo = cell.pointTwo();
                ptThree = cell.pointNine();
                break;
        }
        this.context.beginPath();
        this.context.moveTo(ptOne.x, ptOne.y);
        this.context.lineTo(ptTwo.x, ptTwo.y);
        this.context.lineTo(ptThree.x, ptThree.y);
        this.context.lineTo(ptOne.x, ptOne.y);
        this.context.fill();
        this.context.closePath();
    };

    SnakeGame.prototype.paintFood = function(cell) {
        this.circle(cell);
    };

    SnakeGame.prototype.pause = function() {
        var inst = this;
        this.gameLoop = this.gameLoop ? clearInterval(this.gameLoop) : setInterval(function() {
            inst.paint();
        }, SWOptions.getSpeedInterval());
    };

    SnakeGame.prototype.resetGameLoop = function() {
        if (typeof this.gameLoop != "undefined" && this.gameLoop !== null) {
            clearInterval(this.gameLoop);
        }
        var inst = this;
        this.gameLoop = setInterval(function() {
            inst.paint();
        }, SWOptions.getSpeedInterval());
    };

    SnakeGame.prototype.processKeyPress = function(keyCode) {
        switch (keyCode) {
            case 32:
            case "randomjump":
                this.snake.body[0] = this.getRandomCell();
                break;
            case 37:
            case "left":
                this.snake.move(0);
                break;
            case 38:
            case "up":
                this.snake.move(1);
                break;
            case 39:
            case "right":
                this.snake.move(2);
                break;
            case 40:
            case "down":
                this.snake.move(3);
                break;
            case 66:
            case "B":
                SWOptions.modRandomness(1);
                break;
            case 67:
            case "c":
                SWOptions.printInfo = !SWOptions.printInfo;
                break;
            case 74:
            case "j":
                SWOptions.modSizeVariation(1);
                break;
            case 75:
            case "k":
                SWOptions.modSizeVariation(-1);
                break;
            case 77:
            case "m":
                SWOptions.cycleTheme();
                break;
            case 78:
            case "n":
                SWOptions.cycleStyle();
                break;
            case 80:
            case "pause":
                this.pause();
                break;
        }
    };<|endoftext|>


<commit_before>/**
 * Created by Nitai J. Perez
 * nitai.perez@gmail.com
 * 18/03/2015.
 */

var _times = {};

function time(label) {
    // Init label:
    if (!this._times[label]) this._times[label] = {calls: 0};

    this._times[label].timestamp = Date.now();

}
function timeEnd(label) {
    if (!this._times[label]) {
        throw new Error('No such label: ' + label);
    }

    // Label called, increment count:
    this._times[label].calls += 1;

    // Set duration:
    var duartion = Date.now() - this._times[label];

    // Update average:
    this._times[label].avg =
        this._times[label].avg
            ? ((avg * this._times[label].calls - 1) + duartion) / this._times[label].calls
            : duartion;

    // log:
    console.log('%s: %dms (avg: %dms across %s calls)', label, duartion, this._times[label].avg, this._times[label].calls);
}

/**
 * Wrap a callback in order to time it's call time.
 */
function cbTime(label, cb) {
    time(label);
    return function () {
        timeEnd(label);
        cb.apply(this, arguments);
    }
}

module.exports = {
    cb: cbTime
};<commit_msg>Removed this before _times object<commit_after>/**
 * Created by Nitai J. Perez
 * nitai.perez@gmail.com
 * 18/03/2015.
 */

var _times = {};

function time(label) {
    // Init label:
    if (!_times[label]) _times[label] = {calls: 0};

    _times[label].timestamp = Date.now();

}
function timeEnd(label) {
    if (!_times[label]) {
        throw new Error('No such label: ' + label);
    }

    // Label called, increment count:
    _times[label].calls += 1;

    // Set duration:
    var duartion = Date.now() - _times[label];

    // Update average:
    _times[label].avg =
        _times[label].avg
            ? ((avg * _times[label].calls - 1) + duartion) / _times[label].calls
            : duartion;

    // log:
    console.log('%s: %dms (avg: %dms across %s calls)', label, duartion, _times[label].avg, _times[label].calls);
}

/**
 * Wrap a callback in order to time it's call time.
 */
function cbTime(label, cb) {
    time(label);
    return function () {
        timeEnd(label);
        cb.apply(this, arguments);
    }
}

module.exports = {
    cb: cbTime
};<|endoftext|>


<commit_before>import Marionette from 'backbone.marionette';

require('./SidePanelView.css');
const template = require('./SidePanelView.hbs');

export default Marionette.LayoutView.extend({
  template,

  templateHelpers() {
    return {
      position: this.position,
      isLeft: this.position === 'left',
      icon: this.icon,
      views: this.views,
    };
  },

  // regions: {
  //   sidePanelContent: '.side-panel-content',
  // },

  // regions() {
  //   const regions = {};
  //   this.views.forEach((view, index) => {
  //     regions[`region-${index}`] = `#${this.position}-${index}`;
  //   });
  //   return regions;
  // },

  events: {
    'click .toggle-side-panel': 'onToggleSidePanelClicked',
  },

  initialize(options) {
    this.position = options.position || 'left';
    this.views = options.views;
    this.icon = options.icon;
  },

  onBeforeShow() {
    this.views.forEach((viewConfig, index) => {
      this.addRegion(`region-${index}`, `#${this.position}-${index}`);
      this.showChildView(`region-${index}`, viewConfig.view);
    });
    // this.views.forEach(view => this.showChildView('sidePanelContent', this.view))
    // this.showChildView('sidePanelContent', this.view);
  },

  onToggleSidePanelClicked() {
    this.$('.side-panel').toggleClass('in');
    this.$('.toggle-side-panel-out').toggleClass('out');
  },
});
<commit_msg>Allowing to set the initial visibility of side panels.<commit_after>import Marionette from 'backbone.marionette';

require('./SidePanelView.css');
const template = require('./SidePanelView.hbs');

export default Marionette.LayoutView.extend({
  template,

  templateHelpers() {
    return {
      position: this.position,
      isLeft: this.position === 'left',
      icon: this.icon,
      views: this.views,
    };
  },

  // regions: {
  //   sidePanelContent: '.side-panel-content',
  // },

  // regions() {
  //   const regions = {};
  //   this.views.forEach((view, index) => {
  //     regions[`region-${index}`] = `#${this.position}-${index}`;
  //   });
  //   return regions;
  // },

  events: {
    'click .toggle-side-panel': 'onToggleSidePanelClicked',
  },

  initialize(options) {
    this.position = options.position || 'left';
    this.views = options.views;
    this.icon = options.icon;
    this.defaultOpen = options.defaultOpen;
  },

  onBeforeShow() {
    this.views.forEach((viewConfig, index) => {
      this.addRegion(`region-${index}`, `#${this.position}-${index}`);
      this.showChildView(`region-${index}`, viewConfig.view);
    });

    if (this.defaultOpen) {
      this.onToggleSidePanelClicked();
    }
    // this.views.forEach(view => this.showChildView('sidePanelContent', this.view))
    // this.showChildView('sidePanelContent', this.view);
  },

  onToggleSidePanelClicked() {
    this.$('.side-panel').toggleClass('in');
    this.$('.toggle-side-panel-out').toggleClass('out');
  },
});
<|endoftext|>


<commit_before>var thistutorial = tutorial22;
// Module for the guide pages
tutorial22.pages = {};

// Unshow a string
tutorial22.pages.unString = function(str){
    return str.replace(/^"(.*)"$/,"$1").replace(/\\"/,'"');
}

// Random message from a list of messages
tutorial22.pages.rmsg = function(choices) {
    return choices[Math.floor((Math.random()*100) % choices.length)];
}

// Simple HTML encoding
tutorial22.pages.htmlEncode = function(text,shy){
    var x = $('

');
    x.text(text);
    return x.html();
}

// Added by WV
tutorial22.pages.isNum = function(result) {
  var retval =
    /(Num|Fractional|Integral|Floating)\s+[a-z]+\s+=>\s+[a-z]+/.test( result.type ) ||
    /\(Ord\s+[a-z]+\s*,\s*(Num|Fractional|Integral|Floating)\s+[a-z]+\)\s+=>\s+[a-z]+/.test( result.type ) ||
    /\((Num|Fractional|Integral|Floating)\s+[a-z]+\s*,\s*Ord\s+[a-z]+\)\s+=>\s+[a-z]+/.test( result.type ) ||
    result.type == "Integer" ||
    result.type == "Int" ||
    result.type == "Bool";
    return retval;
}

tutorial22.pages.isBool = function(result) {
    return (result.type == "Bool");
}

// All pages
tutorial22.pages.list =
    [

        {title:'Tutorial 2.2: More Boolean Operations',
         guide:

         '
Tutorial 2.2: More Boolean Operations
' +
         '
We are going to look at further operations on boolean values in this session.
'+
         '
To start the tutorial type next at the λ prompt.
' +
         '
For help about the tutorial environment type help at the λ prompt.
'

        },
        ////////////////////////////////////////////////////////////////////////
        // Lesson 1

        // Simple boolean operators
        {lesson:1,
         title:'Boolean Negation',
         guide:
         '
Boolean Negation
'
         + "
Boolean values are either True or False. True is the opposite of False, and vice versa. The not function returns the opposite boolean value, the logical complement. Try not True
"
        },
        {
          trigger:tutorial22.pages.isBool,
          guide:function(result){
            if (!result) result = {expr:'not True',value:'False'};
            var rexpr = result.expr.replace(/^let\s.+\sin\s+/, "");
            var complied = /False/.test(result.value);
            var valid = /True|False/.test(result.value);

	    var msg = "";
            if (valid) {
              if (complied) {
                msg = "
You got back the boolean value "+result.value+" as expected.
";
              } else {
		var trimmedExpr = result.expr.replace(/^let\s.+\sin\s+/, "");
                msg = '
OK, you calculated ' + trimmedExpr +',so you got back the truth value '+result.value+' as you would expect.
';
              }
            } else {
                msg = '
'+
                    "What you typed does not seem to be a simple boolean operation but it did something sensible!"
                    +'
';

            }
            tutorial22.continueOnError = true;
	    var next_step = "
Now perform a double negation, e.g.  not (not False) and observe that the final result is the same as the initial value.
";
	      return msg + next_step;

          }

        },
        // && and ||
        {lesson:2,
         title:'Boolean And',
         trigger:function(result){
	   bracketError = /applied\s+to\s+two\s+arguments/.test(result.error);
	   booleanResult = result.type == "Bool";
           return bracketError || booleanResult;
	 },
         guide:function(result){
           var msg = "";
	   if (/applied\s+to\s+two\s+arguments/.test(result.error)) {
	       msg = msg + "
Remember to use brackets round the innermost not function call, since this must be evaluated first: not (not True)
."
	   }
           var next_step =
            "
The And operator
"
            +"
Use the && infix operator as a boolean And. This only evaluates to True when both its inputs are True."
            +"e.g. True && True.
";
	   return msg + next_step;
	 }
        },

   // FIXME - need a custom trigger to check we did an AND

        // Expression Syntax - cont'd
        {
         trigger:tutorial22.pages.isBool,
          guide:function(result){
	      tutorial22.continueOnError = true;
            return "
You can apply these operations to other data types. Try comparing two Strings for equality, e.g. \"hello\" == \"hola\".
";
          }
        },

	{
	 // this is a complex trigger - looking for an error or a correctly typed result!
         trigger:function(result) {
	     variableError = /^Not\s+in\s+scope/.test(result.error)
	     missingQuoteError = /lexical\s+error\s+in\s+string/.test(result.error)
	     booleanResult = result.type == "Bool";
	     return variableError || missingQuoteError || booleanResult
	 },
         guide:function(result){
	     msg = "";
	     if (/^Not\s+in\s+scope/.test(result.error)) {
		 msg = "
Did you forget to use double quotes \" \" around your strings? If so, Haskell thinks that you are referring to named values (like program variables)???"
	     }
	     if (/lexical\s+error\s+in\s+string/.test(result.error)) {
		 msg = "
Did you forget a double quote \" at the beginning or end of one of your strings?"
	     }
             tutorial22.continueOnError = false;
             return   msg + "
 Now try String inequality \"foo\" /= \"bar\"
";
         }
        },

        {
         trigger:tutorial22.pages.isBool,
          guide:function(result){
            return "
You can apply these operations to other data types. You might also try comparing two Bools directly, e.g. True /= False.
";
          }
        },

        // Expression Syntax - cont'd
        {
          trigger:tutorial22.pages.isBool,
          guide:function(result){
            if (!result) result = {expr:'True/=False',value:'True'};
            var complied = /Bool/.test(result.type);
            var rexpr = result.expr.replace(/^let\s.+\sin\s+/, "");
            var valid = /True|False/.test(rexpr);

            var msg="";
            if (valid) {
                msg="
So this expression returned "+result.value+", illustrating the use of the equality test operator.
";
            }
            var next_step =
		  "
Now, what happens if you try to compare two values with different types? e.g. True == 1.";
	      tutorial22.continueOnError = true;
            return msg+next_step;
          }
        },

        // Expression Syntax - Corner cases 2
        {
          trigger:function(result){
	      var typeError =
		  (/^\s+Couldn\'t\s+match\s+expected\s+type/.test(result.error)) ||
		  (/^\s+No\s+instance\s+for/.test(result.error));
              return typeError;
          },
          guide:function(result){
          tutorial22.continueOnError = false;
          return   "
As you can see, this equality test fails:  Haskell cannot compare two values that have different types. The full story is more complex, but for now, we can see that types limit the operations we can apply to particular values.
" +
		  "
Haskell supports the standard comparison/relational operators, <, <=, >, >=. Try a simple comparison, e.g. 10 > 9
";
        }
        },

        // Expression Syntax - Corner cases 3
        {
          trigger:tutorial22.pages.isBool,
          guide:function(result){
          return  "
Note that relational operators also work on lists, in a dictionary-order manner (lexicographic). e.g. Try [1,2,3] < [1,2,3,4]
";
        }
        },

	{
	  trigger:tutorial22.pages.isBool,
	  guide:function(result) {
	    return "
 Since strings are lists of characters in Haskell, we can do the same kinds of comparison operations on strings. Check whether Aadvark comes before Aaronic in the dictionary with this code: "Aardvark" < "Aaronic"";
	  }
	},
// should we talk about string comparisons,
// about Eq class?

        {
          trigger:tutorial22.pages.isBool,
          guide:function(result){
          tutorial22.continueOnError = false;
          var next_step = "
Now let's think about list membership. We want a boolean function that returns true if a value is part of a list, and false otherwise. This is the elem function. Try elem 1 [1,2,3]
";
              return next_step;
	}
        },

	{
	  trigger:tutorial22.pages.isBool,
	  guide:function(result){
            if (!result) result = {expr:'elem 1 [1,2,3]',value:'True'};
            var complied = /Bool/.test(result.type);
            var rexpr = result.expr.replace(/^let\s.+\sin\s+/, "");
            var valid = /True|False/.test(result.value);
            var matches = rexpr.match(/^\s*elem\s+([0-9]+) (\[[^\]]*\])/);
	    var element = matches[1];
            var msg="
You see that element " + element + " is " + ((result.value)?"":" not ") + "part of the list.
";
	    var next_step = msg + "
The elem function can be written infix, like an arithmetic operator, by enclosing its name in backquotes ``. Try 3 `elem` [1, 2, 3, 4, 5]."
            return next_step;

	}
	},
	{
	  trigger:tutorial22.pages.isBool,
	  guide:function(result){
	      var next_step ="
In fact, Haskell permits any two-argument function to be written as an infix operator using backquote characters. For a further example, try the max function as an infix operator: 42 `max` 13";
	      return next_step;
          }
        },

	{
	  trigger:tutorial22.pages.isNum,
	  guide:function(result){
	      var next_step = "
Also note that any Haskell infix operator, e.g. +, can be written as a prefix operator by enclosing it in parentheses, like (+) 1 1";
	      return next_step;
	  }
	},

{
	  trigger:function(result){
	    return true;
	  },
	  guide:function(result){

	    var msg="
And that's the end of Tutorial 2.2!
" +
	    		"
Well done, you finished another Haskell tutorial!.
" +
	    		"
Let's recap what we've just discovered:
" +
	            '
'+
	            '
Boolean not, && and || operations
' +
		    '
Boolean functions on lists
' +
                    '
if/then/else conditional expressions
' +
	            '

';

	    return msg;
	  },
	},
    {title:'Haskell Interactive Tutorials',
      	 guide:
      	       '
Haskell Interactive Tutorials
' +
      	       '
In this environment you can try out Haskell code or take tutorials that guide you by prompting you to enter pieces of code and give you feedback on them. '+
      	       'Each tutorial has its own url,'+
      	       ' for this tutorial the url is '+tutorial22.url+'.
'+
      	       '
Functionality
' +
      	       '
This coding environment does not offer all the functionality of the Haskell compiler <tt>ghc</tt> or the interactive Haskell interpreter <tt>ghci</tt>, '+
      	       'because that would allow hackers to compromise your computer. Any feature that could potentially be a security risk has been disabled.
'+
      	       '
Only the following IO actions are supported in this app (more about this later in the course).
' +
      	       '
Navigation commands
' +
      	       '
To navigate between steps in the tutorial you can use following commands:
' +
      	       '
' +
      	       '
To start the tutorial: type start at the λ prompt
' +
      	       '
To go to the next step: next
' +
      	       '
To go to the previous step: prev or back
' +
      	       '
To go to a particular step n: stepn
' +
      	       '
To see this help message: help '+
      	       '
' +
      	       '
Manipulating the context
' +
      	       '
The environment keeps a list of all equations you defined, we call this the context. Every computation you execute uses this context as follows:
' +
      	       '
'+
      	       'let
'+
      	       '  context
'+
      	       'in
'+
      	       '  your_expression'+
      	       '
' +
  	    	   '
You can access and manipulate the context in the following ways:
' +
  	    	   '
' +
      	       '
Inspect the context: context or show
' +
      	       '
Remove the last added equation: undo
' +
      	       '
Remove the equation for a given variable: forget varname
' +
   	       '
Erase the entire context: erase or wipe
' +
   	       '
Reset the tutorial completely: reset
' +
      	       '
'
      	      },

]
<commit_msg>updated and extensively revised tutorial 22 - now has support for more boolean functions, and for if/then/else expressions<commit_after>var thistutorial = tutorial22;
// Module for the guide pages
tutorial22.pages = {};

// Unshow a string
tutorial22.pages.unString = function(str){
    return str.replace(/^"(.*)"$/,"$1").replace(/\\"/,'"');
}

// Random message from a list of messages
tutorial22.pages.rmsg = function(choices) {
    return choices[Math.floor((Math.random()*100) % choices.length)];
}

// Simple HTML encoding
tutorial22.pages.htmlEncode = function(text,shy){
    var x = $('

');
    x.text(text);
    return x.html();
}

// Added by WV
tutorial22.pages.isNum = function(result) {
  var retval =
    /(Num|Fractional|Integral|Floating)\s+[a-z]+\s+=>\s+[a-z]+/.test( result.type ) ||
    /\(Ord\s+[a-z]+\s*,\s*(Num|Fractional|Integral|Floating)\s+[a-z]+\)\s+=>\s+[a-z]+/.test( result.type ) ||
    /\((Num|Fractional|Integral|Floating)\s+[a-z]+\s*,\s*Ord\s+[a-z]+\)\s+=>\s+[a-z]+/.test( result.type ) ||
    result.type == "Integer" ||
    result.type == "Int" ||
    result.type == "Bool";
    return retval;
}

tutorial22.pages.isBool = function(result) {
    return (result.type == "Bool");
}

// All pages
tutorial22.pages.list =
    [

        {title:'Tutorial 2.2: More Boolean Operations',
         guide:

         '
Tutorial 2.2: More Boolean Operations
' +
         '
We are going to look at further operations on boolean values in this session.
'+
         '
To start the tutorial type next at the λ prompt.
' +
         '
For help about the tutorial environment type help at the λ prompt.
'

        },
        ////////////////////////////////////////////////////////////////////////
        // Lesson 1

        // Simple boolean operators
        {lesson:1,
         title:'Boolean Negation',
         guide:
         '
Boolean Negation
'
         + "
Boolean values are either True or False. True is the opposite of False, and vice versa. The not function returns the opposite boolean value, the logical complement. Try not True
"
        },
        {
          trigger:tutorial22.pages.isBool,
          guide:function(result){
            if (!result) result = {expr:'not True',value:'False'};
            var rexpr = result.expr.replace(/^let\s.+\sin\s+/, "");
            var complied = /False/.test(result.value);
            var valid = /True|False/.test(result.value);

	    var msg = "";
            if (valid) {
              if (complied) {
                msg = "
You got back the boolean value "+result.value+" as expected.
";
              } else {
		var trimmedExpr = result.expr.replace(/^let\s.+\sin\s+/, "");
                msg = '
OK, you calculated ' + trimmedExpr +',so you got back the truth value '+result.value+' as you would expect.
';
              }
            } else {
                msg = '
'+
                    "What you typed does not seem to be a simple boolean operation but it did something sensible!"
                    +'
';

            }
            tutorial22.continueOnError = true;
	    var next_step = "
Now perform a double negation, e.g.  not (not False) and observe that the final result is the same as the initial value.
";
	      return msg + next_step;

          }

        },
        // && and ||
        {lesson:2,
         title:'Boolean And',
         trigger:function(result){
	   bracketError = /applied\s+to\s+two\s+arguments/.test(result.error);
	   booleanResult = result.type === "Bool";
           return bracketError || booleanResult;
	 },
         guide:function(result){
           var msg = "";
	   if (/applied\s+to\s+two\s+arguments/.test(result.error)) {
	       msg = msg + "
Remember to use brackets round the innermost not function call, since this must be evaluated first: not (not True)
."
	   }
           else {
               msg = msg +  "
You got back the boolean value "+result.value+" as expected.
";
           }
           var next_step =
            "
The And operator
"
            +"
Use the && infix operator as a boolean conjunction (AND function). This only evaluates to True when both its inputs are True."
            +"i.e. True && True.
";
	   return msg + next_step;
	 }
        },

        {
         trigger:function(result){
             // user did &&, result is boolean True
             andFunction = /\&\&/.test(result.expr);
	     booleanResult = result.type === "Bool";
             trueResult =  /True/.test(result.value);
             return andFunction && booleanResult && trueResult;
         },
         guide:function(result){
             var msg = "
You got back the result " + result.value + " as expected.
";
             var next_step = "
Now try evaluating an AND expression where one of the inputs is False, e.g. False && True. What will the output value be?
";
             return msg + next_step;
         }
        },

	{
         trigger:function(result) {
             // user did &&, result is boolean False
             andFunction = /\&\&/.test(result.expr);
	     booleanResult = result.type === "Bool";
             falseResult =  /False/.test(result.value);
             return andFunction && booleanResult && falseResult;
	 },
         guide:function(result){
             var msg = "
You got back the result " + result.value + " as expected.
";
             var next_step = "
The Or operator
"
             + "
Boolean disjunction (logical OR) is the dual of the AND operation. In Haskell, use the infix || operator for OR. When at least one of the inputs is True, then the output of OR will be True. Try True || False.
";
             return msg + next_step;
         }
        },

	{
         trigger:function(result) {
             // user did ||, result is boolean True
             orFunction = /\|\|/.test(result.expr);
	     booleanResult = result.type === "Bool";
             orResult =  /True/.test(result.value);
             return orFunction && booleanResult && orResult;
	 },
         guide:function(result){
             var msg = "
You got back the result " + result.value + " as expected.
";
             var next_step = "
When both of the inputs are False, then the output of OR will be False. Try False || False.
";
             return msg + next_step;
         }
        },

	{
         trigger:function(result) {
             // user did ||, result is boolean False
             orFunction = /\|\|/.test(result.expr);
	     booleanResult = result.type === "Bool";
             orResult =  /False/.test(result.value);
             return orFunction && booleanResult && orResult;
	 },
         guide:function(result){
             var msg = "
You got back the result " + result.value + " as expected.
";
             var next_step = "
Exclusive OR
"
                 + "
Haskell also defines the xor function, which returns true when its two boolean arguments are different (one is True and the other is False). Try True `xor` False. (Notice that we specify xor as an infix function with the backquotes here.)
";
             return msg + next_step;
         }
        },

        {
         trigger:function(result) {
             // user did xor, result is boolean False
             xorFunction = /xor/.test(result.expr);
	     booleanResult = result.type === "Bool";
             xorResult =  /True/.test(result.value);
             return xorFunction && booleanResult && xorResult;
	 },
         guide:function(result){
            return "
It's straightforward to enumerate the full truth table for two-input boolean functions. We could use a list comprehension expression to enumerate the input values: [(x,y) | x<-[False, True], y<-[False, True]]. Then we could map the boolean function over these input values (extracted from the pairs). For instance, here are the enumerated output values for the xor function: map (\\inputs -> xor (fst inputs) (snd inputs)) [(x,y) | x<-[False, True], y<-[False, True]].
";
          }
        },

        {
         trigger:function(result) {
             // user did map, result is a boolean list
             mapFunction = /map/.test(result.expr);
	     boolListResult = result.type === "[Bool]";
             return mapFunction && boolListResult;
	 },
         guide:function(result){
             return "
Logic Operations with More Inputs
"
                 + "
Sometimes, boolean logic functions like AND and OR have more than two inputs. Haskell supports these multi-input boolean operations with and and or functions that take a list of boolean values as a single input. Effectively, this is a fold of the && or || operator over the input list of boolean values.
"
                 + "
Try and [False, True, False, True] or or [True, True, False], for instance.
";
          }
        },

        {
         trigger:function(result) {
             // user did and or or, result is a boolean
             logicFunction = (/and/.test(result.expr)) || (/or/.test(result.expr));
	     booleanResult = result.type === "Bool";
             return logicFunction && booleanResult;
	 },
         guide:function(result){
             var msg = "
You got back the result " + result.value + " as expected.
";
             var next_step = "
if Expressions
"
                 + "
You might be used to <tt>if</tt> statements in imperative programming languages. Haskell has <tt>if</tt> expressions, which evaluate to either the <tt>then</tt> value or the <tt>else</tt> value, based on the <tt>if</tt> value.
 Try if 2*2==4 then "happy" else "sad".
"
             return msg + next_step;
          }
        },

        // lots of detail on if Expressions now
        {
         trigger:function(result){
             // result is String
             // expression contains if
             var stringResult =
                 (result.type === "String") ||
                 (result.type === "[Char]");
             var ifExpr = /if/.test(result.expr);
             return stringResult && ifExpr;
         },
         guide:function(result){
            var msg = "
This if expression returned "+result.value+", as you might expect.
";
            var next_step =
		"
The Haskell if expression is equivalent to the <tt>?:</tt> ternary operator in C-like languages. The first subexpression (after the if) must have type Bool, then the subsequent two subexpressions (after then and else respectively) must have the same type as each other.
"
                + "
What happens if we supply a non-Boolean value for the first subexpression? Trye if 1 then 0 else -1.
";
	      tutorial22.continueOnError = true;
            return msg+next_step;
          }
        },

        // if expr, type error
        {
          trigger:function(result){
	      var typeError =
		  (/^\s+Couldn\'t\s+match\s+expected\s+type/.test(result.error)) ||
		  (/^\s+No\s+instance\s+for/.test(result.error)) ||
                  (/^\s+Could\s+not\s+deduce/.test(result.error));
              return typeError;
          },
          guide:function(result){
              tutorial22.continueOnError = true;
              return   "
As you can see, this if expression fails to evaluate. Haskell tries to interpret the first subexpression as a Bool value, and fails.

There are other ways to have typing issues with if expressions, for instance when the then and else subexpressions have different types. Try if False then 42 else "foo".
";
          }
        },

        // then/else expr, type error
        {
          trigger:function(result){
	      var typeError =
		  (/^\s+Couldn\'t\s+match\s+expected\s+type/.test(result.error)) ||
		  (/^\s+No\s+instance\s+for/.test(result.error)) ||
                  (/^\s+Could\s+not\s+deduce/.test(result.error));
              return typeError;
          },
          guide:function(result){
              tutorial22.continueOnError = false;
              return "
Once again, this if expression fails to evaluate because of type errors. Haskell detects that the then value and the else value have incompatible types, so it complains.
"
                  + "
It is possible to have two values that are similar, i.e. they could be specialized to the same type, based on their type classes. For instance, try if True then 42 else pi.
";
          }
        },

        {
	  trigger:function(result){
	    return true;
	  },
	  guide:function(result){

	    var msg="
And that's the end of Tutorial 2.2!
" +
	    		"
Well done, you have completed another Haskell tutorial!.
" +
	    		"
Let's recap what we've just discovered:
" +
	            '
'+
	            '
Boolean not, &&,||  and xor operations
' +
		    '
Boolean functions on lists
' +
                    '
if/then/else conditional expressions
' +
	            '

';

	    return msg;
	  },
	},
    {title:'Haskell Interactive Tutorials',
      	 guide:
      	       '
Haskell Interactive Tutorials
' +
      	       '
In this environment you can try out Haskell code or take tutorials that guide you by prompting you to enter pieces of code and give you feedback on them. '+
      	       'Each tutorial has its own url,'+
      	       ' for this tutorial the url is '+tutorial22.url+'.
'+
      	       '
Functionality
' +
      	       '
This coding environment does not offer all the functionality of the Haskell compiler <tt>ghc</tt> or the interactive Haskell interpreter <tt>ghci</tt>, '+
      	       'because that would allow hackers to compromise your computer. Any feature that could potentially be a security risk has been disabled.
'+
      	       '
Only the following IO actions are supported in this app (more about this later in the course).
' +
      	       '
Navigation commands
' +
      	       '
To navigate between steps in the tutorial you can use following commands:
' +
      	       '
' +
      	       '
To start the tutorial: type start at the λ prompt
' +
      	       '
To go to the next step: next
' +
      	       '
To go to the previous step: prev or back
' +
      	       '
To go to a particular step n: stepn
' +
      	       '
To see this help message: help '+
      	       '
' +
      	       '
Manipulating the context
' +
      	       '
The environment keeps a list of all equations you defined, we call this the context. Every computation you execute uses this context as follows:
' +
      	       '
'+
      	       'let
'+
      	       '  context
'+
      	       'in
'+
      	       '  your_expression'+
      	       '
' +
  	    	   '
You can access and manipulate the context in the following ways:
' +
  	    	   '
' +
      	       '
Inspect the context: context or show
' +
      	       '
Remove the last added equation: undo
' +
      	       '
Remove the equation for a given variable: forget varname
' +
   	       '
Erase the entire context: erase or wipe
' +
   	       '
Reset the tutorial completely: reset
' +
      	       '
'
      	      },

]
<|endoftext|>