<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name CanvasView
 *
 * @private
 */
var CanvasView = View.extend(/** @lends CanvasView# */{
	/**
	 * Creates a view object that wraps a canvas element.
	 *
	 * @param {HTMLCanvasElement} canvas The canvas object that this view should
	 * wrap
	 */
	initialize: function(canvas) {
		// Handle canvas argument
		if (!(canvas instanceof HTMLCanvasElement)) {
			// 2nd argument onwards could be view size, otherwise use default:
			var size = Size.read(arguments, 1);
			if (size.isZero())
				size = new Size(1024, 768);
			canvas = CanvasProvider.getCanvas(size);
		}
		this._context = canvas.getContext('2d');
		// Have Item count installed mouse events.
		this._eventCounters = {};
		this.base(canvas);
	},

	/**
	 * Draws the view.
	 *
	 * @name View#draw
	 * @function
	 */
	draw: function(checkRedraw) {
		if (checkRedraw && !this._redrawNeeded)
			return false;
		if (this._stats)
			this._stats.update();
		// Initial tests conclude that clearing the canvas using clearRect
		// is always faster than setting canvas.width = canvas.width
		// http://jsperf.com/clearrect-vs-setting-width/7
		var ctx = this._context,
			size = this._viewSize;
		ctx.clearRect(0, 0, size._width + 1, size._height + 1);

		ctx.save();
		this._matrix.applyToContext(ctx);
		this._project.draw(ctx);
		ctx.restore();
		this._redrawNeeded = false;
		return true;
	}
}, new function() { // Item based mouse handling:

	var hitOptions = {
		fill: true,
		stroke: true,
		tolerance: 0
	};

	var downPoint,
		lastPoint,
		overPoint,
		downItem,
		overItem,
		hasDrag,
		doubleClick,
		clickTime;

	function callEvent(type, event, point, target, lastPoint, bubble) {
		var item = target,
			mouseEvent,
			called = false;
		while (item) {
			if (item.responds(type)) {
				if (!mouseEvent)
					mouseEvent = new MouseEvent(type, event, point, target,
							// Calculate delta if lastPoint was passed
							lastPoint ? point.subtract(lastPoint) : null);
				called = item.fire(type, mouseEvent) || called;
				if (called && (!bubble || mouseEvent._stopped))
					break;
			}
			item = item.getParent();
		}
		return called;
	}

	function handleEvent(view, type, event, point, lastPoint) {
		if (view._eventCounters[type]) {
			var hit = view._project.hitTest(point, hitOptions),
				item = hit && hit.item;
			if (item) {
				// If this is a mousemove event and we change the over item,
				// reset lastPoint to point so delta is (0, 0)
				if (type == 'mousemove' && item != overItem)
					lastPoint = point;
				// If we have a downItem with a mousedrag event, do not send
				// mousemove events to any item while we're dragging.
				// TODO: Do we also need to lock mousenter / mouseleave in the
				// same way?
				if (type != 'mousemove' || !hasDrag)
					callEvent(type, event, point, item, lastPoint);
				return item;
			}
		}
	}

	return {
		_onMouseDown: function(event, point) {
			var item = handleEvent(this, 'mousedown', event, point);
			// See if we're clicking again on the same item, within the
			// double-click time. Firefox uses 300ms as the max time difference:
			doubleClick = downItem == item && Date.now() - clickTime < 300;
			downItem = item;
			downPoint = lastPoint = overPoint = point;
			hasDrag = downItem && downItem.responds('mousedrag');
		},

		_onMouseUp: function(event, point) {
			// TODO: Check
			var item = handleEvent(this, 'mouseup', event, point);
			if (hasDrag) {
				// If the point has changed since the last mousedrag event, send
				// another one
				if (lastPoint && !lastPoint.equals(point))
					callEvent('mousedrag', event, point, downItem, lastPoint);
				// If we had a mousedrag event locking mousemove events and are
				// over another item, send it a mousemove event now.
				// Use point as overPoint, so delta is (0, 0) since this will
				// be the first mousemove event for this item.
				if (item != downItem) {
					overPoint = point;
					callEvent('mousemove', event, point, item, overPoint);
				}
			}
			if (item == downItem) {
				clickTime = Date.now();
				callEvent(doubleClick ? 'doubleclick' : 'click', event,
						downPoint, overItem);
				doubleClick = false;
			}
			downItem = null;
			hasDrag = false;
		},

		_onMouseMove: function(event, point) {
			// Call the mousedrag event first if an item was clicked earlier
			if (downItem)
				callEvent('mousedrag', event, point, downItem, lastPoint);
			var item = handleEvent(this, 'mousemove', event, point, overPoint);
			lastPoint = overPoint = point;
			if (item != overItem) {
				callEvent('mouseleave', event, point, overItem);
				overItem = item;
				callEvent('mouseenter', event, point, item);
			}
		}
	};
});

/*#*/ if (options.server) {
// Node.js server based image exporting code.
CanvasView.inject(new function() {
	var path = require('path');
	// Utility function that converts a number to a string with
	// x amount of padded 0 digits:
	function toPaddedString(number, length) {
		var str = number.toString(10);
		for (var i = 0, l = length - str.length; i < l; i++) {
			str = '0' + str;
		}
		return str;
	}
	return {
		// DOCS: CanvasView#exportFrames(param);
		exportFrames: function(param) {
			param = Base.merge({
				fps: 30,
				prefix: 'frame-',
				amount: 1
			}, param);
			if (!param.directory) {
				throw new Error('Missing param.directory');
			}
			var view = this,
				count = 0,
				frameDuration = 1 / param.fps,
				lastTime = startTime = Date.now();

			// Start exporting frames by exporting the first frame:
			exportFrame(param);

			function exportFrame(param) {
				count++;
				var filename = param.prefix + toPaddedString(count, 6) + '.png',
					uri = param.directory + '/' + filename;
				var out = view.exportImage(uri, function() {
					// When the file has been closed, export the next fame:
					var then = Date.now();
					if (param.onProgress) {
						param.onProgress({
							count: count,
							amount: param.amount,
							percentage: Math.round(count / param.amount
									* 10000) / 100,
							time: then - startTime,
							delta: then - lastTime
						});
					}
					lastTime = then;
					if (count < param.amount) {
						exportFrame(param);
					} else {
						// Call onComplete handler when finished:
						if (param.onComplete) {
							param.onComplete();
						}
					}
				});
				if (view.onFrame) {
					view.onFrame({
						delta: frameDuration,
						time: frameDuration * count,
						count: count
					});
				}
			}
		},
		// DOCS: View#exportImage(uri, callback);
		exportImage: function(uri, callback) {
			this.draw();
			// TODO: is it necessary to resolve the path?
			var out = fs.createWriteStream(path.resolve(__dirname, uri)),
				stream = this._element.createPNGStream();
			// Pipe the png stream to the write stream:
			stream.pipe(out);
			if (callback) {
				out.on('close', callback);
			}
			return out;
		}
	};
});
/*#*/ } // options.server
<commit_msg>Fix a comment.<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name CanvasView
 *
 * @private
 */
