<commit_before>const webpack = require('webpack');
var merge = require('webpack-merge');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var HTMLWebpackPlugin = require('html-webpack-plugin');
var path = require('path');

module.exports = {
    entry: [
        // "bootstrap-loader",
        "./web/static/js/app.js",
    ],
    output: {
        path: path.resolve(__dirname, "../priv/static/assets"),
        filename: "app.js",
        publicPath: "/assets/"
    },
    resolve: {
        modules: [
            path.join(__dirname, "..", "web/static/js"),
            path.join(__dirname, "..", "web/elm"),
            path.join(__dirname, "..", "node_modules")
        ],
        extensions: ['.js', '.jsx', '.elm', '.scss', '.png']
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                // loader: "style-loader!css-loader?importLoaders=1"
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loader: "babel-loader",
                query: {
                    presets: ["es2015"]
                }
            },
            {
                test: /\.elm$/,
                exclude: [/elm-stuff/, /node_modules/],
                use: [
                    {
                        loader: 'elm-assets-loader',
                        options: {
                            module: 'Assets',
                            tagger: 'AssetPath'
                        }
                    },
                    // 'elm-hot-loader',
                    'elm-webpack-loader?forceWatch=true'
                ]
            },
            {
                test: /\.scss$/,
                exclude: [
                    /elm-stuff/, /node_modules/
                ],
                loaders: ["style-loader", "css-loader", "sass-loader"]
            },
            {
                test: /\.css$/,
                exclude: [
                    /elm-stuff/, /node_modules/
                ],
                loaders: ["style-loader", "css-loader"]
            },
            {
                test: /\.(woff2?|svg|png|jpg)$/,
                loader: 'url-loader?limit=10000'
            },
            {
                test: /\.(ttf|eot)$/,
                loader: 'file-loader'
            }
        ]
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            "window.jQuery": "jquery",
            Tether: "tether",
            "window.Tether": "tether",
            Alert: "exports-loader?Alert!bootstrap/js/dist/alert",
            Button: "exports-loader?Button!bootstrap/js/dist/button",
            Carousel: "exports-loader?Carousel!bootstrap/js/dist/carousel",
            Collapse: "exports-loader?Collapse!bootstrap/js/dist/collapse",
            Dropdown: "exports-loader?Dropdown!bootstrap/js/dist/dropdown",
            Modal: "exports-loader?Modal!bootstrap/js/dist/modal",
            Popover: "exports-loader?Popover!bootstrap/js/dist/popover",
            Scrollspy: "exports-loader?Scrollspy!bootstrap/js/dist/scrollspy",
            Tab: "exports-loader?Tab!bootstrap/js/dist/tab",
            Tooltip: "exports-loader?Tooltip!bootstrap/js/dist/tooltip",
            Util: "exports-loader?Util!bootstrap/js/dist/util",
        })
        // new webpack.optimize.UglifyJsPlugin({
        //     minimize: true,
        //     compress: true
        // })
    ]
};
<commit_msg>Specify elm-make path explicitly<commit_after>const webpack = require('webpack');
var merge = require('webpack-merge');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var HTMLWebpackPlugin = require('html-webpack-plugin');
var path = require('path');

module.exports = {
    entry: [
        // "bootstrap-loader",
        "./web/static/js/app.js",
    ],
    output: {
        path: path.resolve(__dirname, "../priv/static/assets"),
        filename: "app.js",
        publicPath: "/assets/"
    },
    resolve: {
        modules: [
            path.join(__dirname, "..", "web/static/js"),
            path.join(__dirname, "..", "web/elm"),
            path.join(__dirname, "..", "node_modules")
        ],
        extensions: ['.js', '.jsx', '.elm', '.scss', '.png']
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                // loader: "style-loader!css-loader?importLoaders=1"
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loader: "babel-loader",
                query: {
                    presets: ["es2015"]
                }
            },
            {
                test: /\.elm$/,
                exclude: [/elm-stuff/, /node_modules/],
                use: [
                    {
                        loader: 'elm-assets-loader',
                        options: {
                            module: 'Assets',
                            tagger: 'AssetPath'
                        }
                    },
                    // 'elm-hot-loader',
                    'elm-webpack-loader?forceWatch=true&pathToMake=./node_modules/elm/binwrappers/elm-make'
                ]
            },
            {
                test: /\.scss$/,
                exclude: [
                    /elm-stuff/, /node_modules/
                ],
                loaders: ["style-loader", "css-loader", "sass-loader"]
            },
            {
                test: /\.css$/,
                exclude: [
                    /elm-stuff/, /node_modules/
                ],
                loaders: ["style-loader", "css-loader"]
            },
            {
                test: /\.(woff2?|svg|png|jpg)$/,
                loader: 'url-loader?limit=10000'
            },
            {
                test: /\.(ttf|eot)$/,
                loader: 'file-loader'
            }
        ]
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            "window.jQuery": "jquery",
            Tether: "tether",
            "window.Tether": "tether",
            Alert: "exports-loader?Alert!bootstrap/js/dist/alert",
            Button: "exports-loader?Button!bootstrap/js/dist/button",
            Carousel: "exports-loader?Carousel!bootstrap/js/dist/carousel",
            Collapse: "exports-loader?Collapse!bootstrap/js/dist/collapse",
            Dropdown: "exports-loader?Dropdown!bootstrap/js/dist/dropdown",
            Modal: "exports-loader?Modal!bootstrap/js/dist/modal",
            Popover: "exports-loader?Popover!bootstrap/js/dist/popover",
            Scrollspy: "exports-loader?Scrollspy!bootstrap/js/dist/scrollspy",
            Tab: "exports-loader?Tab!bootstrap/js/dist/tab",
            Tooltip: "exports-loader?Tooltip!bootstrap/js/dist/tooltip",
            Util: "exports-loader?Util!bootstrap/js/dist/util",
        })
        // new webpack.optimize.UglifyJsPlugin({
        //     minimize: true,
        //     compress: true
        // })
    ]
};
<|endoftext|>


<commit_before>// $(function () {
//
// 	Application.init ();
//
// });