var CanvasView = View.extend(/** @lends CanvasView# */{
	/**
	 * Creates a view object that wraps a canvas element.
	 *
	 * @param {HTMLCanvasElement} canvas The canvas object that this view should
	 * wrap
	 */
	initialize: function(canvas) {
		// Handle canvas argument
		if (!(canvas instanceof HTMLCanvasElement)) {
			// 2nd argument onwards could be view size, otherwise use default:
			var size = Size.read(arguments, 1);
			if (size.isZero())
				size = new Size(1024, 768);
			canvas = CanvasProvider.getCanvas(size);
		}
		this._context = canvas.getContext('2d');
		// Have Item count installed mouse events.
		this._eventCounters = {};
		this.base(canvas);
	},

	/**
	 * Draws the view.
	 *
	 * @name View#draw
	 * @function
	 */
	draw: function(checkRedraw) {
		if (checkRedraw && !this._redrawNeeded)
			return false;
		if (this._stats)
			this._stats.update();
		// Initial tests conclude that clearing the canvas using clearRect
		// is always faster than setting canvas.width = canvas.width
		// http://jsperf.com/clearrect-vs-setting-width/7
		var ctx = this._context,
			size = this._viewSize;
		ctx.clearRect(0, 0, size._width + 1, size._height + 1);

		ctx.save();
		this._matrix.applyToContext(ctx);
		this._project.draw(ctx);
		ctx.restore();
		this._redrawNeeded = false;
		return true;
	}
}, new function() { // Item based mouse handling:

	var hitOptions = {
		fill: true,
		stroke: true,
		tolerance: 0
	};

	var downPoint,
		lastPoint,
		overPoint,
		downItem,
		overItem,
		hasDrag,
		doubleClick,
		clickTime;

	function callEvent(type, event, point, target, lastPoint, bubble) {
		var item = target,
			mouseEvent,
			called = false;
		while (item) {
			if (item.responds(type)) {
				if (!mouseEvent)
					mouseEvent = new MouseEvent(type, event, point, target,
							// Calculate delta if lastPoint was passed
							lastPoint ? point.subtract(lastPoint) : null);
				called = item.fire(type, mouseEvent) || called;
				if (called && (!bubble || mouseEvent._stopped))
					break;
			}
			item = item.getParent();
		}
		return called;
	}

	function handleEvent(view, type, event, point, lastPoint) {
		if (view._eventCounters[type]) {
			var hit = view._project.hitTest(point, hitOptions),
				item = hit && hit.item;
			if (item) {
				// If this is a mousemove event and we change the overItem,
				// reset lastPoint to point so delta is (0, 0)
				if (type == 'mousemove' && item != overItem)
					lastPoint = point;
				// If we have a downItem with a mousedrag event, do not send
				// mousemove events to any item while we're dragging.
				// TODO: Do we also need to lock mousenter / mouseleave in the
				// same way?
				if (type != 'mousemove' || !hasDrag)
					callEvent(type, event, point, item, lastPoint);
				return item;
			}
		}
	}

	return {
		_onMouseDown: function(event, point) {
			var item = handleEvent(this, 'mousedown', event, point);
			// See if we're clicking again on the same item, within the
			// double-click time. Firefox uses 300ms as the max time difference:
			doubleClick = downItem == item && Date.now() - clickTime < 300;
			downItem = item;
			downPoint = lastPoint = overPoint = point;
			hasDrag = downItem && downItem.responds('mousedrag');
		},

		_onMouseUp: function(event, point) {
			// TODO: Check
			var item = handleEvent(this, 'mouseup', event, point);
			if (hasDrag) {
				// If the point has changed since the last mousedrag event, send
				// another one
				if (lastPoint && !lastPoint.equals(point))
					callEvent('mousedrag', event, point, downItem, lastPoint);
				// If we had a mousedrag event locking mousemove events and are
				// over another item, send it a mousemove event now.
				// Use point as overPoint, so delta is (0, 0) since this will
				// be the first mousemove event for this item.
				if (item != downItem) {
					overPoint = point;
					callEvent('mousemove', event, point, item, overPoint);
				}
			}
			if (item == downItem) {
				clickTime = Date.now();
				callEvent(doubleClick ? 'doubleclick' : 'click', event,
						downPoint, overItem);
				doubleClick = false;
			}
			downItem = null;
			hasDrag = false;
		},

		_onMouseMove: function(event, point) {
			// Call the mousedrag event first if an item was clicked earlier
			if (downItem)
				callEvent('mousedrag', event, point, downItem, lastPoint);
			var item = handleEvent(this, 'mousemove', event, point, overPoint);
			lastPoint = overPoint = point;
			if (item != overItem) {
				callEvent('mouseleave', event, point, overItem);
				overItem = item;
				callEvent('mouseenter', event, point, item);
			}
		}
	};
});

/*#*/ if (options.server) {
// Node.js server based image exporting code.
CanvasView.inject(new function() {
	var path = require('path');
	// Utility function that converts a number to a string with
	// x amount of padded 0 digits:
	function toPaddedString(number, length) {
		var str = number.toString(10);
		for (var i = 0, l = length - str.length; i < l; i++) {
			str = '0' + str;
		}
		return str;
	}
	return {
		// DOCS: CanvasView#exportFrames(param);
		exportFrames: function(param) {
			param = Base.merge({
				fps: 30,
				prefix: 'frame-',
				amount: 1
			}, param);
			if (!param.directory) {
				throw new Error('Missing param.directory');
			}
			var view = this,
				count = 0,
				frameDuration = 1 / param.fps,
				lastTime = startTime = Date.now();

			// Start exporting frames by exporting the first frame:
			exportFrame(param);

			function exportFrame(param) {
				count++;
				var filename = param.prefix + toPaddedString(count, 6) + '.png',
					uri = param.directory + '/' + filename;
				var out = view.exportImage(uri, function() {
					// When the file has been closed, export the next fame:
					var then = Date.now();
					if (param.onProgress) {
						param.onProgress({
							count: count,
							amount: param.amount,
							percentage: Math.round(count / param.amount
									* 10000) / 100,
							time: then - startTime,
							delta: then - lastTime
						});
					}
					lastTime = then;
					if (count < param.amount) {
						exportFrame(param);
					} else {
						// Call onComplete handler when finished:
						if (param.onComplete) {
							param.onComplete();
						}
					}
				});
				if (view.onFrame) {
					view.onFrame({
						delta: frameDuration,
						time: frameDuration * count,
						count: count
					});
				}
			}
		},
		// DOCS: View#exportImage(uri, callback);
		exportImage: function(uri, callback) {
			this.draw();
			// TODO: is it necessary to resolve the path?
			var out = fs.createWriteStream(path.resolve(__dirname, uri)),
				stream = this._element.createPNGStream();
			// Pipe the png stream to the write stream:
			stream.pipe(out);
			if (callback) {
				out.on('close', callback);
			}
			return out;
		}
	};
});
/*#*/ } // options.server
<|endoftext|>


<commit_before>import assert from 'assert';
import Intl from 'intl';
import areIntlLocalesSupported from 'intl-locales-supported';
import i18n, { i18nConfig }  from '../lib';

if (global.Intl) {
    // Determine if the built-in `Intl` has the locale data we need.
    if (!areIntlLocalesSupported(['en-US', 'de-DE'])) {
        // `Intl` exists, but it doesn't have the data we need, so load the
        // polyfill and patch the constructors we need with the polyfill's.
        var IntlPolyfill    = Intl;
        Intl.NumberFormat   = IntlPolyfill.NumberFormat;
        Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
    }
} else {
    // No `Intl`, so use and load the polyfill.
    global.Intl = Intl;
}

describe('es2015-i18n-tag', () => {
    it(`should not translate`, () => {
        const name = 'Steffen'
        const amount = 1250.33

        i18nConfig({
            locales: 'en-US',
            number: {
                currency: 'USD'
            }
        })

        const actual = i18n`Hello ${name}, you have ${amount}:c in your bank account.`

        const expected = "Hello Steffen, you have $1,250.33 in your bank account."
        assert.equal(actual, expected);
    })

    it(`should translate to german`, () => {
        const name = 'Steffen'
        const amount = 1250.33

        i18nConfig({
            locales: 'de-DE',
            translations: {
                "Hello ${0}, you have ${1} in your bank account.": "Hallo ${0}, Sie haben ${1} auf Ihrem Bankkonto."
            },
            number: {
                currency: 'EUR'
            }
        })

        const actual = i18n`Hello ${name}, you have ${amount}:c in your bank account.`

        const expected = "Hallo Steffen, Sie haben € 1,250.33 auf Ihrem Bankkonto."
        assert.equal(actual, expected);
    })

    it(`should have 12H date`, () => {
        const name = 'Steffen'
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'de-DE',
            date: {
                hour12: false
            }
        })

        let actual = i18n`Hello ${name}, the date is ${date}:t.`

        let expected = "Hello Steffen, the date is 2012-12-20 19:00:00."
        assert.equal(actual, expected);

        i18nConfig({
            locales: 'de-DE',
            date: {
                hour12: true
            }
        })

        actual = i18n`Hello ${name}, the date is ${date}:t.`

        expected = "Hello Steffen, the date is 2012-12-20 7:00:00 PM."
        assert.equal(actual, expected);
    })

    it(`should format percentage`, () => {
        const name = 'Steffen'
        const percentage = 0.1;

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`Hello ${name}, the percentage is ${percentage}:p.`

        let expected = "Hello Steffen, the percentage is 10%."
        assert.equal(actual, expected);
    })

    it(`should support nested templates`, () => {
        i18nConfig({
            locales: 'en-US'
        })

        let hello = [
            { name: "Steffen", percentage: 0.2 },
            { name: "Jack", percentage: 0.8 }
        ]

        let actual = i18n`
        <users>
        ${hello.map((item) => i18n`
            <user name="${item.name}">${item.percentage}:p</user>
        `).join('')}
        </users>`

        let expected = `
        <users>

            <user name="Steffen">20%</user>

            <user name="Jack">80%</user>

        </users>`
        assert.equal(actual, expected);
    })

    it(`should support standard date format string "d"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(d).`

        let expected = "The date is 12/20/2012."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "D"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(D).`

        let expected = "The date is Thursday, December 20, 2012."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "f"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(f).`

        let expected = "The date is Thursday, December 20, 2012, 7:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "F"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(F).`

        let expected = "The date is Thursday, December 20, 2012, 7:00:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "g"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(g).`

        let expected = "The date is 12/20/2012, 7:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "G"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(G).`

        let expected = "The date is 12/20/2012, 7:00:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "M", "m"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(m).`

        let expected = "The date is December 20."
        assert.equal(actual, expected);
    })

    it(`should support standard date format string "O", "o"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(o).`

        let expected = "The date is 2012-12-20T18:00:00.000Z."
        assert.equal(actual, expected);
    })

    it(`should support standard date format string "R", "r"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(r).`

        let expected = "The date is Thu, 20 Dec 2012 18:00:00 GMT."
        assert.equal(actual, expected);
    })

    it(`should support standard date format string "t"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(t).`

        let expected = "The date is 7:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "T"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(T).`

        let expected = "The date is 7:00:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "Y", "y"`, () => {
        const date = new Date(2012, 11, 20, 19, 0, 0);

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`The date is ${date}:t(y).`

        let expected = "The date is December 2012."
        assert.equal(actual, expected);
    })
})

<commit_msg>Changed timezone in tests to GMT<commit_after>import assert from 'assert';
import Intl from 'intl';
import areIntlLocalesSupported from 'intl-locales-supported';
import i18n, { i18nConfig }  from '../lib';

if (global.Intl) {
    // Determine if the built-in `Intl` has the locale data we need.
    if (!areIntlLocalesSupported(['en-US', 'de-DE'])) {
        // `Intl` exists, but it doesn't have the data we need, so load the
        // polyfill and patch the constructors we need with the polyfill's.
        var IntlPolyfill    = Intl;
        Intl.NumberFormat   = IntlPolyfill.NumberFormat;
        Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
    }
} else {
    // No `Intl`, so use and load the polyfill.
    global.Intl = Intl;
}

describe('es2015-i18n-tag', () => {
    it(`should not translate`, () => {
        const name = 'Steffen'
        const amount = 1250.33

        i18nConfig({
            locales: 'en-US',
            number: {
                currency: 'USD'
            }
        })

        const actual = i18n`Hello ${name}, you have ${amount}:c in your bank account.`

        const expected = "Hello Steffen, you have $1,250.33 in your bank account."
        assert.equal(actual, expected);
    })

    it(`should translate to german`, () => {
        const name = 'Steffen'
        const amount = 1250.33

        i18nConfig({
            locales: 'de-DE',
            translations: {
                "Hello ${0}, you have ${1} in your bank account.": "Hallo ${0}, Sie haben ${1} auf Ihrem Bankkonto."
            },
            number: {
                currency: 'EUR'
            }
        })

        const actual = i18n`Hello ${name}, you have ${amount}:c in your bank account.`

        const expected = "Hallo Steffen, Sie haben € 1,250.33 auf Ihrem Bankkonto."
        assert.equal(actual, expected);
    })

    it(`should have 12H date`, () => {
        const name = 'Steffen'
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'de-DE',
            date: {
                hour12: false,
                timeZone: 'GMT'
            }
        })

        let actual = i18n`Hello ${name}, the date is ${date}:t.`

        let expected = "Hello Steffen, the date is 2012-12-20 18:00:00."
        assert.equal(actual, expected);

        i18nConfig({
            locales: 'de-DE',
            date: {
                hour12: true,
                timeZone: 'GMT'
            }
        })

        actual = i18n`Hello ${name}, the date is ${date}:t.`

        expected = "Hello Steffen, the date is 2012-12-20 6:00:00 PM."
        assert.equal(actual, expected);
    })

    it(`should format percentage`, () => {
        const name = 'Steffen'
        const percentage = 0.1;

        i18nConfig({
            locales: 'en-US'
        })

        let actual = i18n`Hello ${name}, the percentage is ${percentage}:p.`

        let expected = "Hello Steffen, the percentage is 10%."
        assert.equal(actual, expected);
    })

    it(`should support nested templates`, () => {
        i18nConfig({
            locales: 'en-US'
        })

        let hello = [
            { name: "Steffen", percentage: 0.2 },
            { name: "Jack", percentage: 0.8 }
        ]

        let actual = i18n`
        <users>
        ${hello.map((item) => i18n`
            <user name="${item.name}">${item.percentage}:p</user>
        `).join('')}
        </users>`

        let expected = `
        <users>

            <user name="Steffen">20%</user>

            <user name="Jack">80%</user>

        </users>`
        assert.equal(actual, expected);
    })

    it(`should support standard date format string "d"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(d).`

        let expected = "The date is 12/20/2012."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "D"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(D).`

        let expected = "The date is Thursday, December 20, 2012."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "f"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(f).`

        let expected = "The date is Thursday, December 20, 2012, 6:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "F"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(F).`

        let expected = "The date is Thursday, December 20, 2012, 6:00:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "g"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(g).`

        let expected = "The date is 12/20/2012, 6:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "G"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(G).`

        let expected = "The date is 12/20/2012, 6:00:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "M", "m"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(m).`

        let expected = "The date is December 20."
        assert.equal(actual, expected);
    })

    it(`should support standard date format string "O", "o"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(o).`

        let expected = "The date is 2012-12-20T18:00:00.000Z."
        assert.equal(actual, expected);
    })

    it(`should support standard date format string "R", "r"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(r).`

        let expected = "The date is Thu, 20 Dec 2012 18:00:00 GMT."
        assert.equal(actual, expected);
    })

    it(`should support standard date format string "t"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(t).`

        let expected = "The date is 6:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "T"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(T).`

        let expected = "The date is 6:00:00 PM."
        assert.equal(actual, expected);

    })

    it(`should support standard date format string "Y", "y"`, () => {
        const date = new Date('Thu, 20 Dec 2012 18:00:00 GMT');

        i18nConfig({
            locales: 'en-US',
            date: {
                timeZone: 'GMT'
            }
        })

        let actual = i18n`The date is ${date}:t(y).`

        let expected = "The date is December 2012."
        assert.equal(actual, expected);
    })
})

<|endoftext|>


<commit_before>// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

base.requireStylesheet('ui.quad_stack');

base.require('base.bbox2');
base.require('base.quad');
base.require('base.raf');
base.require('ui.quad_view');
base.require('cc.region');