var Application = function () {

	var validationRules = getValidationRules ();

	return { init: init, validationRules: validationRules };

	function init () {

		enableBackToTop ();
		enableLightbox ();
		enableCirque ();
		enableEnhancedAccordion ();

		$('.ui-tooltip').tooltip();
	    $('.ui-popover').popover();

	}

	function enableCirque () {
		if ($.fn.lightbox) {
			$('.ui-lightbox').lightbox ();
		}
	}

	function enableLightbox () {
		if ($.fn.cirque) {
			$('.ui-cirque').cirque ({  });
		}
	}

	function enableBackToTop () {
		var backToTop = $('', { id: 'back-to-top', href: '#top' });
		var icon = $('', { class: 'icon-chevron-up' });

		backToTop.appendTo ('body');
		icon.appendTo (backToTop);

	    backToTop.hide();

	    $(window).scroll(function () {
	        if ($(this).scrollTop() > 150) {
	            backToTop.fadeIn ();
	        } else {
	            backToTop.fadeOut ();
	        }
	    });

	    backToTop.click (function (e) {
	    	e.preventDefault ();

	        $('body, html').animate({
	            scrollTop: 0
	        }, 600);
	    });
	}
	function enableEnhancedAccordion () {
		$('.accordion-toggle').on('click', function (e) {
	         $(e.target).parent ().parent ().parent ().addClass('open');
	    });
	    $('.accordion-toggle').on('click', function (e) {
	        $(this).parents ('.panel').siblings ().removeClass ('open');
	    });

	}

	function getValidationRules () {
		var custom = {
	    	focusCleanup: false,

			wrapper: 'div',
			errorElement: 'span',

			highlight: function(element) {
				$(element).parents ('.form-group').removeClass ('success').addClass('error');
			},
			success: function(element) {
				$(element).parents ('.form-group').removeClass ('error').addClass('success');
				$(element).parents ('.form-group:not(:has(.clean))').find ('div:last').before ('

');
			},
			errorPlacement: function(error, element) {
				error.prependTo(element.parents ('.form-group'));
			}

	    };

	    return custom;
	}

}();
<commit_msg>Removes obsolete file<commit_after><|endoftext|>


<commit_before>(function() {
	var exports = app.namespace("app.views.board");

	/**
	 * createInstance returns a new instance of class Board.
	 * @param HTMLElement board Root element where all other markup is placed to create the board.
	 * @param integer columnMaxWidth Maximum width of columns in pixels.
	 * @param integer columnMarginLeft Margin between columns in pixels.
	 * @returns object Board
	 */
	exports.createInstance = function(board, columnMaxWidth, columnMarginLeft) {
		return new Board(board, columnMaxWidth, columnMarginLeft);
	};

	/**
	 * Class Board manages the display of images as well as some delegated click
	 * events.
	 * @param HTMLElement board Root element where all other markup is placed to create the board.
	 * @param integer columnMaxWidth Maximum width of columns in pixels.
	 * @param integer columnMarginLeft Margin between columns in pixels.
	 */
	function Board(board, columnMaxWidth, columnMarginLeft) {
		var self = this;

		var columnCount;
		var columnWidth;

		var columns = [];
		var columnHeights = [];

		var boardItems = [];
		var resizeTimeoutId;

		// For keeping track of states
		var hasLoadedAnImage = false;
		var isWaitingForRedditResponse = false;
		var lastThreadId;
		var requestToReddit;

		/**
		 * Number of running requests.
		 * @var integer
		 */
		var runningRequestsCount = 0;

		/**
		 * <style> element that is injected into the document head with
		 * generated CSS that changes the board column widths, among other
		 * things.
		 * @var HTMLElement
		 */
		var styleElement;

		/**
		 * initialize adds event listeners to the window and board to handle the
		 * window resize event, some board click events and a custom app-wide
		 * event. Do not call this method more than once.
		 * @returns void
		 */
		self.initialize = function() {
			window.addEvent("app.views.boardControls.userDidAskForImages", handleUserDidAskForImagesEvent);
			window.addEvent("resize", handleWindowResizeEvent);
			board.addEvent("click:relay(.board-item-image-anchor)", handleBoardItemImageAnchorClickEvent);
			board.addEvent("click:relay(.board-item-title-anchor)", handleBoardItemTitleAnchorClickEvent);
		};

		function handleBoardItemImageAnchorClickEvent(event) {
			event.stop();
			var anchor = event.target;

			if (anchor.get("tag") !== "a") {
				anchor = anchor.getParent(".board-item-image-anchor");
			}

			if (!anchor) {
				return;
			}

			var url = anchor.getProperty("href");

			if (!url) {
				return;
			}

			window.open(url);
		}

		function handleBoardItemTitleAnchorClickEvent(event) {
			event.stop();
			var url = event.target.getProperty("href");

			if (!url) {
				return;
			}

			window.open(url);
		}

		function handleWindowResizeEvent() {
			if (!board) {
				return;
			}

			if (resizeTimeoutId) {
				clearTimeout(resizeTimeoutId);
			}

			resizeTimeoutId = self.rebuild.delay(10);
		}

		/**
		 * rebuild calculates how many columns can be displayed, adjusts the
		 * column size, and if necessary empties the board and creates new
		 * columns that are filled with board items.
		 * @returns HTMLElement
		 */
		self.rebuild = function() {
			if (typeOf(board) !== "element") {
				return;
			}

			var availableBoardWidth = board.getWidth();
			var newColumnCount = 1;
			var newColumnWidth = columnMaxWidth;

			if (newColumnWidth > availableBoardWidth) {
				newColumnWidth = availableBoardWidth;
			} else {
				newColumnCount += Math.floor((availableBoardWidth - newColumnWidth) / (newColumnWidth + columnMarginLeft));
			}

			if (newColumnWidth !== columnWidth) {
				resizeColumns(newColumnWidth, columnMarginLeft);
				columnWidth = newColumnWidth;
			}

			// If we show the same number of columns as before, don't rebuild the board
			if (newColumnCount === columnCount) {
				return;
			}

			columnCount = newColumnCount;

			// Reset variables
			columns = [];
			columnHeights = [];

			// Remove all columns
			board.empty();

			// Create new columns
			for (var i = 0; i < columnCount; i++) {
				var column = createColumn();
				columns.push(column);
				columnHeights.push(0);
				board.grab(column);
			}

			console.log("app.views.board.rebuild: Rebuilding board with %d images.", boardItems.length);

			// Fill columns with previously fetched images, if any.
			for (var i = 0, boardItemCount = boardItems.length; i < boardItemCount; i++) {
				addBoardItemToBoard(boardItems[i]);
			}

			return board;
		};

		function createColumn() {
			var column = new Element("div", {
				"class": "board-column"
			});

			return column;
		}

		function getUrl() {
			var subredditNames = app.extractSubredditNamesFromLocationHash(location.hash);

			if (subredditNames.length === 0) {
				subredditNames = app.config.core.getNamesOfDefaultSubreddits();
			}

			url = "http://www.reddit.com/r/" + subredditNames.join("+") + ".json?limit=25";

			if (lastThreadId) {
				url += "&after=" + lastThreadId;
			}

			return url;
		}

		function handleUserDidAskForImagesEvent() {
			window.fireEvent("app.views.board.willLoadMoreImages");

			requestToReddit = new Request.JSONP({
				callbackKey: "jsonp",
				onCancel: handleRedditRequestCancelEvent,
				onComplete: handleRedditRequestCompleteEvent,
				onTimeout: handleRedditRequestTimeoutEvent,
				timeout: app.config.core.network.timeout,
				url: getUrl()
			});

			runningRequestsCount++;
			requestToReddit.send();
		}

		function handleRedditRequestCancelEvent() {
			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {runningRequestsCount: runningRequestsCount});
		}

		function handleRedditRequestCompleteEvent(response) {
			var threads = response.data.children;

			for (var i = 0, threadCount = threads.length; i < threadCount; i++) {
				var url = threads[i].data.url;
				var imgurImageHash = threads[i].data.url.match(/^https?:\/\/(?:i\.)?imgur\.com\/([a-zA-Z0-9]+)/);

				// If image is hosted on Imgur, try to load large preview version of image
				if (imgurImageHash) {
					url = "http://i.imgur.com/" + imgurImageHash[1] + "l.jpg";
					fullsizeImageUrl = "http://i.imgur.com/" + imgurImageHash[1] + ".jpg";

					var image = new Image();
					image.addEvent("error", handleImgurRequestErrorEvent.pass([threads[i].data]));
					image.addEvent("load", handleImgurRequestLoadEvent.pass([threads[i].data, image, fullsizeImageUrl]));

					runningRequestsCount++;
					image.src = url;
					continue;
				}

				// Load image
				var image = new Image();
				image.addEvent("error", handleImageErrorEvent.pass([threads[i].data]));
				image.addEvent("load", handleImageLoadEvent.pass([threads[i].data, image]));

				runningRequestsCount++;
				image.src = url;
			}

			lastThreadId = response.data.after;

			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {runningRequestsCount: runningRequestsCount});
		}

		function handleRedditRequestTimeoutEvent(event) {
			console.log("timeout", arguments);
			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {runningRequestsCount: runningRequestsCount});
			alert("Panoptikos cannot retrieve data from Reddit because Reddit is slow or you are not connected to the Internet.");
		}

		function handleImgurRequestErrorEvent(thread) {
			console.log("app.views.board.handleImgurRequestErrorEvent:", thread)
			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {runningRequestsCount: runningRequestsCount});
		}

		function handleImgurRequestLoadEvent(thread, image, fullsizeImageUrl) {
			handleImageLoadEvent(thread, image, fullsizeImageUrl);
		}

		function handleImageErrorEvent() {
			console.log("app.views.board.handleImageErrorEvent:", arguments);
			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {runningRequestsCount: runningRequestsCount});
		}

		function handleImageLoadEvent(thread, image, fullsizeImageUrl) {
			// Ignore Imgur's "Image does not exist" image
			// TODO: Find a way to make absolutely sure we are actually blocking Imgur's "Image does not exist" image and not a random image with the same dimensions.
			if (image.height === 81
					&& image.width === 161
					&& image.src.match(/^https?:\/\/i\.imgur\.com\//)) {
				console.log("app.views.board.handleImageLoadEvent: Ignoring image: " + image.src);

				runningRequestsCount--;
				window.fireEvent("app.views.board.didCompleteRequest", {
					runningRequestsCount: runningRequestsCount
				});
				return;
			}

			var boardItem = app.views.boardItem.createInstance();
			var boardItemElement = boardItem.create(thread, image, fullsizeImageUrl);

			boardItems.push(boardItemElement);
			addBoardItemToBoard(boardItemElement);

			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {
				hasLoadedAnImage: true,
				runningRequestsCount: runningRequestsCount
			});
		}

		/**
		 * addBoardItemToBoard adds the board item to the shortest column.
		 * @returns void
		 */
		function addBoardItemToBoard(boardItem) {
			var columnIndex = getIndexOfShortestColumn(columnHeights);

			if (columnIndex === null || !columns || !columns[columnIndex]) {
				return;
			}

			columns[columnIndex].grab(boardItem);
			columnHeights[columnIndex] = columns[columnIndex].getHeight();
		}

		/**
		 * getIndexOfShortestColumn returns the index of the shortest column, or
		 * null if there are no columns.
		 * @param array<integer> columnHeights Array of integers.
		 * @returns integer
		 */
		function getIndexOfShortestColumn(columnHeights) {
			var shortestColumnHeight = null;
			var shortestColumnIndex = null;

			for (var i = 0, columnHeightsCount = columnHeights.length; i < columnHeightsCount; i++) {
				if (shortestColumnHeight === null) {
					shortestColumnHeight = columnHeights[i];
					shortestColumnIndex = i;
					continue;
				}

				if (shortestColumnHeight > columnHeights[i]) {
					shortestColumnHeight = columnHeights[i];
					shortestColumnIndex = i;
				}
			}

			return shortestColumnIndex;
		}

		/**
		 * resizeColumns injects CSS with style rules for Board margin, Board
		 * width and BoardItem width into the page by creating a <style> element
		 * or modifying the created <style> element.
		 * @param integer columnWidth New width of columns in pixels.
		 * @param integer columnMarginLeft New margin between board columns in pixels.
		 * @returns void
		 */
		function resizeColumns(columnWidth, columnMarginLeft) {
			var style = ".board-column {margin-left: " + columnMarginLeft + "px; width: " + columnWidth + "px;}";
			style += ".board-item {width: " + (columnWidth - 8) + "px;}";

			if (!styleElement) {
				styleElement = new Element("style", {
					"html": style,
					"type": "text/css"
				});

				styleElement.inject($(document.head));
				return;
			}

			styleElement.set("html", style);
		}
	};
})();
<commit_msg>Removed unused variables. Added comments.<commit_after>(function() {
	var exports = app.namespace("app.views.board");

	/**
	 * createInstance returns a new instance of class Board.
	 * @param HTMLElement board Root element where all other markup is placed to create the board.
	 * @param integer columnMaxWidth Maximum width of columns in pixels.
	 * @param integer columnMarginLeft Margin between columns in pixels.
	 * @returns object Board
	 */
	exports.createInstance = function(board, columnMaxWidth, columnMarginLeft) {
		return new Board(board, columnMaxWidth, columnMarginLeft);
	};

	/**
	 * Class Board manages the display of images as well as some delegated click
	 * events.
	 * @param HTMLElement board Root element where all other markup is placed to create the board.
	 * @param integer columnMaxWidth Maximum width of columns in pixels.
	 * @param integer columnMarginLeft Margin between columns in pixels.
	 */
	function Board(board, columnMaxWidth, columnMarginLeft) {
		var self = this;

		var columnCount;
		var columnWidth;

		var columns = [];
		var columnHeights = [];

		var boardItems = [];
		var resizeTimeoutId;

		/**
		 * ID of the last Reddit thread that the HTTP response contained. Used
		 * to request threads that come afterwards.
		 * @var string
		 */
		var lastThreadId;

		/**
		 * Number of running requests.
		 * @var integer
		 */
		var runningRequestsCount = 0;

		/**
		 * <style> element that is injected into the document head with
		 * generated CSS that changes the board column widths, among other
		 * things.
		 * @var HTMLElement
		 */
		var styleElement;

		/**
		 * initialize adds event listeners to the window and board to handle the
		 * window resize event, some board click events and a custom app-wide
		 * event. Do not call this method more than once.
		 * @returns void
		 */
		self.initialize = function() {
			window.addEvent("app.views.boardControls.userDidAskForImages", handleUserDidAskForImagesEvent);
			window.addEvent("resize", handleWindowResizeEvent);
			board.addEvent("click:relay(.board-item-image-anchor)", handleBoardItemImageAnchorClickEvent);
			board.addEvent("click:relay(.board-item-title-anchor)", handleBoardItemTitleAnchorClickEvent);
		};

		function handleBoardItemImageAnchorClickEvent(event) {
			event.stop();
			var anchor = event.target;

			if (anchor.get("tag") !== "a") {
				anchor = anchor.getParent(".board-item-image-anchor");
			}

			if (!anchor) {
				return;
			}

			var url = anchor.getProperty("href");

			if (!url) {
				return;
			}

			window.open(url);
		}

		function handleBoardItemTitleAnchorClickEvent(event) {
			event.stop();
			var url = event.target.getProperty("href");

			if (!url) {
				return;
			}

			window.open(url);
		}

		function handleWindowResizeEvent() {
			if (resizeTimeoutId) {
				clearTimeout(resizeTimeoutId);
			}

			resizeTimeoutId = self.rebuild.delay(10);
		}

		/**
		 * rebuild calculates how many columns can be displayed, adjusts the
		 * column size, and if necessary empties the board and creates new
		 * columns that are filled with board items.
		 * @returns HTMLElement
		 */
		self.rebuild = function() {
			if (typeOf(board) !== "element") {
				return;
			}

			var availableBoardWidth = board.getWidth();
			var newColumnCount = 1;
			var newColumnWidth = columnMaxWidth;

			if (newColumnWidth > availableBoardWidth) {
				newColumnWidth = availableBoardWidth;
			} else {
				newColumnCount += Math.floor((availableBoardWidth - newColumnWidth) / (newColumnWidth + columnMarginLeft));
			}

			if (newColumnWidth !== columnWidth) {
				resizeColumns(newColumnWidth, columnMarginLeft);
				columnWidth = newColumnWidth;
			}

			// If we show the same number of columns as before, don't rebuild the board
			if (newColumnCount === columnCount) {
				return;
			}

			columnCount = newColumnCount;

			// Reset variables
			columns = [];
			columnHeights = [];

			// Remove all columns
			board.empty();

			// Create new columns
			for (var i = 0; i < columnCount; i++) {
				var column = createColumn();
				columns.push(column);
				columnHeights.push(0);
				board.grab(column);
			}

			console.log("app.views.board.rebuild: Rebuilding board with %d images.", boardItems.length);

			// Fill columns with previously fetched images, if any.
			for (var i = 0, boardItemCount = boardItems.length; i < boardItemCount; i++) {
				addBoardItemToBoard(boardItems[i]);
			}

			return board;
		};

		function createColumn() {
			var column = new Element("div", {
				"class": "board-column"
			});

			return column;
		}

		function getUrl() {
			var subredditNames = app.extractSubredditNamesFromLocationHash(location.hash);

			if (subredditNames.length === 0) {
				subredditNames = app.config.core.getNamesOfDefaultSubreddits();
			}

			url = "http://www.reddit.com/r/" + subredditNames.join("+") + ".json?limit=25";

			if (lastThreadId) {
				url += "&after=" + lastThreadId;
			}

			return url;
		}

		function handleUserDidAskForImagesEvent() {
			window.fireEvent("app.views.board.willLoadMoreImages");

			var requestToReddit = new Request.JSONP({
				callbackKey: "jsonp",
				onCancel: handleRedditRequestCancelEvent,
				onComplete: handleRedditRequestCompleteEvent,
				onTimeout: handleRedditRequestTimeoutEvent,
				timeout: app.config.core.network.timeout,
				url: getUrl()
			});

			runningRequestsCount++;
			requestToReddit.send();
		}

		function handleRedditRequestCancelEvent() {
			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {runningRequestsCount: runningRequestsCount});
		}

		function handleRedditRequestCompleteEvent(response) {
			var threads = response.data.children;

			for (var i = 0, threadCount = threads.length; i < threadCount; i++) {
				var url = threads[i].data.url;
				var imgurImageHash = threads[i].data.url.match(/^https?:\/\/(?:i\.)?imgur\.com\/([a-zA-Z0-9]+)/);

				// If image is hosted on Imgur, try to load large preview version of image
				if (imgurImageHash) {
					url = "http://i.imgur.com/" + imgurImageHash[1] + "l.jpg";
					fullsizeImageUrl = "http://i.imgur.com/" + imgurImageHash[1] + ".jpg";

					var image = new Image();
					image.addEvent("error", handleImgurRequestErrorEvent.pass([threads[i].data]));
					image.addEvent("load", handleImgurRequestLoadEvent.pass([threads[i].data, image, fullsizeImageUrl]));

					runningRequestsCount++;
					image.src = url;
					continue;
				}

				// Load image
				var image = new Image();
				image.addEvent("error", handleImageErrorEvent.pass([threads[i].data]));
				image.addEvent("load", handleImageLoadEvent.pass([threads[i].data, image]));

				runningRequestsCount++;
				image.src = url;
			}

			lastThreadId = response.data.after;

			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {runningRequestsCount: runningRequestsCount});
		}

		function handleRedditRequestTimeoutEvent(event) {
			console.log("timeout", arguments);
			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {runningRequestsCount: runningRequestsCount});
			alert("Panoptikos cannot retrieve data from Reddit because Reddit is slow or you are not connected to the Internet.");
		}

		function handleImgurRequestErrorEvent(thread) {
			console.log("app.views.board.handleImgurRequestErrorEvent:", thread)
			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {runningRequestsCount: runningRequestsCount});
		}

		function handleImgurRequestLoadEvent(thread, image, fullsizeImageUrl) {
			handleImageLoadEvent(thread, image, fullsizeImageUrl);
		}

		function handleImageErrorEvent() {
			console.log("app.views.board.handleImageErrorEvent:", arguments);
			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {runningRequestsCount: runningRequestsCount});
		}

		function handleImageLoadEvent(thread, image, fullsizeImageUrl) {
			// Ignore Imgur's "Image does not exist" image
			// TODO: Find a way to make absolutely sure we are actually blocking Imgur's "Image does not exist" image and not a random image with the same dimensions.
			if (image.height === 81
					&& image.width === 161
					&& image.src.match(/^https?:\/\/i\.imgur\.com\//)) {
				console.log("app.views.board.handleImageLoadEvent: Ignoring image: " + image.src);

				runningRequestsCount--;
				window.fireEvent("app.views.board.didCompleteRequest", {
					runningRequestsCount: runningRequestsCount
				});
				return;
			}

			var boardItem = app.views.boardItem.createInstance();
			var boardItemElement = boardItem.create(thread, image, fullsizeImageUrl);

			boardItems.push(boardItemElement);
			addBoardItemToBoard(boardItemElement);

			runningRequestsCount--;
			window.fireEvent("app.views.board.didCompleteRequest", {
				hasLoadedAnImage: true,
				runningRequestsCount: runningRequestsCount
			});
		}

		/**
		 * addBoardItemToBoard adds the board item to the shortest column.
		 * @returns void
		 */
		function addBoardItemToBoard(boardItem) {
			var columnIndex = getIndexOfShortestColumn(columnHeights);

			if (columnIndex === null || !columns || !columns[columnIndex]) {
				return;
			}

			columns[columnIndex].grab(boardItem);
			columnHeights[columnIndex] = columns[columnIndex].getHeight();
		}

		/**
		 * getIndexOfShortestColumn returns the index of the shortest column, or
		 * null if there are no columns.
		 * @param array<integer> columnHeights Array of integers.
		 * @returns integer
		 */
		function getIndexOfShortestColumn(columnHeights) {
			var shortestColumnHeight = null;
			var shortestColumnIndex = null;

			for (var i = 0, columnHeightsCount = columnHeights.length; i < columnHeightsCount; i++) {
				if (shortestColumnHeight === null) {
					shortestColumnHeight = columnHeights[i];
					shortestColumnIndex = i;
					continue;
				}

				if (shortestColumnHeight > columnHeights[i]) {
					shortestColumnHeight = columnHeights[i];
					shortestColumnIndex = i;
				}
			}

			return shortestColumnIndex;
		}

		/**
		 * resizeColumns injects CSS with style rules for Board margin, Board
		 * width and BoardItem width into the page by creating a <style> element
		 * or modifying the created <style> element.
		 * @param integer columnWidth New width of columns in pixels.
		 * @param integer columnMarginLeft New margin between board columns in pixels.
		 * @returns void
		 */
		function resizeColumns(columnWidth, columnMarginLeft) {
			var style = ".board-column {margin-left: " + columnMarginLeft + "px; width: " + columnWidth + "px;}";
			style += ".board-item {width: " + (columnWidth - 8) + "px;}";

			if (!styleElement) {
				styleElement = new Element("style", {
					"html": style,
					"type": "text/css"
				});

				styleElement.inject($(document.head));
				return;
			}

			styleElement.set("html", style);
		}
	};
})();
<|endoftext|>