base.exportTo('ui', function() {
  var QuadView = ui.QuadView;

  function lerp(a, b, interp) {
    return (a * (1 - interp)) +
        (b * interp);
  }

  function validateQuads(quads) {
    for (var i = 0; i < quads.length; i++) {
      if (quads[i].stackingGroupId === undefined)
        throw new Error('All quads must have stackingGroupIds');
    }
  }

  /**
   * @constructor
   */
  var QuadStack = ui.define('quad-stack');

  QuadStack.prototype = {
    __proto__: HTMLUnknownElement.prototype,

    decorate: function() {
      this.contentContainer_ = document.createElement('view-container');
      this.appendChild(this.contentContainer_);
      this.viewport_ = undefined;
      this.quads_ = undefined;
      this.deviceViewportSizeForFrame_ = undefined;

      this.onMouseDown_ = this.onMouseDown_.bind(this);
      this.onMouseMove_ = this.onMouseMove_.bind(this);
      this.onMouseUp_ = this.onMouseUp_.bind(this);
      this.addEventListener('mousedown', this.onMouseDown_);

      this.cameraStart_ = {x: 0, y: 0};
      this.rotations_ = {x: 0, y: 0};
      this.rotationStart_ = {x: 0, y: 0};
      this.matrixParameters_ = {
        thicknessRatio: 0.012, // Ratio of thickness to world size.
        strengthRatioX: 0.7, // Ratio of mousemove X pixels to degrees rotated.
        strengthRatioY: 0.25 // Ratio of mousemove Y pixels to degrees rotated.
      };
    },

    setQuadsViewportAndDeviceViewportSize: function(
        quads, viewport, deviceViewportSizeForFrame, opt_insideRAF) {
      validateQuads(quads);
      this.quads_ = quads;
      this.viewport_ = viewport;
      this.deviceViewportSizeForFrame_ = deviceViewportSizeForFrame;
      this.updateContents_();
      if (opt_insideRAF && this.repaintPending_)
        this.repaint_();
    },

    get quads() {
      return this.quads_;
    },

    set quads(quads) {
      validateQuads(quads);
      this.quads_ = quads;
      this.updateContents_();
    },

    get viewport() {
      return this.viewport_;
    },

    set viewport(viewport) {
      this.viewport_ = viewport;
      this.updateContents_();
    },

    get deviceViewportSizeForFrame() {
      return this.deviceViewportSizeForFrame_;
    },

    set deviceViewportSizeForFrame(deviceViewportSizeForFrame) {
      this.deviceViewportSizeForFrame_ = deviceViewportSizeForFrame;
      this.updateContents_();
    },

    updateContents_: function() {
      var percX, percY;
      if (this.viewport_) {
        var layoutRect = this.viewport_.layoutRect;
        percX = 100 * (Math.abs(layoutRect.x) / layoutRect.width);
        percX = 100 * (Math.abs(layoutRect.y) / layoutRect.height);
        this.style.width = layoutRect.width + 'px';
        this.style.height = layoutRect.height + 'px';
      } else {
        percX = '50';
        percY = '50';
      }
      this.style.webkitPerspectiveOrigin = percX + '% ' + percY + '%';

      // Build the stacks.
      var stackingGroupsById = {};
      var quads = this.quads;
      for (var i = 0; i < quads.length; i++) {
        var quad = quads[i];
        if (stackingGroupsById[quad.stackingGroupId] === undefined)
          stackingGroupsById[quad.stackingGroupId] = [];
        stackingGroupsById[quad.stackingGroupId].push(quad);
      }

      // Get rid of old quad views if needed.
      var numStackingGroups = base.dictionaryValues(stackingGroupsById).length;
      while (this.contentContainer_.children.length > numStackingGroups) {
        var n = this.contentContainer_.children.length - 1;
        this.contentContainer_.removeChild(
            this.contentContainer_.children[n]);
      }

      // Helper function to create a new quad view and track the current one.
      var that = this;
      var curQuadViewIndex = -1;
      var curQuadView = undefined;
      function appendNewQuadView() {
        curQuadViewIndex++;
        if (curQuadViewIndex < that.contentContainer_.children.length) {
          curQuadView = that.contentContainer_.children[curQuadViewIndex];
        } else {
          curQuadView = new QuadView();
          that.contentContainer_.appendChild(curQuadView);
        }
        curQuadView.quads = undefined;
        curQuadView.viewport = that.viewport_;
        curQuadView.pendingQuads = [];
        curQuadView.region = new cc.Region();
        return curQuadView;
      }

      appendNewQuadView();
      for (var stackingGroupId in stackingGroupsById) {
        var stackingGroup = stackingGroupsById[stackingGroupId];
        var bbox = new base.BBox2();
        stackingGroup.forEach(function(q) { bbox.addQuad(q); });
        var bboxRect = bbox.asRect();

        if (curQuadView.region.rectIntersects(bboxRect))
          appendNewQuadView();
        curQuadView.region.rects.push(bboxRect);
        stackingGroup.forEach(function(q) {
          curQuadView.pendingQuads.push(q);
        });
      }

      for (var i = 0; i < this.contentContainer_.children.length; i++) {
        var child = this.contentContainer_.children[i];
        if (i != this.contentContainer_.children.length - 1) {
          child.deviceViewportSizeForFrame = undefined;
        } else {
          child.drawDeviceViewportMask = true;
          child.deviceViewportSizeForFrame =
              this.deviceViewportSizeForFrame_;
        }
        child.quads = child.pendingQuads;
        delete child.pendingQuads;
      }

      this.scheduleRepaint_();
    },

    scheduleRepaint_: function() {
      if (this.repaintPending_)
        return;
      this.repaintPending_ = true;
      base.requestAnimationFrameInThisFrameIfPossible(
          this.repaint_, this);
    },

    repaint_: function() {
      if (!this.repaintPending_)
        return;
      this.repaintPending_ = false;
      var layers = this.contentContainer_.children;
      var numLayers = layers.length;

      var vpThickness = this.matrixParameters_.thicknessRatio *
          Math.min(this.viewport_.worldRect.width,
                   this.viewport_.worldRect.height);
      vpThickness = Math.max(vpThickness, 15);

      // When viewing the stack head-on, we want no foreshortening effects. As
      // we move off axis, let the thickness grow as well as the amount of
      // perspective foreshortening.
      var maxRotation = Math.max(Math.abs(this.rotations_.x),
                                 Math.abs(this.rotations_.y));
      var clampLimit = 30;
      var clampedMaxRotation = Math.min(maxRotation, clampLimit);
      var percentToClampLimit = clampedMaxRotation / clampLimit;
      var persp = Math.pow(Math.E,
                           lerp(Math.log(5000), Math.log(250),
                                percentToClampLimit));
      this.style.webkitPerspective = persp;
      var effectiveThickness = vpThickness * percentToClampLimit;

      // Set depth of each layer such that they center around 0.
      var deepestLayerZ = -effectiveThickness * 0.5;
      var depthIncreasePerLayer = effectiveThickness /
          Math.max(1, numLayers - 1);
      for (var i = 0; i < numLayers; i++) {
        var layer = layers[i];
        var newDepth = deepestLayerZ + i * depthIncreasePerLayer;
        layer.style.webkitTransform = 'translateZ(' + newDepth + 'px)';
      }

      // Set rotation matrix to whatever is stored.
      var transformString = '';
      transformString += 'rotateX(' + this.rotations_.x + 'deg)';
      transformString += ' rotateY(' + this.rotations_.y + 'deg)';
      this.contentContainer_.style.webkitTransform = transformString;
    },

    updateCameraStart_: function(x, y) {
      this.cameraStart_.x = x;
      this.cameraStart_.y = y;
      this.rotationStart_.x = this.rotations_.x;
      this.rotationStart_.y = this.rotations_.y;
    },

    updateCamera_: function(x, y) {
      var delta = {
        x: this.cameraStart_.x - x,
        y: this.cameraStart_.y - y
      };
      // update new rotation matrix (note the parameter swap)
      // "strength" is ration between mouse dist and rotation amount.
      this.rotations_.x = this.rotationStart_.x + delta.y *
          this.matrixParameters_.strengthRatioY;
      this.rotations_.y = this.rotationStart_.y + -delta.x *
          this.matrixParameters_.strengthRatioX;
      this.scheduleRepaint_();
    },

    onMouseDown_: function(e) {
      this.updateCameraStart_(e.x, e.y);
      document.addEventListener('mousemove', this.onMouseMove_);
      document.addEventListener('mouseup', this.onMouseUp_);
      e.preventDefault();
      return true;
    },

    onMouseMove_: function(e) {
      this.updateCamera_(e.x, e.y);
    },

    onMouseUp_: function(e) {
      document.removeEventListener('mousemove', this.onMouseMove_);
      document.removeEventListener('mouseup', this.onMouseUp_);
      this.updateCamera_(e.x, e.y);
    }

  };

  return {
    QuadStack: QuadStack
  };
});
<commit_msg>Use less perspective in stack view<commit_after>// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

base.requireStylesheet('ui.quad_stack');

base.require('base.bbox2');
base.require('base.quad');
base.require('base.raf');
base.require('ui.quad_view');
base.require('cc.region');

base.exportTo('ui', function() {
  var QuadView = ui.QuadView;

  function lerp(a, b, interp) {
    return (a * (1 - interp)) +
        (b * interp);
  }

  function validateQuads(quads) {
    for (var i = 0; i < quads.length; i++) {
      if (quads[i].stackingGroupId === undefined)
        throw new Error('All quads must have stackingGroupIds');
    }
  }

  /**
   * @constructor
   */
  var QuadStack = ui.define('quad-stack');

  QuadStack.prototype = {
    __proto__: HTMLUnknownElement.prototype,

    decorate: function() {
      this.contentContainer_ = document.createElement('view-container');
      this.appendChild(this.contentContainer_);
      this.viewport_ = undefined;
      this.quads_ = undefined;
      this.deviceViewportSizeForFrame_ = undefined;

      this.onMouseDown_ = this.onMouseDown_.bind(this);
      this.onMouseMove_ = this.onMouseMove_.bind(this);
      this.onMouseUp_ = this.onMouseUp_.bind(this);
      this.addEventListener('mousedown', this.onMouseDown_);

      this.cameraStart_ = {x: 0, y: 0};
      this.rotations_ = {x: 0, y: 0};
      this.rotationStart_ = {x: 0, y: 0};
      this.matrixParameters_ = {
        thicknessRatio: 0.012, // Ratio of thickness to world size.
        strengthRatioX: 0.7, // Ratio of mousemove X pixels to degrees rotated.
        strengthRatioY: 0.25 // Ratio of mousemove Y pixels to degrees rotated.
      };
    },

    setQuadsViewportAndDeviceViewportSize: function(
        quads, viewport, deviceViewportSizeForFrame, opt_insideRAF) {
      validateQuads(quads);
      this.quads_ = quads;
      this.viewport_ = viewport;
      this.deviceViewportSizeForFrame_ = deviceViewportSizeForFrame;
      this.updateContents_();
      if (opt_insideRAF && this.repaintPending_)
        this.repaint_();
    },

    get quads() {
      return this.quads_;
    },

    set quads(quads) {
      validateQuads(quads);
      this.quads_ = quads;
      this.updateContents_();
    },

    get viewport() {
      return this.viewport_;
    },

    set viewport(viewport) {
      this.viewport_ = viewport;
      this.updateContents_();
    },

    get deviceViewportSizeForFrame() {
      return this.deviceViewportSizeForFrame_;
    },

    set deviceViewportSizeForFrame(deviceViewportSizeForFrame) {
      this.deviceViewportSizeForFrame_ = deviceViewportSizeForFrame;
      this.updateContents_();
    },

    updateContents_: function() {
      var percX, percY;
      if (this.viewport_) {
        var layoutRect = this.viewport_.layoutRect;
        percX = 100 * (Math.abs(layoutRect.x) / layoutRect.width);
        percX = 100 * (Math.abs(layoutRect.y) / layoutRect.height);
        this.style.width = layoutRect.width + 'px';
        this.style.height = layoutRect.height + 'px';
      } else {
        percX = '50';
        percY = '50';
      }
      this.style.webkitPerspectiveOrigin = percX + '% ' + percY + '%';

      // Build the stacks.
      var stackingGroupsById = {};
      var quads = this.quads;
      for (var i = 0; i < quads.length; i++) {
        var quad = quads[i];
        if (stackingGroupsById[quad.stackingGroupId] === undefined)
          stackingGroupsById[quad.stackingGroupId] = [];
        stackingGroupsById[quad.stackingGroupId].push(quad);
      }

      // Get rid of old quad views if needed.
      var numStackingGroups = base.dictionaryValues(stackingGroupsById).length;
      while (this.contentContainer_.children.length > numStackingGroups) {
        var n = this.contentContainer_.children.length - 1;
        this.contentContainer_.removeChild(
            this.contentContainer_.children[n]);
      }

      // Helper function to create a new quad view and track the current one.
      var that = this;
      var curQuadViewIndex = -1;
      var curQuadView = undefined;
      function appendNewQuadView() {
        curQuadViewIndex++;
        if (curQuadViewIndex < that.contentContainer_.children.length) {
          curQuadView = that.contentContainer_.children[curQuadViewIndex];
        } else {
          curQuadView = new QuadView();
          that.contentContainer_.appendChild(curQuadView);
        }
        curQuadView.quads = undefined;
        curQuadView.viewport = that.viewport_;
        curQuadView.pendingQuads = [];
        curQuadView.region = new cc.Region();
        return curQuadView;
      }

      appendNewQuadView();
      for (var stackingGroupId in stackingGroupsById) {
        var stackingGroup = stackingGroupsById[stackingGroupId];
        var bbox = new base.BBox2();
        stackingGroup.forEach(function(q) { bbox.addQuad(q); });
        var bboxRect = bbox.asRect();

        if (curQuadView.region.rectIntersects(bboxRect))
          appendNewQuadView();
        curQuadView.region.rects.push(bboxRect);
        stackingGroup.forEach(function(q) {
          curQuadView.pendingQuads.push(q);
        });
      }

      for (var i = 0; i < this.contentContainer_.children.length; i++) {
        var child = this.contentContainer_.children[i];
        if (i != this.contentContainer_.children.length - 1) {
          child.deviceViewportSizeForFrame = undefined;
        } else {
          child.drawDeviceViewportMask = true;
          child.deviceViewportSizeForFrame =
              this.deviceViewportSizeForFrame_;
        }
        child.quads = child.pendingQuads;
        delete child.pendingQuads;
      }

      this.scheduleRepaint_();
    },

    scheduleRepaint_: function() {
      if (this.repaintPending_)
        return;
      this.repaintPending_ = true;
      base.requestAnimationFrameInThisFrameIfPossible(
          this.repaint_, this);
    },

    repaint_: function() {
      if (!this.repaintPending_)
        return;
      this.repaintPending_ = false;
      var layers = this.contentContainer_.children;
      var numLayers = layers.length;

      var vpThickness = this.matrixParameters_.thicknessRatio *
          Math.min(this.viewport_.worldRect.width,
                   this.viewport_.worldRect.height);
      vpThickness = Math.max(vpThickness, 15);

      // When viewing the stack head-on, we want no foreshortening effects. As
      // we move off axis, let the thickness grow as well as the amount of
      // perspective foreshortening.
      var maxRotation = Math.max(Math.abs(this.rotations_.x),
                                 Math.abs(this.rotations_.y));
      var clampLimit = 30;
      var clampedMaxRotation = Math.min(maxRotation, clampLimit);
      var percentToClampLimit = clampedMaxRotation / clampLimit;
      var persp = Math.pow(Math.E,
                           lerp(Math.log(5000), Math.log(500),
                                percentToClampLimit));
      this.style.webkitPerspective = persp;
      var effectiveThickness = vpThickness * percentToClampLimit;

      // Set depth of each layer such that they center around 0.
      var deepestLayerZ = -effectiveThickness * 0.5;
      var depthIncreasePerLayer = effectiveThickness /
          Math.max(1, numLayers - 1);
      for (var i = 0; i < numLayers; i++) {
        var layer = layers[i];
        var newDepth = deepestLayerZ + i * depthIncreasePerLayer;
        layer.style.webkitTransform = 'translateZ(' + newDepth + 'px)';
      }

      // Set rotation matrix to whatever is stored.
      var transformString = '';
      transformString += 'rotateX(' + this.rotations_.x + 'deg)';
      transformString += ' rotateY(' + this.rotations_.y + 'deg)';
      this.contentContainer_.style.webkitTransform = transformString;
    },

    updateCameraStart_: function(x, y) {
      this.cameraStart_.x = x;
      this.cameraStart_.y = y;
      this.rotationStart_.x = this.rotations_.x;
      this.rotationStart_.y = this.rotations_.y;
    },

    updateCamera_: function(x, y) {
      var delta = {
        x: this.cameraStart_.x - x,
        y: this.cameraStart_.y - y
      };
      // update new rotation matrix (note the parameter swap)
      // "strength" is ration between mouse dist and rotation amount.
      this.rotations_.x = this.rotationStart_.x + delta.y *
          this.matrixParameters_.strengthRatioY;
      this.rotations_.y = this.rotationStart_.y + -delta.x *
          this.matrixParameters_.strengthRatioX;
      this.scheduleRepaint_();
    },

    onMouseDown_: function(e) {
      this.updateCameraStart_(e.x, e.y);
      document.addEventListener('mousemove', this.onMouseMove_);
      document.addEventListener('mouseup', this.onMouseUp_);
      e.preventDefault();
      return true;
    },

    onMouseMove_: function(e) {
      this.updateCamera_(e.x, e.y);
    },

    onMouseUp_: function(e) {
      document.removeEventListener('mousemove', this.onMouseMove_);
      document.removeEventListener('mouseup', this.onMouseUp_);
      this.updateCamera_(e.x, e.y);
    }

  };

  return {
    QuadStack: QuadStack
  };
});
<|endoftext|>


<commit_before>io.sails.autoConnect = false;
io.sails.url = 'wss://realtime.beam.pro/';
io.sails.transports = ['websocket'];
io.sails.useCORSRouteToGetCookie = false;

socket = io.sails.connect();
socket.on('connect', function() {
    console.log('[Socket] Connected - Subscribing to events');

    clickEvents();
});
socket.on('disconnect', function() {
    setTimeout(function() {
        socket = io.sails.connect();
        console.info('Reconnect detected: Resubbing to all channels.');
        reconnectSubscribe();
    }, 5000);
});