<commit_before>// STUN, TURN Servers
var iceServer = {
	iceServers : [
	{
		urls : 'turn:turn1.nplab.de:3478',
		username: 'tiny',
		credential : 'turner'
	}, {
		urls : 'turn:turn2.nplab.de:3478',
		username: 'tiny',
		credential : 'turner'
	}, {
		urls : 'stun:stun.l.google.com:19302'
	}, {
		urls : 'stun:stun1.l.google.com:19302'
	}, {
		urls : 'stun:stun2.l.google.com:19302'
	}, {
		urls : 'stun:stun3.l.google.com:19302'
	}, {
		urls : 'stun:stun4.l.google.com:19302'
	}]
};

var PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
var SessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription;

// generate a unique-ish string for storage in firebase
function generateSignalingId() {
	return (Math.random() * 10000| 0).toString();
}

var sdpConstraints = { "audio": true, "video": true };

document.getElementById("enteruser").style.display = "none";

navigator.getMedia = navigator.getUserMedia|| navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

var localwebcam = document.getElementById("local");
var remotewebcam = document.getElementById("remote");
	navigator.getMedia(sdpConstraints, function(stream) {
	localwebcam.src = URL.createObjectURL(stream);
	localwebcam.muted = true;
	localstream = stream;
	pc.addStream(localstream);
}, errorHandler);

// Reference to Firebase APP
var dbRef = new Firebase("https://webrtcchatv.firebaseio.com/");

var bufferedAmountLimit = 1 * 1024 * 1024;

var arrayToStoreChunks = [];
var pc = new PeerConnection(iceServer);
var peerRole = "offerer";
var role = "answerer";
var signalingId;
var freshsignalingId = generateSignalingId();
var signalingIdRef = dbRef.child("roomIDs");
var dcControl = {};

// clean firebase ref
signalingIdRef.child(freshsignalingId).remove();

// wrapper to send data to FireBase
function firebaseSend(signalingId, key, data) {
	signalingIdRef.child(signalingId).child(key).set(data);
	console.log('firebaseSend - ' + key + ' - ' + data);
}

// wrapper function to receive data from FireBase - with callback function
function firebaseReceive(signalingId, type, cb) {
	signalingIdRef.child(signalingId).child(type).on("value", function(snapshot, key) {
		var data = snapshot.val();
		if (data) {
			cb(data);
			console.log('firebaseReceive - ' + type + ' - ' + data);
		}
	});
}

// generic error handler
function errorHandler(err) {
	console.error(err);
}

// handle local ice candidates
pc.onicecandidate = function(event) {
	// take the first candidate that isn't null
	if (!pc || !event || !event.candidate) {
		return;
	}

	var ip = extractIpFromString(event.candidate.candidate);

	// add local ice candidate to firebase
	signalingIdRef.child(signalingId).child(role + '-iceCandidates').push(JSON.stringify(event.candidate));

	console.log('onicecandidate - ip:' + ip);
};

pc.oniceconnectionstatechange = function(event) {
	console.log("oniceconnectionstatechange - " + pc.iceConnectionState);
	if (pc.iceConnectionState === 'disconnected') {
		chatConnectionLost();
	}
};

function chatCreateSignalingId() {
	console.log('chatCreateSignalingId');
	signalingId = freshsignalingId;
	role = "offerer";
	peerRole = "answerer";

	console.log('creating signaling id:' + signalingId);
	chatConnect();
}

function chatConnectTosignalingId() {
	console.log('chatConnectTosignalingId');
	signalingId = $("#signalingId").val();
	role = "answerer";
	peerRole = "offerer";

	console.log('connecting to peer:' + signalingId);
	chatConnect();
}