function clickEvents(channelID) {

    $('body').click(function(e) {
        // The element that was clicked.
        // Make sure to use Elem everywhere instead of "this".
        var Elem = e.target;

        // Add Online Streamer
        if($(Elem).hasClass('addonline')){
            var username = $(Elem).attr('name');
            console.log('Clicked online for '+username);
            singleChannelSubscribe(username)
        }

        // Get Started Button
        // Finds username and userid then updates notifications.
        if ($(Elem).hasClass('followeraddbtn')) {
            var localUsername = $('.followeradd').val();
            if (localUsername !== '') {
                localStorage.setItem("username", localUsername);
                socket.request({
                    url: '/api/v1/channels/' + localUsername + '?fields=userId',
                    method: 'get'
                }, function(body, response) {
                    // Get list of people user follows.
                    if (response.statusCode == 200) {
                        var userID = body.userId;
                        localStorage.setItem('userID', userID);
                        jumperUpdate(0);
                    } else {
                        console.error('Error getting the userID number.');
                    }
                });
            }
        }

        // Add Other Box
        if ($(Elem).hasClass('streamerinputaddbtn')) {
            var username = $('.streamerinputadd').val();

            // Local Storage List
            if (username !== '') {
                if ($('.' + username).length === 0) {
                    singleChannelSubscribe(username);
                    var pastAdds = localStorage.getItem("streamer");
                }
                $('.streamerinputadd').val('');
            }

        }

        // Add All Button
        if ($(Elem).hasClass('addallbutton')) {
            var localUsername = localStorage.getItem('username');

            // Local Storage List
            if (localUsername !== '' && localUsername !== undefined) {
                multiChannelSubscribe(localUsername, 0);
            }
        }

        // Close button
        if ($(Elem).hasClass('closebtn')) {
            var stream = $(Elem).attr('stream');
            var username = $('#' + stream + ' h2').text();
            var savedStreams = localStorage.getItem("streamer");
            $(Elem).closest('.stream').remove();
        };

    }); // end on body clicks

}

function singleChannelSubscribe(username) {
    socket.request({
        url: '/api/v1/channels/' + username + '?fields=id,online,token,name,type,partnered,numFollowers',
        method: 'get'
    }, function(body, response) {
        // If response recieved and the div isn't already on the page.
        if (response.statusCode == 200 && $('.' + body.token).length === 0) {
            var channelID = body.id;
            var cName = body.token;
            var cTitle = body.name;
            if (body.type !== null || body.type !== undefined) {
                var cGame = body.type.name;
            } else {
                var cGame = "No Game Set";
            }
            var cPartnered = body.partnered;
            var cFollowers = body.numFollowers;

            var cOnline = body.online;
            buildChannel(channelID, cName, cTitle, cGame, cPartnered, cFollowers);

            // If channel is offline when added, make title gray.
            if (cOnline === false) {
                $('#' + channelID + ' h2').addClass('offline');
            };

            // Unfortunately since the channel is always visible in this mode we need to subscribe to all events.
            // Subscribe to channel updates.
            var updateSlug = ["channel:" + channelID + ":update"];
            socket.request({
                url: '/api/v1/live',
                method: 'put',
                params: {
                    slug: updateSlug
                }
            }, function(body, response) {
                if (response.statusCode == 200) {
                    socket.on(updateSlug, function(data) {
                        onChannelUpdate(channelID, cName, cTitle, cGame, data, cPartnered);
                    });
                } else if (response.statusCode == 420) {
                    console.error('Too many subscriptions');
                } else {
                    console.error('There was an error subbing to the channel update event.', cName);
                }
            });

        } else {
            console.error('Error checking the online state for the channel', username);
        }
    });
}

function multiChannelSubscribe(localUsername, page) {
    // Get user ID.
    socket.request({
        url: '/api/v1/channels/' + localUsername + '?fields=userId',
        method: 'get'
    }, function(body, response) {
        // Get list of people user follows.
        if (response.statusCode == 200) {
            var userID = body.userId;
            localStorage.setItem('userID', userID);
            socket.request({
                url: '/api/v1/users/' + userID + '/follows?limit=50&where=online:eq:1&page='+page,
                method: 'get'
            }, function(body, response) {
                if (response.statusCode == 200) {
                    // Loop through list.
                    $.each(body, function(index, element) {
                        // If channel not suspended and they aren't already on the page (and thus subscribed in websocket)...
                        if (element.suspended !== true && $('#' + element.id).length === 0) {
                            var channelID = element.id;
                            var cName = element.token;
                            var cTitle = element.name;
                            if (element.type !== null && element.type !== undefined && element.type !== "") {
                                var cGame = element.type.name;
                            } else {
                                var cGame = "No Game Set";
                            }
                            var cPartnered = element.partnered;
                            var cFollowers = element.numFollowers;

                            // Sub to channel updates for all followed channels.
                            var updateSlug = ["channel:" + channelID + ":update"];
                            socket.request({
                                url: '/api/v1/live',
                                method: 'put',
                                params: {
                                    slug: updateSlug
                                }
                            }, function(body, response) {
                                if (response.statusCode == 200) {
                                    socket.on(updateSlug, function(data) {
                                        // Debug to log all events for all channels.
                                        // console.log(data, cName);
                                        onChannelUpdate(channelID, cName, cTitle, cGame, data, cPartnered);
                                    });
                                } else if (response.statusCode == 420) {
                                    console.error('Error: Too many subscriptions.');
                                    $('#' + channelID + ' .stream-notice').text('No updates.');
                                } else {
                                    console.error('There was an error subbing to the channel update event.', cName, response.statusCode);
                                }
                            });

                            // If the channel is online then go ahead and build it.
                            buildChannel(channelID, cName, cTitle, cGame, cPartnered, cFollowers);
                        }
                    });

		            // If the number of results equals 50, then we need to check the next page for new items.
		            // Otherwise, we've cycled through all of their followers.
			        if(body.length === 50){
		            	var pageCount = parseInt(page, 10) + 1;
		            	multiChannelSubscribe(localUsername, pageCount);
		            }

                } else {
                    console.error('Error getting the follower list.');
                }
            });

        } else {
            console.error('Error getting the userID number.');
        }
    });
}

// Reconnect Function
// This function is run only when the websocket reconnects.
// It will loop through all divs on page and resubscribe to updates.
function reconnectSubscribe() {
    // When socket reconnects we need to resub to all events.
    var streamerList = []

    // Cycle through all streams on the page already and add them to array.
    // These are the only ones we need to re-sub to in order to get updates again.
    $(".stream").each(function(index) {
        if ($(this).hasClass('streamembed') === false) {
            var streamName = $(this).find('h2').text();
            streamerList.push(streamName);
        }
    });

    $.each(streamerList, function(index, value) {

        socket.request({
            url: '/api/v1/channels/' + value + '?fields=id,online,token,name,type,partnered',
            method: 'get'
        }, function(body, response) {
            // If response recieved
            if (response.statusCode == 200) {
                var channelID = body.id;
                var cName = body.token;
                var cTitle = body.name;
                if (body.type !== null || body.type !== undefined) {
                    var cGame = body.type.name;
                } else {
                    var cGame = "No Game Set";
                }
                var cPartnered = body.partnered;
                var cOnline = body.online;

                // If channel is offline when added, make title gray.
                if (cOnline === false) {
                    $('#' + channelID + ' h2').addClass('offline');
                };

                // Since the channel is always visible in this mode we need to subscribe to all events.
                // Subscribe to channel updates.
                var updateSlug = ["channel:" + channelID + ":update"];
                socket.request({
                    url: '/api/v1/live',
                    method: 'put',
                    params: {
                        slug: updateSlug
                    }
                }, function(body, response) {
                    if (response.statusCode == 200) {
                        socket.on(updateSlug, function(data) {
                            onChannelUpdate(channelID, cName, cTitle, cGame, data, cPartnered);
                        });
                    } else if (response.statusCode == 420) {
                        console.error('Too many subscriptions');
                    } else {
                        console.error('There was an error subbing to the channel update event.', cName);
                    }
                });

            } else {
                console.error('Error checking the online state for the channel', username);
            }
        });
    }); // End each statement
}