function chatConnectTosignalingIdFromUrl() {
	console.log('chatConnectTosignalingId');
	role = "answerer";
	peerRole = "offerer";

	console.log('connecting to peer:' + signalingId);
	chatConnect();
}

function chatConnect() {
	if (role === "offerer") {
	document.getElementById("ausgabe").value = signalingId;
		dcControl = pc.createDataChannel('control');
		bindEventsControl(dcControl);

		// create the offer SDP
		pc.createOffer(function(offer) {
			pc.setLocalDescription(offer);

			// send the offer SDP to FireBase
			firebaseSend(signalingId, "offer", JSON.stringify(offer));

			// wait for an answer SDP from FireBase
			firebaseReceive(signalingId, "answer", function(answer) {
				pc.setRemoteDescription(new SessionDescription(JSON.parse(answer)));
			});
		}, errorHandler, sdpConstraints);
		console.log("connect - role: offerer");

		// answerer role
	} else {
		// answerer must wait for the data channel
		pc.ondatachannel = function(event) {
			if (event.channel.label == "control") {
				dcControl = event.channel;
				bindEventsControl(event.channel);
			} else {
				alert("error: unknown channel!");
			}

			console.log('incoming datachannel');
		};

		// answerer needs to wait for an offer before generating the answer SDP
		firebaseReceive(signalingId, "offer", function(offer) {
			pc.setRemoteDescription(new SessionDescription(JSON.parse(offer)));

			// now we can generate our answer SDP
			pc.createAnswer(function(answer) {
				pc.setLocalDescription(answer);

				// send it to FireBase
				firebaseSend(signalingId, "answer", JSON.stringify(answer));
			}, errorHandler);
		});
		console.log('connect - role answerer');
	}

	// add handler for peers ice candidates
	signalingIdRef.child(signalingId).child(peerRole + '-iceCandidates').on('child_added', function(childSnapshot) {
		var childVal = childSnapshot.val();
		var peerCandidate = JSON.parse(childVal);

		var peerIceCandidate = new IceCandidate(peerCandidate);
		pc.addIceCandidate(new IceCandidate(peerCandidate));

		var peerIp = extractIpFromString(peerIceCandidate.candidate);

		console.log('peerIceCandidate: ' + peerIp);
	});
}

// find and return an IPv4 Address from a given string
function extractIpFromString(string) {
	console.log(string);
	var pattern = '(?:25[0-5]|2[0-4][0-9]|1?[0-9][0-9]{1,2}|[0-9]){1,}(?:\\.(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2}|0)){3}';
	var match = string.match(pattern);
	return match[0];
}

function bindEventsControl(channel) {
	channel.onopen = function() {
		console.log("Channel Open - Label:" + channel.label + ', ID:' + channel.id);
		document.getElementById("enteruser").style.display = "block";
	};

	channel.onclose = function(e) {
		console.log("Channel Close");
		chatConnectionLost();

	};

	window.onbeforeunload = function() {
		channel.close();
	};

	channel.onmessage = function(e) {
		msgHandleJson(e.data.toString());
    };
}

function chatConnectionLost() {
	//speedtestContinueSending = false;
	document.getElementById("chatarea").value = "CONNECTION LOST";
}

var chatnanme = "unkown";
document.getElementById("eingabe").style.display = "none";
document.getElementById("chatarea").style.display = "none";

            //  document.getElementById('welcomeDiv1').style.display = "block";

function sendmessage()
{
	var eingabemsg = document.getElementById("eingabe").value;
	document.getElementById("chatarea").value = document.getElementById("chatarea").value + eingabemsg + "\r\n";
	chatarea.scrollTop = chatarea.scrollHeight;
	nachricht = document.getElementById("chatarea").value;

	var peermsg = {
						type : 'msg',
						nachricht : nachricht
					};
	dcControl.send(JSON.stringify(peermsg));
}

function setmessage(nachricht)
{
	document.getElementById("chatarea").value = nachricht;
	chatarea.scrollTop = chatarea.scrollHeight;
}

function sendfile()
{
	var file = document.getElementById('upload').files[0];
    var reader = new window.FileReader();
	reader.readAsDataURL(file);
	reader.onload = onReadAsDataURL;
}

function setfile(file){
    arrayToStoreChunks.push(file.message); // pushing chunks in array
    console.log("added chunck");

    if (file.last) {
        SaveToDisk(arrayToStoreChunks.join(''), file.filename);
        arrayToStoreChunks = []; // resetting array
    }
}

function msgHandleJson(message) {
	var messageObject = JSON.parse(message);
	switch(messageObject.type) {

	// peer indicates finish
	case 'msg':
		setmessage(messageObject.nachricht);
	break;

	case 'file' :
		 setfile(messageObject);
	break;

	default:
		alert('Unknown messagetype: ' + messageObject.type);
		break;
	}
}

$('#name').keypress(function (e) {
	if (e.which == 13) {

	test = document.getElementById("name").value;
	  	if(test.length != 0)
	  	{
	  		username = document.getElementById("name").value;
	  		document.getElementById("enteruser").style.display = "none";
	  		document.getElementById("eingabe").style.display = "block";
			document.getElementById("chatarea").style.display = "block";
	  		document.getElementById("eingabe").focus();

	  	}
  	}
});

$('#eingabe').keypress(function (e) {
	if (e.which == 13) {
	  	var eingabemsg = document.getElementById("eingabe").value;
		document.getElementById("chatarea").value = document.getElementById("chatarea").value + username + " :" + eingabemsg + "\r\n";
		nachricht = document.getElementById("chatarea").value;

		var peermsg = {
							type : 'msg',
							nachricht : nachricht
						};

		dcControl.send(JSON.stringify(peermsg));

		document.getElementById("eingabe").value = "";
  	}
});

pc.onaddstream = function (obj) {
	remotewebcam.src = URL.createObjectURL(obj.stream);
	remotewebcam.play();
	window.AudioContext = window.AudioContext || window.webkitAudioContext;
	var audioContext = new AudioContext();
	var mediaStreamSource = audioContext.createMediaStreamSource(stream);
	mediaStreamSource.connect(audioContext.destination);
	console.log("got stream");
};

function onReadAsDataURL(event, text) {
	var filename = document.getElementById('upload').files[0].name;
    var data = {
    	type : 'file',
    	filename : filename
    }; // data object to transmit over data channel
    var chunkLength = 1000;

    if (event) text = event.target.result; // on first invocation

    if (text.length > chunkLength) {
        data.message = text.slice(0, chunkLength); // getting chunk using predefined chunk length
    } else {

        data.message = text;
        data.last = true;
    }

   	dcControl.send(JSON.stringify(data));

    var remainingDataURL = text.slice(data.message.length);
    if (remainingDataURL.length) setTimeout(function () {
        onReadAsDataURL(null, remainingDataURL);
    }, 5);
}

function SaveToDisk(fileUrl, fileName) {
    var save = document.createElement('a');
    save.href = fileUrl;
    save.target = '_blank';
    save.download = fileName || fileUrl;
    var event = document.createEvent('Event');
    event.initEvent('click', true, true);

    save.dispatchEvent(event);
    (window.URL || window.webkitURL).revokeObjectURL(save.href);
}
<commit_msg>added download button<commit_after>// STUN, TURN Servers
var iceServer = {
	iceServers : [
	{
		urls : 'turn:turn1.nplab.de:3478',
		username: 'tiny',
		credential : 'turner'
	}, {
		urls : 'turn:turn2.nplab.de:3478',
		username: 'tiny',
		credential : 'turner'
	}, {
		urls : 'stun:stun.l.google.com:19302'
	}, {
		urls : 'stun:stun1.l.google.com:19302'
	}, {
		urls : 'stun:stun2.l.google.com:19302'
	}, {
		urls : 'stun:stun3.l.google.com:19302'
	}, {
		urls : 'stun:stun4.l.google.com:19302'
	}]
};

var PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
var SessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription;

// generate a unique-ish string for storage in firebase
function generateSignalingId() {
	return (Math.random() * 10000| 0).toString();
}

var sdpConstraints = { "audio": true, "video": true };

document.getElementById("enteruser").style.display = "none";

navigator.getMedia = navigator.getUserMedia|| navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

var localwebcam = document.getElementById("local");
var remotewebcam = document.getElementById("remote");
	navigator.getMedia(sdpConstraints, function(stream) {
	localwebcam.src = URL.createObjectURL(stream);
	localwebcam.muted = true;
	localstream = stream;
	pc.addStream(localstream);
}, errorHandler);

// Reference to Firebase APP
var dbRef = new Firebase("https://webrtcchatv.firebaseio.com/");

var bufferedAmountLimit = 1 * 1024 * 1024;

var arrayToStoreChunks = [];
var pc = new PeerConnection(iceServer);
var peerRole = "offerer";
var role = "answerer";
var signalingId;
var freshsignalingId = generateSignalingId();
var signalingIdRef = dbRef.child("roomIDs");
var dcControl = {};

// clean firebase ref
signalingIdRef.child(freshsignalingId).remove();

// wrapper to send data to FireBase
function firebaseSend(signalingId, key, data) {
	signalingIdRef.child(signalingId).child(key).set(data);
	console.log('firebaseSend - ' + key + ' - ' + data);
}

// wrapper function to receive data from FireBase - with callback function
function firebaseReceive(signalingId, type, cb) {
	signalingIdRef.child(signalingId).child(type).on("value", function(snapshot, key) {
		var data = snapshot.val();
		if (data) {
			cb(data);
			console.log('firebaseReceive - ' + type + ' - ' + data);
		}
	});
}

// generic error handler
function errorHandler(err) {
	console.error(err);
}

// handle local ice candidates
pc.onicecandidate = function(event) {
	// take the first candidate that isn't null
	if (!pc || !event || !event.candidate) {
		return;
	}

	var ip = extractIpFromString(event.candidate.candidate);

	// add local ice candidate to firebase
	signalingIdRef.child(signalingId).child(role + '-iceCandidates').push(JSON.stringify(event.candidate));

	console.log('onicecandidate - ip:' + ip);
};

pc.oniceconnectionstatechange = function(event) {
	console.log("oniceconnectionstatechange - " + pc.iceConnectionState);
	if (pc.iceConnectionState === 'disconnected') {
		chatConnectionLost();
	}
};

function chatCreateSignalingId() {
	console.log('chatCreateSignalingId');
	signalingId = freshsignalingId;
	role = "offerer";
	peerRole = "answerer";

	console.log('creating signaling id:' + signalingId);
	chatConnect();
}

function chatConnectTosignalingId() {
	console.log('chatConnectTosignalingId');
	signalingId = $("#signalingId").val();
	role = "answerer";
	peerRole = "offerer";

	console.log('connecting to peer:' + signalingId);
	chatConnect();
}

function chatConnectTosignalingIdFromUrl() {
	console.log('chatConnectTosignalingId');
	role = "answerer";
	peerRole = "offerer";

	console.log('connecting to peer:' + signalingId);
	chatConnect();
}

function chatConnect() {
	if (role === "offerer") {
	document.getElementById("ausgabe").value = signalingId;
		dcControl = pc.createDataChannel('control');
		bindEventsControl(dcControl);

		// create the offer SDP
		pc.createOffer(function(offer) {
			pc.setLocalDescription(offer);

			// send the offer SDP to FireBase
			firebaseSend(signalingId, "offer", JSON.stringify(offer));

			// wait for an answer SDP from FireBase
			firebaseReceive(signalingId, "answer", function(answer) {
				pc.setRemoteDescription(new SessionDescription(JSON.parse(answer)));
			});
		}, errorHandler, sdpConstraints);
		console.log("connect - role: offerer");

		// answerer role
	} else {
		// answerer must wait for the data channel
		pc.ondatachannel = function(event) {
			if (event.channel.label == "control") {
				dcControl = event.channel;
				bindEventsControl(event.channel);
			} else {
				alert("error: unknown channel!");
			}

			console.log('incoming datachannel');
		};

		// answerer needs to wait for an offer before generating the answer SDP
		firebaseReceive(signalingId, "offer", function(offer) {
			pc.setRemoteDescription(new SessionDescription(JSON.parse(offer)));

			// now we can generate our answer SDP
			pc.createAnswer(function(answer) {
				pc.setLocalDescription(answer);

				// send it to FireBase
				firebaseSend(signalingId, "answer", JSON.stringify(answer));
			}, errorHandler);
		});
		console.log('connect - role answerer');
	}

	// add handler for peers ice candidates
	signalingIdRef.child(signalingId).child(peerRole + '-iceCandidates').on('child_added', function(childSnapshot) {
		var childVal = childSnapshot.val();
		var peerCandidate = JSON.parse(childVal);

		var peerIceCandidate = new IceCandidate(peerCandidate);
		pc.addIceCandidate(new IceCandidate(peerCandidate));

		var peerIp = extractIpFromString(peerIceCandidate.candidate);

		console.log('peerIceCandidate: ' + peerIp);
	});
}

// find and return an IPv4 Address from a given string
function extractIpFromString(string) {
	console.log(string);
	var pattern = '(?:25[0-5]|2[0-4][0-9]|1?[0-9][0-9]{1,2}|[0-9]){1,}(?:\\.(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2}|0)){3}';
	var match = string.match(pattern);
	return match[0];
}

function bindEventsControl(channel) {
	channel.onopen = function() {
		console.log("Channel Open - Label:" + channel.label + ', ID:' + channel.id);
		document.getElementById("enteruser").style.display = "block";
		document.getElementById("upload").style.display = "block";
		document.getElementById("sendfile").style.display = "block";
	};

	channel.onclose = function(e) {
		console.log("Channel Close");
		chatConnectionLost();

	};

	window.onbeforeunload = function() {
		channel.close();
	};

	channel.onmessage = function(e) {
		msgHandleJson(e.data.toString());
    };
}

function chatConnectionLost() {
	//speedtestContinueSending = false;
	document.getElementById("chatarea").value = "CONNECTION LOST";
}

var chatnanme = "unkown";
document.getElementById("eingabe").style.display = "none";
document.getElementById("chatarea").style.display = "none";
document.getElementById("download").style.display = "none";
document.getElementById("upload").style.display = "none";
document.getElementById("sendfile").style.display = "none";

function sendmessage()
{
	var eingabemsg = document.getElementById("eingabe").value;
	document.getElementById("chatarea").value = document.getElementById("chatarea").value + eingabemsg + "\r\n";
	chatarea.scrollTop = chatarea.scrollHeight;
	nachricht = document.getElementById("chatarea").value;

	var peermsg = {
						type : 'msg',
						nachricht : nachricht
					};
	dcControl.send(JSON.stringify(peermsg));
}

function setmessage(nachricht)
{
	document.getElementById("chatarea").value = nachricht;
	chatarea.scrollTop = chatarea.scrollHeight;
}

function sendfile()
{
	var file = document.getElementById('upload').files[0];
    var reader = new window.FileReader();
	reader.readAsDataURL(file);
	reader.onload = onReadAsDataURL;
}

function setfile(file){
    arrayToStoreChunks.push(file.message); // pushing chunks in array
    console.log("added chunck");

    if (file.last) {
        SaveToDisk(arrayToStoreChunks.join(''), file.filename);
        arrayToStoreChunks = []; // resetting array
    }
}

function msgHandleJson(message) {
	var messageObject = JSON.parse(message);
	switch(messageObject.type) {

	// peer indicates finish
	case 'msg':
		setmessage(messageObject.nachricht);
	break;

	case 'file' :
		 setfile(messageObject);
	break;

	default:
		alert('Unknown messagetype: ' + messageObject.type);
		break;
	}
}

$('#name').keypress(function (e) {
	if (e.which == 13) {

	test = document.getElementById("name").value;
	  	if(test.length != 0)
	  	{
	  		username = document.getElementById("name").value;
	  		document.getElementById("enteruser").style.display = "none";
	  		document.getElementById("eingabe").style.display = "block";
			document.getElementById("chatarea").style.display = "block";
	  		document.getElementById("eingabe").focus();

	  	}
  	}
});

$('#eingabe').keypress(function (e) {
	if (e.which == 13) {
	  	var eingabemsg = document.getElementById("eingabe").value;
		document.getElementById("chatarea").value = document.getElementById("chatarea").value + username + " :" + eingabemsg + "\r\n";
		nachricht = document.getElementById("chatarea").value;

		var peermsg = {
							type : 'msg',
							nachricht : nachricht
						};

		dcControl.send(JSON.stringify(peermsg));

		document.getElementById("eingabe").value = "";
  	}
});

pc.onaddstream = function (obj) {
	remotewebcam.src = URL.createObjectURL(obj.stream);
	remotewebcam.play();
	window.AudioContext = window.AudioContext || window.webkitAudioContext;
	var audioContext = new AudioContext();
	var mediaStreamSource = audioContext.createMediaStreamSource(stream);
	mediaStreamSource.connect(audioContext.destination);
	console.log("got stream");
};

function onReadAsDataURL(event, text) {
	var filename = document.getElementById('upload').files[0].name;
    var data = {
    	type : 'file',
    	filename : filename
    }; // data object to transmit over data channel
    var chunkLength = 1000;

    if (event) text = event.target.result; // on first invocation

    if (text.length > chunkLength) {
        data.message = text.slice(0, chunkLength); // getting chunk using predefined chunk length
    } else {

        data.message = text;
        data.last = true;
    }

   	dcControl.send(JSON.stringify(data));

    var remainingDataURL = text.slice(data.message.length);
    if (remainingDataURL.length) setTimeout(function () {
        onReadAsDataURL(null, remainingDataURL);
    }, 5);
}

function SaveToDisk(fileUrl, fileName) {
    var save = document.getElementById('download');
    save.href = fileUrl;
    save.target = '_blank';
    save.download = fileName || fileUrl;
    save.text = "Download: " + fileName;
    document.getElementById("download").style.display = "block";
}
<|endoftext|>


<commit_before>// Test the widget framework.
casper.notebook_test(function () {
    var index;

    this.then(function () {

        // Check if the WidgetManager class is defined.
        this.test.assert(this.evaluate(function() {
            return IPython.WidgetManager != undefined;
        }), 'WidgetManager class is defined');
    });

    index = this.append_cell(
        'from IPython.html import widgets\n' +
        'from IPython.display import display, clear_output\n' +
        'print("Success")');
    this.execute_cell_then(index);

    this.wait(500); // Wait for require.js async callbacks to load dependencies.

    this.then(function () {
        // Check if the widget manager has been instanciated.
        this.test.assert(this.evaluate(function() {
            return IPython.widget_manager != undefined;
        }), 'Notebook widget manager instanciated');
    });

    throttle_index = this.append_cell(
        'import time\n' +
        'textbox = widgets.TextBoxWidget()\n' +
        'display(textbox)\n'+
        'textbox.add_class("my-throttle-textbox")\n' +
        'def handle_change(name, old, new):\n' +
        '    print(len(new))\n' +
        '    time.sleep(0.5)\n' +
        'textbox.on_trait_change(handle_change)\n' +
        'print("Success")');
    this.execute_cell_then(throttle_index, function(index){
        this.test.assert(this.get_output_cell(index).text == 'Success\n',
            'Test throttling cell executed with correct output');

        this.test.assert(this.cell_element_exists(index,
            '.widget-area .widget-subarea'),
            'Widget subarea exists.');

        this.test.assert(this.cell_element_exists(index,
            '.my-throttle-textbox'), 'Textbox exists.');

        // Send 20 characters
        this.sendKeys('.my-throttle-textbox', '....................');
    });

    this.wait(2000); // Wait for clicks to execute in kernel

    this.then(function(){
        var resume = true;
        var i = 0;
        while (resume) {
            i++;
            var output = this.get_output_cell(throttle_index, i);
            if (output === undefined || output === null) {
                resume = false;
                i--;
            }
        }

        // Only 4 outputs should have printed, but because of timing, sometimes
        // 5 outputs will print.  All we need to do is verify num outputs <= 5
        // because that is much less than 20.
        this.test.assert(i <= 5, 'Messages throttled.');

        // We also need to verify that the last state sent was correct.
        var last_state = this.get_output_cell(throttle_index, i).text;
        this.test.assert(last_state == "20\n", "Last state sent when throttling.");
    })
});
<commit_msg>Fixed widget_manager instance test.<commit_after>// Test the widget framework.
casper.notebook_test(function () {
    var index;

    this.then(function () {

        // Check if the WidgetManager class is defined.
        this.test.assert(this.evaluate(function() {
            return IPython.WidgetManager != undefined;
        }), 'WidgetManager class is defined');
    });

    index = this.append_cell(
        'from IPython.html import widgets\n' +
        'from IPython.display import display, clear_output\n' +
        'print("Success")');
    this.execute_cell_then(index);

    this.wait(500); // Wait for require.js async callbacks to load dependencies.

    this.then(function () {
        // Check if the widget manager has been instanciated.
        this.test.assert(this.evaluate(function() {
            return IPython.notebook.kernel.widget_manager != undefined;
        }), 'Notebook widget manager instanciated');
    });

    throttle_index = this.append_cell(
        'import time\n' +
        'textbox = widgets.TextBoxWidget()\n' +
        'display(textbox)\n'+
        'textbox.add_class("my-throttle-textbox")\n' +
        'def handle_change(name, old, new):\n' +
        '    print(len(new))\n' +
        '    time.sleep(0.5)\n' +
        'textbox.on_trait_change(handle_change)\n' +
        'print("Success")');
    this.execute_cell_then(throttle_index, function(index){
        this.test.assert(this.get_output_cell(index).text == 'Success\n',
            'Test throttling cell executed with correct output');

        this.test.assert(this.cell_element_exists(index,
            '.widget-area .widget-subarea'),
            'Widget subarea exists.');

        this.test.assert(this.cell_element_exists(index,
            '.my-throttle-textbox'), 'Textbox exists.');

        // Send 20 characters
        this.sendKeys('.my-throttle-textbox', '....................');
    });

    this.wait(2000); // Wait for clicks to execute in kernel

    this.then(function(){
        var resume = true;
        var i = 0;
        while (resume) {
            i++;
            var output = this.get_output_cell(throttle_index, i);
            if (output === undefined || output === null) {
                resume = false;
                i--;
            }
        }

        // Only 4 outputs should have printed, but because of timing, sometimes
        // 5 outputs will print.  All we need to do is verify num outputs <= 5
        // because that is much less than 20.
        this.test.assert(i <= 5, 'Messages throttled.');

        // We also need to verify that the last state sent was correct.
        var last_state = this.get_output_cell(throttle_index, i).text;
        this.test.assert(last_state == "20\n", "Last state sent when throttling.");
    });
});
<|endoftext|>