// Build Channel
// This function adds the channel to teh page using gathered info.
function buildChannel(channelID, username, title, game, partnered, followers) {

    // If the channel is partnered we're gonna make the name purple.
    if (partnered === true) {
        var partnered = "partnered";
    } else {
        var partnered = "not-partnered";
    }
    // If channel is not already on the page...
    if ($('#' + channelID).length === 0) {
        var streamEmbed = "https://beam.pro/embed/player/" + username;
        var chatEmbed = "https://beam.pro/embed/chat/" + username;

        // Build out the stream on the page.
        $('.channelwrapper').append('

<button class="closebtn" stream="' + channelID + '">X</button>

');
        $('#' + channelID + ' .chatwrap').html('');
        $('#' + channelID + ' .streamtop h2').html('' + username + '');
        $('#' + channelID + ' .cGame').text(game);
        $('#' + channelID + ' .cTitle').text(title);
        $('#' + channelID + ' .streamtop').append('<button class="watch" stream="' + username + '">Watch</button>');
        $('#' + channelID).css('display', 'block');

        // Watch Button to watch streams..
        $('#' + channelID + ' .watch').click(function() {
            $('#' + channelID).before('

<button class="closebtn" stream="' + channelID + '">X</button>

' + username + '

');

            // Set height of chat window to the same height as the video..
            var newHeight = $('.streamembed[stream="' + channelID + '"] iframe').height();
            $('.chatembed').css('height', newHeight);

            // Scroll to element to account for new chat height.
            $('html, body').delay(750).animate({
                scrollTop: $('.streamembed[stream="' + channelID + '"]').offset().top
            }, 1500);

            $('.streamembed[stream="' + channelID + '"] .closestreamembed .closebtn').click(function() {
                $('.streamembed[stream="' + channelID + '"]').remove();
                // Set related chat window back to regular height.
                $('.chatembed').css('height', '400px');
            });
        });

    }
}

// Displays an alert if a new channel comes online that isn't already shown.
function jumperUpdate(page) {

    var userID = localStorage.getItem('userID');
    if (userID !== '' && userID !== undefined && userID !== null){
        socket.request({
            url: '/api/v1/users/' + userID + '/follows?limit=50&where=online:eq:1&page='+page,
            method: 'get'
        }, function(body, response) {
            if (response.statusCode == 200) {
                // Loop through list.
                $.each(body, function(index, element) {
                    var cName = element.token;
                    var partnered = element.partnered;
                    if (partnered === true){
                        var partnered = "partner-notification";
                    }else{
                        var partnered = "regular-notification";
                    }

                    // Remove default notification text
                    $('.default-notify-text').remove();

                    // If channel not suspended and they aren't already on the page (and thus subscribed in websocket)...
                    if (element.suspended !== true && $('#' + element.id).length === 0 && element.online === true) {
                        // Add line to notification box
                        if ( $('.'+cName+'-notification').length < 1){
                            $('.notification').prepend('
<button class="addonline" name="'+cName+'">Add</button>'+cName+'New
');
                        }

                        // If menu is not opened then add a notification alert.
                        if ( $('.menu-link.active').length === 0 ){
                            // Update notification number to match number of new notifications.
                            var notificationNumber = parseInt( $('.menu-notification-alert').html() );
                            var notificationList = $('.notification .notification-new').length;
                            if(notificationNumber !== notificationList){
                                $('.menu-notification-alert').text(notificationList);
                                $('.menu-notification-alert').fadeIn('fast');
                            }
                        }

                        // Show scroll bar once notification area hits 600 px tall.
                        if ( $('.notification').height() >= 400){
                            $('.notification').css('overflow-y','scroll');
                        } else{
                            $('.notification').css('overflow-y','hidden');
                        }

                        // Throw in the add all button if people are online and local username is set.
                        var localUser = localStorage.getItem('username');
                        var onlinePeople = $('.notification-name').length;
                        if ( localUser !== undefined && localUser !== '' && $('.addallbutton').is('visible') === false && onlinePeople > 2){
                            $('.addallbutton').fadeIn('fast');
                        } else {
                            $('.addallbutton').fadeOut('fast');
                        }

                    } else if ( $('#' + element.id).length > 0 && element.online === false ) {

                        // Remove their line from the notification box.
                        $('.'+cName+'-notification').remove();
                    }
                });

	            // If the number of results equals 50, then we need to check the next page for new items.
	            // Otherwise, we've cycled through all of their followers.
	            if(body.length === 50){
	            	var pageCount = parseInt(page, 10) + 1;
	            	console.log("Finished page "+page+" moving on to page "+pageCount+".");
	            	jumperUpdate(pageCount);
	            }

                /*
                // Debug - return channels we're subbed to.
                socket.request({
                    url: '/api/v1/live',
                    method: 'get'
                }, function(body,response){
                        if(response.statusCode == 200){
                            console.log('Returning socket list...');
                            console.log(response);
                        }
                });
                */

            } else {
                console.error('Error getting the follower list.');
            }
        });
    }
}

// Function to run every time new data is recieved from the websocket.
function onChannelUpdate(channelID, username, title, game, data, partnered) {
    // Stream Title Update
    if (data.name !== undefined) {
        var wcTitle = data.name;

        var currentTitle = $('#' + channelID + ' .cTitle').text();
        if (currentTitle != wcTitle) {
            $('#' + channelID + ' .cTitle').text(wcTitle);
        }
    }

    // Stream Game Update
    if (data.type !== undefined) {
        var wGame = data.type.name;
        // Channel Game
        var currentGame = $('#' + channelID + ' .cGame').text();
        if (currentGame != wGame) {
            $('#' + channelID + ' .cGame').text(wGame);
        }
    }

    // Cleanup Bot
    // Runs on every websocket response to add or remove people.
    if (data.online !== undefined && $('.cleanup').prop('checked') === true) {
        var onlineStatus = data.online;
        if (onlineStatus === false) {
            // If offline response recieved then fade the channel out and remove it.
            $('#' + channelID).fadeOut(500, function() {
                $(this).remove();
            });
            $('.'+username+'-notification').remove();
        }
    } else if (data.online !== undefined && $('.cleanup').prop('checked') === false) {
        if (onlineStatus === false) {
            // If autolurk isn't checked and a channel goes offline the streamer name should turn gray.
            $('#' + channelID + ' h2').addClass('offline');
            $('.'+username+'-notification').remove();
        } else {
            // Otherwise it should change back to default.
            $('#' + channelID + ' h2').removeClass('offline');
        }
    }
}

// Navigation Open and Closed Triggers
function navOpen(){
    // When menu is opened, clear alert number and hide alert.
    $('.menu-notification-alert').text('0');
    $('.menu-notification-alert').hide();
}
function navClosed(){
    // Remove new notification in notification list.
    $('.notification-new').remove();
}
function stickyHeader(){
    $(window).scroll(function() {
        if ($(this).scrollTop() > 1){
            $('.top').addClass("sticky");
        }else{
            $('.top').removeClass("sticky");
        }
    });
}

$(document).ready(function() {
    // Load up past username.
    var localUsername = localStorage.getItem('username');
    $('.followeradd').val(localUsername);

    // Make streams sortable.
    $(".channelwrapper").sortable({
        placeholder: 'stream-placeholder'
    });

    // Run notification
    setInterval(function(){
        jumperUpdate(0);
    }, 60000)

    // Settings Menu
    $('.menu-link').bigSlide({
        easyClose: true,
        state: 'open',
        afterOpen: function(){
            navOpen();
        },
        afterClose: function(){
            navClosed();
        }
    });

    stickyHeader();
    $('.settings').show();

});
<commit_msg>Removing live loading features until I have time to implement beam Constellation.<commit_after>
function clickEvents() {

    $('body').click(function(e) {
        // The element that was clicked.
        // Make sure to use Elem everywhere instead of "this".
        var Elem = e.target;

        // Add Online Streamer
        if($(Elem).hasClass('addonline')){
            var username = $(Elem).attr('name');
            console.log('Clicked online for '+username);
            singleChannelSubscribe(username)
        }

        // Go Button!
        // Finds username and userid then updates notifications.
        if ($(Elem).hasClass('followeraddbtn')) {
            var localUsername = $('.followeradd').val();
            if (localUsername !== '') {
                localStorage.setItem("username", localUsername);
                $.getJSON("https://beam.pro/api/v1/channels/"+localUsername+"?fields=userId", function(body) {
                    var userID = body.userId;
                    localStorage.setItem('userID', userID);
                    jumperUpdate(0);
                });
            }
        }

        // Add Other Box
        if ($(Elem).hasClass('streamerinputaddbtn')) {
            var username = $('.streamerinputadd').val();

            // Local Storage List
            if (username !== '') {
                if ($('.' + username).length === 0) {
                    singleChannelSubscribe(username);
                    var pastAdds = localStorage.getItem("streamer");
                }
                $('.streamerinputadd').val('');
            }

        }

        // Add All Button
        if ($(Elem).hasClass('addallbutton')) {
            var localUsername = localStorage.getItem('username');

            // Local Storage List
            if (localUsername !== '' && localUsername !== undefined) {
                multiChannelSubscribe(localUsername, 0);
            }
        }

        // Close button
        if ($(Elem).hasClass('closebtn')) {
            var stream = $(Elem).attr('stream');
            var username = $('#' + stream + ' h2').text();
            var savedStreams = localStorage.getItem("streamer");
            $(Elem).closest('.stream').remove();
        };

    }); // end on body clicks

}

function singleChannelSubscribe(username) {
    $.getJSON('https://beam.pro/api/v1/channels/' + username + '?fields=id,online,token,name,type,partnered,numFollowers', function(body, response) {
        // If response recieved and the div isn't already on the page.
        if ( $('.' + body.token).length === 0 ) {
            var channelID = body.id;
            var cName = body.token;
            var cTitle = body.name;
			if (body.type !== null && body.type !== undefined && body.type !== "") {
                var cGame = body.type.name;
            } else {
                var cGame = "No Game Set";
            }
            var cPartnered = body.partnered;
            var cFollowers = body.numFollowers;

            var cOnline = body.online;
            buildChannel(channelID, cName, cTitle, cGame, cPartnered, cFollowers);

            // If channel is offline when added, make title gray.
            if (cOnline === false) {
                $('#' + channelID + ' h2').addClass('offline');
            };

        } else {
            console.error('Error checking the online state for the channel', username);
        }
    });
}

function multiChannelSubscribe(localUsername, page) {
    // Get user ID.
    $.getJSON('https://beam.pro/api/v1/channels/' + localUsername + '?fields=userId', function(body, response) {
        // Get list of people user follows.
        var userID = body.userId;
        localStorage.setItem('userID', userID);
        $.getJSON('https://beam.pro/api/v1/users/' + userID + '/follows?limit=50&where=online:eq:1&page='+page, function(body, response) {
            // Loop through list.
            $.each(body, function(index, element) {
                // If channel not suspended and they aren't already on the page...
                if (element.suspended !== true && $('#' + element.id).length === 0) {
                    var channelID = element.id;
                    var cName = element.token;
                    var cTitle = element.name;
                    if (element.type !== null && element.type !== undefined && element.type !== "") {
                        var cGame = element.type.name;
                    } else {
                        var cGame = "No Game Set";
                    }
                    var cPartnered = element.partnered;
                    var cFollowers = element.numFollowers;

                    // If the channel is online then go ahead and build it.
                    buildChannel(channelID, cName, cTitle, cGame, cPartnered, cFollowers);
                }
            });

            // If the number of results equals 50, then we need to check the next page for new items.
            // Otherwise, we've cycled through all of their followers.
	        if(body.length === 50){
            	var pageCount = parseInt(page, 10) + 1;
            	multiChannelSubscribe(localUsername, pageCount);
            }
        });
    });
}

// Build Channel
// This function adds the channel to teh page using gathered info.
function buildChannel(channelID, username, title, game, partnered, followers) {

    // If the channel is partnered we're gonna make the name purple.
    if (partnered === true) {
        var partnered = "partnered";
    } else {
        var partnered = "not-partnered";
    }
    // If channel is not already on the page...
    if ($('#' + channelID).length === 0) {
        var streamEmbed = "https://beam.pro/embed/player/" + username;
        var chatEmbed = "https://beam.pro/embed/chat/" + username;

        // Build out the stream on the page.
        $('.channelwrapper').append('

<button class="closebtn" stream="' + channelID + '">X</button>

');
        $('#' + channelID + ' .chatwrap').html('');
        $('#' + channelID + ' .streamtop h2').html('' + username + '');
        $('#' + channelID + ' .cGame').text(game);
        $('#' + channelID + ' .cTitle').text(title);
        $('#' + channelID + ' .streamtop').append('<button class="watch" stream="' + username + '">Watch</button>');
        $('#' + channelID).css('display', 'block');

        // Watch Button to watch streams..
        $('#' + channelID + ' .watch').click(function() {
            $('#' + channelID).before('

<button class="closebtn" stream="' + channelID + '">X</button>

' + username + '

');

            // Set height of chat window to the same height as the video..
            var newHeight = $('.streamembed[stream="' + channelID + '"] iframe').height();
            $('.chatembed').css('height', newHeight);

            // Scroll to element to account for new chat height.
            $('html, body').delay(750).animate({
                scrollTop: $('.streamembed[stream="' + channelID + '"]').offset().top
            }, 1500);

            $('.streamembed[stream="' + channelID + '"] .closestreamembed .closebtn').click(function() {
                $('.streamembed[stream="' + channelID + '"]').remove();
                // Set related chat window back to regular height.
                $('.chatembed').css('height', '400px');
            });
        });

    }
}

// Displays an alert if a new channel comes online that isn't already shown.
function jumperUpdate(page) {

    var userID = localStorage.getItem('userID');
    if (userID !== '' && userID !== undefined && userID !== null){
        $.getJSON('https://beam.pro/api/v1/users/' + userID + '/follows?limit=50&where=online:eq:1&page='+page, function(body, response) {
            // Loop through list.
            $.each(body, function(index, element) {
                var cName = element.token;
                var partnered = element.partnered;
                if (partnered === true){
                    var partnered = "partner-notification";
                }else{
                    var partnered = "regular-notification";
                }

                // Remove default notification text
                $('.default-notify-text').remove();

                // If channel not suspended and they aren't already on the page...
                if (element.suspended !== true && $('#' + element.id).length === 0 && element.online === true) {
                    // Add line to notification box
                    if ( $('.'+cName+'-notification').length < 1){
                        $('.notification').prepend('
<button class="addonline" name="'+cName+'">Add</button>'+cName+'New
');
                    }

                    // If menu is not opened then add a notification alert.
                    if ( $('.menu-link.active').length === 0 ){
                        // Update notification number to match number of new notifications.
                        var notificationNumber = parseInt( $('.menu-notification-alert').html() );
                        var notificationList = $('.notification .notification-new').length;
                        if(notificationNumber !== notificationList){
                            $('.menu-notification-alert').text(notificationList);
                            $('.menu-notification-alert').fadeIn('fast');
                        }
                    }

                    // Show scroll bar once notification area hits 600 px tall.
                    if ( $('.notification').height() >= 400){
                        $('.notification').css('overflow-y','scroll');
                    } else{
                        $('.notification').css('overflow-y','hidden');
                    }

                    // Throw in the add all button if people are online and local username is set.
                    var localUser = localStorage.getItem('username');
                    var onlinePeople = $('.notification-name').length;
                    if ( localUser !== undefined && localUser !== '' && $('.addallbutton').is('visible') === false && onlinePeople > 2){
                        $('.addallbutton').fadeIn('fast');
                    } else {
                        $('.addallbutton').fadeOut('fast');
                    }

                } else if ( $('#' + element.id).length > 0 && element.online === false ) {

                    // Remove their line from the notification box.
                    $('.'+cName+'-notification').remove();
                }
            });

            // If the number of results equals 50, then we need to check the next page for new items.
            // Otherwise, we've cycled through all of their followers.
            if(body.length === 50){
            	var pageCount = parseInt(page, 10) + 1;
            	console.log("Finished page "+page+" moving on to page "+pageCount+".");
            	jumperUpdate(pageCount);
            }
        });
    }
}

// Navigation Open and Closed Triggers
function navOpen(){
    // When menu is opened, clear alert number and hide alert.
    $('.menu-notification-alert').text('0');
    $('.menu-notification-alert').hide();
}
function navClosed(){
    // Remove new notification in notification list.
    $('.notification-new').remove();
}
function stickyHeader(){
    $(window).scroll(function() {
        if ($(this).scrollTop() > 1){
            $('.top').addClass("sticky");
        }else{
            $('.top').removeClass("sticky");
        }
    });
}

$(document).ready(function() {
	clickEvents();

    // Load up past username.
    var localUsername = localStorage.getItem('username');
    $('.followeradd').val(localUsername);

    // Make streams sortable.
    $(".channelwrapper").sortable({
        placeholder: 'stream-placeholder'
    });

    // Run notification
    setInterval(function(){
        jumperUpdate(0);
    }, 60000)

    // Settings Menu
    $('.menu-link').bigSlide({
        easyClose: true,
        state: 'open',
        afterOpen: function(){
            navOpen();
        },
        afterClose: function(){
            navClosed();
        }
    });

    stickyHeader();
    $('.settings').show();

});
<|endoftext|>