<commit_before>// go.components.plumbing (endpoints)
// ==================================
// Components for endpoints attached to states in a state diagram (or 'plumbing
// view') in Go

(function(exports) {
  var utils = go.utils,
      functor = utils.functor;

  var structures = go.components.structures,
      ViewCollectionGroup = structures.ViewCollectionGroup,
      SubviewCollection = structures.SubviewCollection,
      SubviewCollectionGroup = structures.SubviewCollectionGroup;

  var views = go.components.views,
      UniqueView = views.UniqueView;

  // Base components
  // ---------------

  // View for a single endpoint on a state in a state diagram.
  //
  // Options:
  // - state: The view to which this endpoint is to be attached
  var EndpointView = UniqueView.extend({
    className: 'endpoint',

    uuid: function() { return this.model.id; },

    // Whether endpoint can be a source/target for connections
    isSource: true,
    isTarget: true,

    // The params passed to jsPlumb when configuring the element as a
    // connection source/target
    plumbSourceOptions: function() {
      return {
        anchor:['Continuous', { faces:[ 'top', 'bottom' ] } ],
        filter: this.dragFilter
      };
    },

    plumbTargetOptions: {
      anchor:['Continuous', { faces:[ 'top', 'bottom' ] } ],
      dropOptions: {
        hoverClass: 'is-hovered-target'
      }
    },

    initialize: function(options) {
      this.dragFilter = this.dragFilter.bind(this);

      // the state view that this endpoint is part of
      this.state = options.state;
      this.$state = this.state.$el;

      // the collection of endpoint views that this endpoint is part of
      this.collection = options.collection;

      if (this.isSource) {
        jsPlumb.makeSource(this.$el, _(this).result('plumbSourceOptions'));
      }

      if (this.isTarget) {
        jsPlumb.makeTarget(this.$el, _(this).result('plumbTargetOptions'));
      }
    },

    isConnected: function() {
      var connections = this.state.diagram.connections;

      return connections.findWhere({source: this})
          || connections.findWhere({target: this});
    },

    destroy: function() {
      this.trigger('ending');
      this.$el.remove();
      return this;
    },

    render: function() {
      if (this.isSource) { this.$el.addClass('endpoint-source'); }
      if (this.isTarget) { this.$el.addClass('endpoint-target');}
      this.collection.appendToView(this);
    },

    dragFilter: function() {
      return !this.isConnected();
    }
  });

  // A collection of endpoint views attached to a state view
  var EndpointViewCollection = SubviewCollection.extend({
    type: EndpointView,
    viewOptions: function() { return {state: this.view, collection: this}; },

    remove: function(viewOrId, options) {
      var view = this.resolveView(viewOrId),
          connections = this.view.diagram.connections,
          remove = function(c) { connections.remove(c, options); };

      connections.where({source: view}).forEach(remove);
      connections.where({target: view}).forEach(remove);

      return SubviewCollection.prototype.remove.call(this, view, options);
    }
  });

  // Keeps track of a state's endpoints
  var StateEndpointGroup = SubviewCollectionGroup.extend();

  // Keeps track of all the endpoints across all states in a diagram
  var DiagramEndpointGroup = ViewCollectionGroup.extend({
    constructor: function(diagram) {
      ViewCollectionGroup.prototype.constructor.call(this);
      this.diagram = diagram;

      // Add the initial states' endpoints
      var states = diagram.states;
      states.eachItem(this.addState, this);

      states.on('add', this.addState, this);
      states.on('remove', this.removeState, this);
    },

    addState: function(id, state) { this.subscribe(id, state.endpoints); },

    removeState: function(id) { this.unsubscribe(id); }
  });

  // Derived components
  // ------------------

  var PositionableEndpointView = EndpointView.extend({
    side: 'left',

    offset: function() {
      return {
        left: this.$el.outerWidth() * -0.5,
        top: this.$el.outerHeight() * -0.5
      };
    },

    initialize: function(options) {
      EndpointView.prototype.initialize.call(this, options);

      this.side = options.side || this.side;
      this.offset = functor(options.offset || this.offset);
    },

    // Override to specialise how the endpoint is positioned
    position: function() { this.offset(); },

    render: function() {
      EndpointView.prototype.render.call(this);

      this.$el
        .css('position', 'absolute')
        .css(this.position());
    }
  });

  // An endpoint view type which resides on a side of the state, and
  // can be positioned along the side based on a parameter t.
  var ParametricEndpointView = PositionableEndpointView.extend({
    initialize: function(options) {
      PositionableEndpointView.prototype.initialize.call(this, options);
      this.positioner = this.positioners[this.side];
      this.t = 0.5;
    },

    positioners: {
      left: function(t) {
        var offset = this.offset();
        return {
          left: offset.left,
          top: offset.top + (t * this.$state.outerHeight())
        };
      },

      right: function(t) {
        var offset = this.offset();
        return {
          left: offset.left + this.$state.outerWidth(),
          top: offset.top + (t * this.$state.outerHeight())
        };
      },

      top: function(t) {
        var offset = this.offset();
        return {
          left: offset.left + (t * this.$state.outerWidth()),
          top: offset.top
        };
      },

      bottom: function(t) {
        var offset = this.offset();
        return {
          left: offset.left + (t * this.$state.outerWidth()),
          top: offset.top + this.$state.outerHeight()
        };
      }
    },

    // Move the endpoint along its side based on parameter t, where
    // 0 <= t <= 1.
    reposition: function(t) {
      this.t = t;
      return this;
    },

    position: function() { return this.positioner(this.t); }
  });

  // An endpoint view type which resides on a side of the state, and
  // and follows the vertical position of one of the state's child elements.
  var FollowingEndpointView = PositionableEndpointView.extend({
    initialize: function(options) {
      PositionableEndpointView.prototype.initialize.call(this, options);
      this.target = functor(options.target || this.target);
      this.position = this.positioners[this.side];
    },

    positioners: {
      left: function() {
        var offset = this.offset();
        return {
          left: offset.left,
          top: offset.top + this.targetOffset()
        };
      },

      right: function() {
        var offset = this.offset();
        return {
          left: offset.left + this.$state.outerWidth(),
          top: offset.top + this.targetOffset()
        };
      }
    },

    targetOffset: function() {
      var $target = this.state.$(this.target());
      return $target.position().top + ($target.outerHeight() / 2);
    }
  });

  // Automatically aligns its endpoints to be evenly spaced on one side of the
  // state view.
  //
  // NOTE: Must be used with `ParametricEndpointView` types, or its derivatives
  var AligningEndpointCollection = EndpointViewCollection.extend({
    addDefaults: _.defaults(
      {render: false},
      EndpointViewCollection.prototype.addDefaults),

    type: ParametricEndpointView,

    margin: 0.005,  // margin spacing on each end of the state side

    viewOptions: function() {
      return {
        state: this.view,
        collection: this,
        side: this.side
      };
    },

    initialize: function(options) {
      EndpointViewCollection.prototype.initialize.call(this, options);

      this.side = options.side || this.side;
      this.margin = options.margin || this.margin;

      this.on('add', this.render, this);
      this.on('remove', this.render, this);
    },

    realign: function() {
      var size = this.size();
      if (!size) { return this; }

      var space = 1 - (this.margin * 2),
          incr = space / (size + 1),
          t = this.margin;

      this.each(function(e) { e.reposition(t += incr); });
      return this;
    },

    render: function() {
      this.realign();
      EndpointViewCollection.prototype.render.call(this);
    }
  });

  _.extend(exports, {
    EndpointView: EndpointView,
    EndpointViewCollection: EndpointViewCollection,
    StateEndpointGroup: StateEndpointGroup,
    DiagramEndpointGroup: DiagramEndpointGroup,

    PositionableEndpointView: PositionableEndpointView,
    ParametricEndpointView: ParametricEndpointView,
    FollowingEndpointView: FollowingEndpointView,
    AligningEndpointCollection: AligningEndpointCollection
  });
})(go.components.plumbing.endpoints = {});
<commit_msg>Update js code style<commit_after>// go.components.plumbing (endpoints)
// ==================================
// Components for endpoints attached to states in a state diagram (or 'plumbing
// view') in Go

(function(exports) {
  var utils = go.utils,
      functor = utils.functor;

  var structures = go.components.structures,
      ViewCollectionGroup = structures.ViewCollectionGroup,
      SubviewCollection = structures.SubviewCollection,
      SubviewCollectionGroup = structures.SubviewCollectionGroup;

  var views = go.components.views,
      UniqueView = views.UniqueView;

  // Base components
  // ---------------

  // View for a single endpoint on a state in a state diagram.
  //
  // Options:
  // - state: The view to which this endpoint is to be attached
  var EndpointView = UniqueView.extend({
    className: 'endpoint',

    uuid: function() { return this.model.id; },

    // Whether endpoint can be a source/target for connections
    isSource: true,
    isTarget: true,

    // The params passed to jsPlumb when configuring the element as a
    // connection source/target
    plumbSourceOptions: function() {
      return {
        anchor: ['Continuous', {faces:['top', 'bottom']}],
        filter: this.dragFilter
      };
    },

    plumbTargetOptions: {
      anchor: ['Continuous', {faces:['top', 'bottom']}],
      dropOptions: {
        hoverClass: 'is-hovered-target'
      }
    },

    initialize: function(options) {
      this.dragFilter = this.dragFilter.bind(this);

      // the state view that this endpoint is part of
      this.state = options.state;
      this.$state = this.state.$el;

      // the collection of endpoint views that this endpoint is part of
      this.collection = options.collection;

      if (this.isSource) {
        jsPlumb.makeSource(this.$el, _(this).result('plumbSourceOptions'));
      }

      if (this.isTarget) {
        jsPlumb.makeTarget(this.$el, _(this).result('plumbTargetOptions'));
      }
    },

    isConnected: function() {
      var connections = this.state.diagram.connections;

      return connections.findWhere({source: this})
          || connections.findWhere({target: this});
    },

    destroy: function() {
      this.trigger('ending');
      this.$el.remove();
      return this;
    },

    render: function() {
      if (this.isSource) { this.$el.addClass('endpoint-source'); }
      if (this.isTarget) { this.$el.addClass('endpoint-target');}
      this.collection.appendToView(this);
    },

    dragFilter: function() {
      return !this.isConnected();
    }
  });

  // A collection of endpoint views attached to a state view
  var EndpointViewCollection = SubviewCollection.extend({
    type: EndpointView,
    viewOptions: function() { return {state: this.view, collection: this}; },

    remove: function(viewOrId, options) {
      var view = this.resolveView(viewOrId),
          connections = this.view.diagram.connections,
          remove = function(c) { connections.remove(c, options); };

      connections.where({source: view}).forEach(remove);
      connections.where({target: view}).forEach(remove);

      return SubviewCollection.prototype.remove.call(this, view, options);
    }
  });

  // Keeps track of a state's endpoints
  var StateEndpointGroup = SubviewCollectionGroup.extend();

  // Keeps track of all the endpoints across all states in a diagram
  var DiagramEndpointGroup = ViewCollectionGroup.extend({
    constructor: function(diagram) {
      ViewCollectionGroup.prototype.constructor.call(this);
      this.diagram = diagram;

      // Add the initial states' endpoints
      var states = diagram.states;
      states.eachItem(this.addState, this);

      states.on('add', this.addState, this);
      states.on('remove', this.removeState, this);
    },

    addState: function(id, state) { this.subscribe(id, state.endpoints); },

    removeState: function(id) { this.unsubscribe(id); }
  });

  // Derived components
  // ------------------

  var PositionableEndpointView = EndpointView.extend({
    side: 'left',

    offset: function() {
      return {
        left: this.$el.outerWidth() * -0.5,
        top: this.$el.outerHeight() * -0.5
      };
    },

    initialize: function(options) {
      EndpointView.prototype.initialize.call(this, options);

      this.side = options.side || this.side;
      this.offset = functor(options.offset || this.offset);
    },

    // Override to specialise how the endpoint is positioned
    position: function() { this.offset(); },

    render: function() {
      EndpointView.prototype.render.call(this);

      this.$el
        .css('position', 'absolute')
        .css(this.position());
    }
  });

  // An endpoint view type which resides on a side of the state, and
  // can be positioned along the side based on a parameter t.
  var ParametricEndpointView = PositionableEndpointView.extend({
    initialize: function(options) {
      PositionableEndpointView.prototype.initialize.call(this, options);
      this.positioner = this.positioners[this.side];
      this.t = 0.5;
    },

    positioners: {
      left: function(t) {
        var offset = this.offset();
        return {
          left: offset.left,
          top: offset.top + (t * this.$state.outerHeight())
        };
      },

      right: function(t) {
        var offset = this.offset();
        return {
          left: offset.left + this.$state.outerWidth(),
          top: offset.top + (t * this.$state.outerHeight())
        };
      },

      top: function(t) {
        var offset = this.offset();
        return {
          left: offset.left + (t * this.$state.outerWidth()),
          top: offset.top
        };
      },

      bottom: function(t) {
        var offset = this.offset();
        return {
          left: offset.left + (t * this.$state.outerWidth()),
          top: offset.top + this.$state.outerHeight()
        };
      }
    },

    // Move the endpoint along its side based on parameter t, where
    // 0 <= t <= 1.
    reposition: function(t) {
      this.t = t;
      return this;
    },

    position: function() { return this.positioner(this.t); }
  });

  // An endpoint view type which resides on a side of the state, and
  // and follows the vertical position of one of the state's child elements.
  var FollowingEndpointView = PositionableEndpointView.extend({
    initialize: function(options) {
      PositionableEndpointView.prototype.initialize.call(this, options);
      this.target = functor(options.target || this.target);
      this.position = this.positioners[this.side];
    },

    positioners: {
      left: function() {
        var offset = this.offset();
        return {
          left: offset.left,
          top: offset.top + this.targetOffset()
        };
      },

      right: function() {
        var offset = this.offset();
        return {
          left: offset.left + this.$state.outerWidth(),
          top: offset.top + this.targetOffset()
        };
      }
    },

    targetOffset: function() {
      var $target = this.state.$(this.target());
      return $target.position().top + ($target.outerHeight() / 2);
    }
  });

  // Automatically aligns its endpoints to be evenly spaced on one side of the
  // state view.
  //
  // NOTE: Must be used with `ParametricEndpointView` types, or its derivatives
  var AligningEndpointCollection = EndpointViewCollection.extend({
    addDefaults: _.defaults(
      {render: false},
      EndpointViewCollection.prototype.addDefaults),

    type: ParametricEndpointView,

    margin: 0.005,  // margin spacing on each end of the state side

    viewOptions: function() {
      return {
        state: this.view,
        collection: this,
        side: this.side
      };
    },

    initialize: function(options) {
      EndpointViewCollection.prototype.initialize.call(this, options);

      this.side = options.side || this.side;
      this.margin = options.margin || this.margin;

      this.on('add', this.render, this);
      this.on('remove', this.render, this);
    },

    realign: function() {
      var size = this.size();
      if (!size) { return this; }

      var space = 1 - (this.margin * 2),
          incr = space / (size + 1),
          t = this.margin;

      this.each(function(e) { e.reposition(t += incr); });
      return this;
    },

    render: function() {
      this.realign();
      EndpointViewCollection.prototype.render.call(this);
    }
  });

  _.extend(exports, {
    EndpointView: EndpointView,
    EndpointViewCollection: EndpointViewCollection,
    StateEndpointGroup: StateEndpointGroup,
    DiagramEndpointGroup: DiagramEndpointGroup,

    PositionableEndpointView: PositionableEndpointView,
    ParametricEndpointView: ParametricEndpointView,
    FollowingEndpointView: FollowingEndpointView,
    AligningEndpointCollection: AligningEndpointCollection
  });
})(go.components.plumbing.endpoints = {});
<|endoftext|>


<commit_before>/**
* {@link enyo.Spotlight.Container}
*
* @typedef {Object} enyo.Spotlight.Container definition
*
* @class enyo.Spotlight.Container
* @public
*/
enyo.Spotlight.Container = new function() {

    //* @protected
    /************************************************************/

    var _oThis = this;

    /**
    * Creates `oSender._spotlight` object.
    *
    * @param  {Object} oSender - The object that will be initalized for Spotlight.
    * @private
    */
    var _initComponent = function(oSender) {
            var oLastFocusedChild;
            if (!_isInitialized(oSender)) {
                if (oSender.defaultSpotlightControl) {
                    if (oSender.$[oSender.defaultSpotlightControl]) {
                        oLastFocusedChild = oSender.$[oSender.defaultSpotlightControl];
                    } else if (oSender.owner.$[oSender.defaultSpotlightControl]) {
                        oLastFocusedChild = oSender.owner.$[oSender.defaultSpotlightControl];
                    } else {
                        throw 'Invalid default spotlight control specified in ' + oSender.name;
                    }
                } else {
                    oLastFocusedChild = enyo.Spotlight.getFirstChild(oSender);
                }

                if (oLastFocusedChild && oLastFocusedChild.isDescendantOf(oSender)) {
                    _oThis.setLastFocusedChild(oSender, oLastFocusedChild);
                }
            }
        },

        /**
        * Determines whether Spotlight has been initialized.
        *
        * @param  {Object} oSender - The object that will be checked for
        * Spotlight initalization.
        * @return {Boolean} `true` if Spotlight has been initialized; otherwise, `false`.
        * @private
        */
        _isInitialized = function(oSender) {
            return typeof oSender._spotlight.lastFocusedChild != 'undefined';
        },

        /**
        * Handles events bubbling from within the container.
        *
        * @param  {Object} oSender - The object that is sending the event.
        * @param  {Object} oEvent - The event to bubble.
        * @private
        */
        _handleEvent = function(oSender, oEvent) {
            oSender.cachePoint = true;
            switch (oEvent.type) {
                case 'onSpotlightFocus':
                    if (oEvent.originator !== oSender) {
                        _oThis.setLastFocusedChild(oSender, oEvent.originator);
                    }
                    break;
                case 'onSpotlightKeyDown':
                    // Inform other controls that spotlight 5-way event was generated within a container
                    oEvent.spotSentFromContainer = true;
                    break;
                case 'onSpotlightContainerEnter':
                    if(oEvent.last.isDescendantOf(oSender)) {
                        return true;
                    }
                    break;
                case 'onSpotlightContainerLeave':
                    if(oEvent.commonAncestor && oEvent.commonAncestor.isDescendantOf(oSender)) {
                        return true;
                    }
                    break;
            }
        },

        /**
        * Determines whether last spotted control was the container's child.
        *
        * @param  {Object} oSender
        * @returns {Object}
        * @private
        */
        _hadFocus = function(oSender) {
            var oLastControl = enyo.Spotlight.getLastControl();
            if (oSender._spotlight.bEnorceOutsideIn) {
                return false;
            } // Programmatically spotted containers are always treated as not having focus
            if (!enyo.Spotlight.isSpottable(oLastControl)) {
                return false;
            } // Because oLastControl might have been DHD'd
            return enyo.Spotlight.Util.isChild(oSender, oLastControl);
        };

    /**
    * Starts intercepting events to keep track of last focus for container.
    * Call this API after dynamically setting Spotlight as "container".
    *
    * @param  {Object} control
    * @public
    */
    this.initContainer = function(control) {
        if (!control._spotlight || (control._spotlight && !control._spotlight.interceptEvents)) {
            control._spotlight = control._spotlight || {};
            control._spotlight.interceptEvents = true;
            enyo.Spotlight.Util.interceptEvents(control, _handleEvent);
        }
    };

    /**
    * Handles Spotlight focus event.
    *
    * @param  {Object} oSender - The sender of the event.
    * @param  {Object} oEvent - The focus event.
    * @public
    */
    this.onSpotlightFocus = function(oSender, oEvent) {
        oSender._spotlight = oSender._spotlight || {};
        oSender._spotlight.bEnorceOutsideIn = !oEvent.dir;
    };

    /**
    * Handles Spotlight focused event.
    *
    * @param  {Object} oSender - The sender of the event.
    * @param  {Object} oEvent - The focused event.
    * @returns {Boolean}
    * @public
    */
    this.onSpotlightFocused = function(oSender, oEvent) {
        if (enyo.Spotlight.isInitialized() && enyo.Spotlight.getPointerMode()) {
            return true;
        }
        _initComponent(oSender);

        var s5WayEventType = enyo.Spotlight.getLast5WayEvent() ? enyo.Spotlight.getLast5WayEvent().type : '';

        // Focus came from inside AND this was a 5-way move
        if (_hadFocus(oSender)) {
            if (s5WayEventType) {

                // Re-dispatch 5 way event
                enyo.Spotlight.Util.dispatchEvent(
                    s5WayEventType, {
                        spotSentFromContainer: true
                    },
                    oSender
                );
            }

            // Focus came from outside or this was a programmatic spot
        } else {
            var oLastFocusedChild = this.getLastFocusedChild(oSender);
            if (oLastFocusedChild) {
                enyo.Spotlight.spot(oLastFocusedChild);
            } else {
                if (s5WayEventType) {

                    // Re-dispatch 5 way event
                    enyo.Spotlight.Util.dispatchEvent(
                        s5WayEventType, {
                            spotSentFromContainer: true
                        },
                        oSender
                    );
                    return true;
                }
            }
        }

        return true;
    };

    /**
    * Returns last focused child of the container.
    *
    * @param  {Object} oSender
    * @returns {Object} - The last focused child of the container.
    * @public
    */
    this.getLastFocusedChild = function(oSender) {
        oSender._spotlight = oSender._spotlight || {};
        if (!oSender._spotlight.lastFocusedChild || !enyo.Spotlight.isSpottable(oSender._spotlight.lastFocusedChild)) {
            oSender._spotlight.lastFocusedChild = enyo.Spotlight.getChildren(oSender)[0];
        }
        return oSender._spotlight.lastFocusedChild;
    };

    /**
    * Sets last focused child for the container.
    *
    * @param  {Object} oSender
    * @param  {Object} oChild - The child to set as the last focused child.
    * @public
    */
    this.setLastFocusedChild = function(oSender, oChild) {
        if (!enyo.Spotlight.isSpottable(oChild)) {
            oChild = enyo.Spotlight.getFirstChild(oChild);
        }
        if (oChild) {
            oSender._spotlight = oSender._spotlight || {};
            oSender._spotlight.lastFocusedChild = oChild;
        } else {
            enyo.warn('Spotlight Container attempting to set non-spottable lastFocusedChild');
        }
    };

    this.fireContainerEvents = function (blurredControl, focusedControl) {
        if(blurredControl) {
            var to = focusedControl.hasNode(),
                from = blurredControl,
                position = 0;

            // find common ancestor
            do {
                position = enyo.dom.compareDocumentPosition(to, from.hasNode());
                if(position & 8) {  // 8 == 'contains'
                    enyo.Spotlight.Util.dispatchEvent('onSpotlightContainerLeave', {
                        commonAncestor: from
                    }, blurredControl);
                    break;
                } else {
                    from = from.parent;
                }
            } while (from);
        }

        if(focusedControl) {
            enyo.Spotlight.Util.dispatchEvent('onSpotlightContainerEnter', {
                last: blurredControl,
                current: focusedControl
            }, focusedControl);
        }
    };
};

/*
Using the hack below to ensure that statically declared Spotlight containers are
initialized upon creation. Our previous pass at this used enyo.Control.extend(),
which meant it failed to work for Control subkinds whose constructors were created
immediately (vs. being deferred). Unfortunately, this caused big problems in webOS,
where the "container" app systematically disables the deferral of constructor
creation.

There is some discussion ongoing as to whether we need a nicer mechanism for
extending functionality in cases like this (see comments in BHV-15323), but in
the meantime we need to proceed with a quick fix for this issue.
*/

var originalEnyoComponentCreate = enyo.Component.create;

enyo.Component.create = function () {
    var component = originalEnyoComponentCreate.apply(enyo.Component, arguments);
    if (component.spotlight == 'container') {
        enyo.Spotlight.Container.initContainer(component);
    }
    return component;
};

<commit_msg>ENYO-508: Guard against the absence of a last (blurred) control.<commit_after>/**
* {@link enyo.Spotlight.Container}
*
* @typedef {Object} enyo.Spotlight.Container definition
*
* @class enyo.Spotlight.Container
* @public
*/
enyo.Spotlight.Container = new function() {

    //* @protected
    /************************************************************/

    var _oThis = this;

    /**
    * Creates `oSender._spotlight` object.
    *
    * @param  {Object} oSender - The object that will be initalized for Spotlight.
    * @private
    */
    var _initComponent = function(oSender) {
            var oLastFocusedChild;
            if (!_isInitialized(oSender)) {
                if (oSender.defaultSpotlightControl) {
                    if (oSender.$[oSender.defaultSpotlightControl]) {
                        oLastFocusedChild = oSender.$[oSender.defaultSpotlightControl];
                    } else if (oSender.owner.$[oSender.defaultSpotlightControl]) {
                        oLastFocusedChild = oSender.owner.$[oSender.defaultSpotlightControl];
                    } else {
                        throw 'Invalid default spotlight control specified in ' + oSender.name;
                    }
                } else {
                    oLastFocusedChild = enyo.Spotlight.getFirstChild(oSender);
                }

                if (oLastFocusedChild && oLastFocusedChild.isDescendantOf(oSender)) {
                    _oThis.setLastFocusedChild(oSender, oLastFocusedChild);
                }
            }
        },

        /**
        * Determines whether Spotlight has been initialized.
        *
        * @param  {Object} oSender - The object that will be checked for
        * Spotlight initalization.
        * @return {Boolean} `true` if Spotlight has been initialized; otherwise, `false`.
        * @private
        */
        _isInitialized = function(oSender) {
            return typeof oSender._spotlight.lastFocusedChild != 'undefined';
        },

        /**
        * Handles events bubbling from within the container.
        *
        * @param  {Object} oSender - The object that is sending the event.
        * @param  {Object} oEvent - The event to bubble.
        * @private
        */
        _handleEvent = function(oSender, oEvent) {
            oSender.cachePoint = true;
            switch (oEvent.type) {
                case 'onSpotlightFocus':
                    if (oEvent.originator !== oSender) {
                        _oThis.setLastFocusedChild(oSender, oEvent.originator);
                    }
                    break;
                case 'onSpotlightKeyDown':
                    // Inform other controls that spotlight 5-way event was generated within a container
                    oEvent.spotSentFromContainer = true;
                    break;
                case 'onSpotlightContainerEnter':
                    if(oEvent.last && oEvent.last.isDescendantOf(oSender)) {
                        return true;
                    }
                    break;
                case 'onSpotlightContainerLeave':
                    if(oEvent.commonAncestor && oEvent.commonAncestor.isDescendantOf(oSender)) {
                        return true;
                    }
                    break;
            }
        },

        /**
        * Determines whether last spotted control was the container's child.
        *
        * @param  {Object} oSender
        * @returns {Object}
        * @private
        */
        _hadFocus = function(oSender) {
            var oLastControl = enyo.Spotlight.getLastControl();
            if (oSender._spotlight.bEnorceOutsideIn) {
                return false;
            } // Programmatically spotted containers are always treated as not having focus
            if (!enyo.Spotlight.isSpottable(oLastControl)) {
                return false;
            } // Because oLastControl might have been DHD'd
            return enyo.Spotlight.Util.isChild(oSender, oLastControl);
        };

    /**
    * Starts intercepting events to keep track of last focus for container.
    * Call this API after dynamically setting Spotlight as "container".
    *
    * @param  {Object} control
    * @public
    */
    this.initContainer = function(control) {
        if (!control._spotlight || (control._spotlight && !control._spotlight.interceptEvents)) {
            control._spotlight = control._spotlight || {};
            control._spotlight.interceptEvents = true;
            enyo.Spotlight.Util.interceptEvents(control, _handleEvent);
        }
    };

    /**
    * Handles Spotlight focus event.
    *
    * @param  {Object} oSender - The sender of the event.
    * @param  {Object} oEvent - The focus event.
    * @public
    */
    this.onSpotlightFocus = function(oSender, oEvent) {
        oSender._spotlight = oSender._spotlight || {};
        oSender._spotlight.bEnorceOutsideIn = !oEvent.dir;
    };

    /**
    * Handles Spotlight focused event.
    *
    * @param  {Object} oSender - The sender of the event.
    * @param  {Object} oEvent - The focused event.
    * @returns {Boolean}
    * @public
    */
    this.onSpotlightFocused = function(oSender, oEvent) {
        if (enyo.Spotlight.isInitialized() && enyo.Spotlight.getPointerMode()) {
            return true;
        }
        _initComponent(oSender);

        var s5WayEventType = enyo.Spotlight.getLast5WayEvent() ? enyo.Spotlight.getLast5WayEvent().type : '';

        // Focus came from inside AND this was a 5-way move
        if (_hadFocus(oSender)) {
            if (s5WayEventType) {

                // Re-dispatch 5 way event
                enyo.Spotlight.Util.dispatchEvent(
                    s5WayEventType, {
                        spotSentFromContainer: true
                    },
                    oSender
                );
            }

            // Focus came from outside or this was a programmatic spot
        } else {
            var oLastFocusedChild = this.getLastFocusedChild(oSender);
            if (oLastFocusedChild) {
                enyo.Spotlight.spot(oLastFocusedChild);
            } else {
                if (s5WayEventType) {

                    // Re-dispatch 5 way event
                    enyo.Spotlight.Util.dispatchEvent(
                        s5WayEventType, {
                            spotSentFromContainer: true
                        },
                        oSender
                    );
                    return true;
                }
            }
        }

        return true;
    };

    /**
    * Returns last focused child of the container.
    *
    * @param  {Object} oSender
    * @returns {Object} - The last focused child of the container.
    * @public
    */
    this.getLastFocusedChild = function(oSender) {
        oSender._spotlight = oSender._spotlight || {};
        if (!oSender._spotlight.lastFocusedChild || !enyo.Spotlight.isSpottable(oSender._spotlight.lastFocusedChild)) {
            oSender._spotlight.lastFocusedChild = enyo.Spotlight.getChildren(oSender)[0];
        }
        return oSender._spotlight.lastFocusedChild;
    };

    /**
    * Sets last focused child for the container.
    *
    * @param  {Object} oSender
    * @param  {Object} oChild - The child to set as the last focused child.
    * @public
    */
    this.setLastFocusedChild = function(oSender, oChild) {
        if (!enyo.Spotlight.isSpottable(oChild)) {
            oChild = enyo.Spotlight.getFirstChild(oChild);
        }
        if (oChild) {
            oSender._spotlight = oSender._spotlight || {};
            oSender._spotlight.lastFocusedChild = oChild;
        } else {
            enyo.warn('Spotlight Container attempting to set non-spottable lastFocusedChild');
        }
    };

    this.fireContainerEvents = function (blurredControl, focusedControl) {
        if(blurredControl) {
            var to = focusedControl.hasNode(),
                from = blurredControl,
                position = 0;

            // find common ancestor
            do {
                position = enyo.dom.compareDocumentPosition(to, from.hasNode());
                if(position & 8) {  // 8 == 'contains'
                    enyo.Spotlight.Util.dispatchEvent('onSpotlightContainerLeave', {
                        commonAncestor: from
                    }, blurredControl);
                    break;
                } else {
                    from = from.parent;
                }
            } while (from);
        }

        if(focusedControl) {
            enyo.Spotlight.Util.dispatchEvent('onSpotlightContainerEnter', {
                last: blurredControl,
                current: focusedControl
            }, focusedControl);
        }
    };
};

/*
Using the hack below to ensure that statically declared Spotlight containers are
initialized upon creation. Our previous pass at this used enyo.Control.extend(),
which meant it failed to work for Control subkinds whose constructors were created
immediately (vs. being deferred). Unfortunately, this caused big problems in webOS,
where the "container" app systematically disables the deferral of constructor
creation.

There is some discussion ongoing as to whether we need a nicer mechanism for
extending functionality in cases like this (see comments in BHV-15323), but in
the meantime we need to proceed with a quick fix for this issue.
*/

var originalEnyoComponentCreate = enyo.Component.create;

enyo.Component.create = function () {
    var component = originalEnyoComponentCreate.apply(enyo.Component, arguments);
    if (component.spotlight == 'container') {
        enyo.Spotlight.Container.initContainer(component);
    }
    return component;
};

<|endoftext|>