<commit_before>'use strict';

function TopLevelCtrl(session, lib, $scope, $http, $location, $route, $window, $timeout, analytics) {

	$scope.isHeaderVisible = true;
	$scope.keyboard = {};
	$scope.mouse = {};

	// If children want to hide the top header.
	$scope.hideHeader = function () {
		$scope.isHeaderVisible = false;
	};

	$scope.setMindset = function (m) {
		lib.mindset.set(m);
		$scope.$broadcast('mindsetChanged', lib.mindset.get());
	};

	$scope.isMindset = function (m) {
		return lib.mindset.is(m);
	};

	$scope.setMindsetLabel = function (text) {
		$scope.mindsetLabel = text;
	};

	$scope.mousedown = function (e) {
		$scope.mouse.isButtonDown = true;
		$scope.mouse.isButtonUp = false;
		$scope.mouse.dragStartPoint = {
			x: e.pageX,
			y: e.pageY
		};
		$scope.$broadcast('mouseDown', e);
	};

	$scope.mouseup = function (e) {
		$scope.mouse.isButtonDown = false;
		$scope.mouse.isButtonUp = true;
		$scope.$broadcast('mouseUp', e);
	};

	$scope.mousemove = function (e) {
		$scope.mouse.previousPosition = $scope.mouse.position;
		$scope.mouse.position = {
			x: e.pageX,
			y: e.pageY
		};

		if ($scope.mouse.position && $scope.mouse.previousPosition) {
			var previous = $scope.mouse.previousPosition;
			var position = $scope.mouse.position;

			if (previous.y > position.y) {
				$scope.mouse.isMovingUp = true;
				$scope.mouse.isMovingDown = false;
				$scope.mouse.direction = 'up';
			}

			if (previous.y < position.y) {
				$scope.mouse.isMovingUp = false;
				$scope.mouse.isMovingDown = true;
				$scope.mouse.direction = 'down';
			}
		}

		if ($scope.mouse.isButtonDown) {
			$scope.$broadcast('mouseDrag', e);
		}
	};

	$scope.mouseleave = function (e) {
		$scope.$broadcast('mouseLeave', e);
	}

	$scope.keydown = function(e) {
		if (e.keyCode === 27) { // esc
			$scope.$broadcast('keyEscape', e);
		}

		if (e.target.type === "textarea" ||
			e.target.type === "text" ||
			e.target.type === "email" ||
			e.target.type === "password") {
			// Ignore things while we're actually typing.
			return;
		}

		if (e.keyCode === 16) {
			$scope.keyboard.isShiftDown = true;
		}
		if (e.keyCode === 224 ||
			e.keyCode === 17 ||
			e.keyCode === 91 ||
			e.keyCode === 93) {
			$scope.keyboard.isCommandDown = true;
		}

		if (e.keyCode === 49) { // "1"
			$scope.setMindset('detailed');
		}
		if (e.keyCode === 50) { // "2"
			$scope.setMindset('bump');
		}
		if (e.keyCode === 51) { // "3"
			$scope.setMindset('roadmap');
		}
		if (e.keyCode === 52) { // "4"
			$scope.setMindset('mine');
		}

		// Keys for interacting with the highlighted story
		if (e.keyCode === 13) { // enter key
			$scope.$broadcast('keyEnter', e);
		}
		if (e.keyCode === 38) { // up arrow
			$scope.$broadcast('keyUpArrow', e);
		}
		if (e.keyCode === 40) { // down arrow
			$scope.$broadcast('keyDownArrow', e);
		}

		if (e.keyCode === 88 && $scope.keyboard.isCommandDown) {
			// Cmd-X
			$scope.$broadcast('keyCut', e);
		}
		if (e.keyCode === 86 && $scope.keyboard.isCommandDown) {
			// Cmd-V
			$scope.$broadcast('keyPaste', e);
		}
		if (e.keyCode === 67 && $scope.keyboard.isCommandDown) {
			// Cmd-C
			$scope.$broadcast('keyCopy', e);
		}

		if (e.keyCode === 68) {
			// D
			$scope.$broadcast('keyDone', e);
		}

		if (e.keyCode === 87 || e.keyCode === 65) {
			// W or A
			$scope.$broadcast('keyAssigned', e);
		}

		if (e.keyCode === 79 || e.keyCode === 83) {
			// O or S
			$scope.$broadcast('keyActive', e);
		}

		if (e.keyCode === 191) {
			// ? (question mark)
			$scope.$broadcast('keyClearStatus', e);
		}

		// Scrolling: page up, page down, end, home
		if (e.keyCode === 33
			|| e.keyCode === 34
			|| e.keyCode === 35
			|| e.keyCode === 36) {
			$scope.$broadcast('viewportChanged');
		}
	};

	$scope.keyup = function (e) {
		if (e.keyCode === 16) {
			$scope.keyboard.isShiftDown = false;
		}

		if (e.keyCode === 224 ||
			e.keyCode === 17 ||
			e.keyCode === 91 ||
			e.keyCode === 93) {
			$scope.keyboard.isCommandDown = false;
		}
	};

	$scope.isSignedIn = function() {
		if (session.user && session.user.email) {
			return true;
		}

		return false;
	};

	var isInGroup = function (groupName, circleId) {
		if ($scope.isSignedIn()) {
			var memberships = session.user.memberships;
			for (var membershipKey in memberships) {
				if (circleId && memberships[membershipKey].circle !== circleId) {
					continue;
				}

				if (memberships[membershipKey].name === groupName) {
					return true;
				}
			}
		}
		return false;
	};

	$scope.isAdmin = function () {
		return isInGroup("Administrative", session.activeCircle);
	};

	$scope.hasMainframeAccess = function () {
		return isInGroup("Mainframe");
	};

	$scope.getAccountName = function () {
		if (session && session.user) {
			return session.user.name;
		}
	};

	$scope.setActiveCircle = function (circle, refreshPage) {
		session.activeCircle = circle._id;
		session.save();
		$scope.$broadcast("circleChanged", circle._id);
		if (refreshPage) {
			$window.location.href = "/#/";
		}
	};

	$scope.isCurrentPath = function (val) {
		var path = $location.path();
		if (!path) {
			return false;
		}

		return path === val || (path.indexOf(val) === 0);
	};

	var getActiveCircle = function () {
		var activeCircle = undefined;
		angular.forEach(session.circleList, function (circle) {
			if (circle.id === session.activeCircle) {
				activeCircle = circle;
			}
		});
		return activeCircle;
	};
	$scope.getActiveCircle = getActiveCircle;

	$scope.$on('setActiveCircle', function (e, circle, refreshPage, callback) {
		$scope.setActiveCircle(circle, refreshPage);
		if (!refreshPage && callback) {
			callback();
		}
	});

	$scope.getActiveCircleName = function () {
		if (!session || !session.activeCircle || !session.circleList) {
			return;
		}

		var activeCircle = getActiveCircle();
		if (activeCircle) {
			return activeCircle.name;
		}
	};

	$scope.getActiveCircleColors = function () {
		var activeCircle = getActiveCircle();
		if (activeCircle) {
			return activeCircle.colors;
		}
	};

	$scope.isActiveCircle = function (circle) {
		if (!circle || !session) {
			return false;
		}
		return session.activeCircle === circle.id;
	};

	$scope.circleList = function () {
		if ($scope.isSignedIn()) {
			return session.circleList;
		}
	};

	var resetSession = function () {
		// clear out the logged in user
		session.user = {};
		session.save();
	};

	$scope.signOut = function() {
		$http.get('/auth/signout')
		.success(function () {
			resetSession();
			$window.location.href = "/signin";
		})
		.error(function (data, status, headers, config) {
			// TODO: Is there anything to do?
			console.log(data);
		});
	};

	$scope.isEqual = function (a, b) {
		return a === b;
	};

	// HACK: This is a workaround for Safari not scrolling to
	// the top of the page when we change location / views.
	var scrollToTop = function () {
		$('html, body').animate({
			scrollTop: $("#topLevel").offset().top
		}, 100);
	};

	var collapseNavbar = function () {
		$timeout(function () {
			var navbar = $('#navigation-circles');
			if (navbar.hasClass('collapse') && navbar.hasClass('in')) {
				navbar.collapse('hide');
			}
		}, 100);
	};

	// This is very brittle. Please feel free to create
	// a better solution.
	$scope.$on('$viewContentLoaded', function() {
		scrollToTop();
		collapseNavbar();
	});

	$scope.showErrorModal = function () {
		var options = {
			// In defaults we trust!
		};
		$('#errorModal').modal(options);
	};

	$scope.reload = function () {
		$route.reload();
	};

	$scope.$on('circleblvd-error', function (e, err) {
		$scope.showErrorModal();
	});

	var enableAnalytics = function () {
		// Send a few basic things up to Google Analytics,
		// to measure site activity.
		$scope.$on('storySelected', function () {
			analytics.trackEvent('story', 'opened');
		});

		$scope.$on('storyMoved', function () {
			analytics.trackEvent('story', 'moved');
		});

		$scope.$on('storyBlockMoved', function () {
			analytics.trackEvent('story', 'block moved');
		});

		$scope.$on('storyMovedToTop', function () {
			analytics.trackEvent('story', 'movedToTop');
		});

		$scope.$on('storySaved', function () {
			analytics.trackEvent('story', 'saved');
		});

		$scope.$on('storyArchived', function () {
			analytics.trackEvent('story', 'archived');
		});
	}(); // closure

	$scope.isPagePublic = function (path) {
		if (path === '/docs'
			|| path === '/tour'
			|| path.indexOf('/tour') === 0
			|| path === '/sponsor'
			|| path === '/donate'
			|| path === '/about'
			|| path === '/privacy'
			|| path === '/invite'
			|| path.indexOf('/invite') === 0) {
			return true;
		}

		return false;
	};

	var initSocketIO = function () {
		if (typeof(io) !== 'undefined') {
			var socket = io();

			socket.on('o', function () {
				$scope.$broadcast('o');
			});

			socket.on('story-save', function (data) {
				$scope.$broadcast('ioStory', data);
			});

			socket.on('story-move-block', function (data) {
				$scope.$broadcast('ioMoveBlock', data);
			});

			socket.on('story-add', function (data) {
				$scope.$broadcast('ioStoryAdded', data);
			});

			socket.on('story-remove', function (data) {
				$scope.$broadcast('ioStoryRemoved', data);
			});

			socket.on('story-highlighted', function (data) {
				$scope.$broadcast('ioStoryHighlighted', data);
			});

			socket.on('connect', function (data) {
				socket.emit('join-circle', { circle: session.activeCircle });
			});

			socket.on('disconnect', function (data) {
				console.log("IO DISCONNECT")
				console.log(data)
				// TODO: Refresh our data when we reconnect?
			});

			$scope.$on('storyHighlighted', function (e, story) {
				socket.emit('story-highlighted', {
					circle: story.projectId,
					storyId: story.id
				});
			});

			io.connect();

		}
	}(); // closure

	var init = function() {
		if ($window.location.hash.length === 0) {
			analytics.trackPage();
		}

		$scope.$on('$routeChangeSuccess', function () {
			$scope.isHeaderVisible = true;
			var path = $location.path();
			if (path === '/initialize') {
				return;
			}
			// Don't save /signin or /invite paths
			if (path.indexOf('/signin') !== 0 && path.indexOf('/invite') !== 0) {
				session.lastLocationPath = $location.path();
			}

			analytics.trackPage();

			$http.get('/data/user')
			.success(function (user) {
				session.user = user;

				var buildCircleList = function() {
					var circleList = {};
					var memberships = user.memberships;
					for (var key in memberships) {
						if (memberships[key].circle) {
							circleList[memberships[key].circle] = {
								_id: memberships[key].circle,
								id: memberships[key].circle,
								name: memberships[key].circleName,
								colors: memberships[key].circleColors
							};
						}
					}

					session.circleList = circleList;
				}(); // closure

				session.save();
			})
			.error(function (data, status, headers, config) {
				if (status === 401) {
					// TODO: This is not cool.
					if ($scope.isPagePublic(path)) {
						// We're fine.
					}
					else {
						// We need to be signed in to access
						// whatever page we're at, but we're
						// not, so redirect to the signin page.
						$scope.signOut();
					}
				}
				else {
					// Either the server timed out or something bad.
					// Either way, we don't know who is signed in.
					// However, if we get here, there will likely
					// be a bunch of other errors in the system, so
					// let them be handled elsewhere.
				}
			});
		});

		// TODO: What's an efficient way of doing this?
		// Does CouchDB take care of that? (ish?)
		$http.get('/data/settings')
		.success(function (settings) {
			session.settings = settings;
		})
		.error(function (data, status) {
			// Do nothing.
		});
	};

	init();
}
TopLevelCtrl.$inject = ['session', 'lib', '$scope', '$http', '$location', '$route', '$window', '$timeout', 'analytics'];<commit_msg>Navbar: Sort circle list by name<commit_after>'use strict';

function TopLevelCtrl(session, lib, $scope, $http, $location, $route, $window, $timeout, analytics) {

	$scope.isHeaderVisible = true;
	$scope.keyboard = {};
	$scope.mouse = {};

	// If children want to hide the top header.
	$scope.hideHeader = function () {
		$scope.isHeaderVisible = false;
	};

	$scope.setMindset = function (m) {
		lib.mindset.set(m);
		$scope.$broadcast('mindsetChanged', lib.mindset.get());
	};

	$scope.isMindset = function (m) {
		return lib.mindset.is(m);
	};

	$scope.setMindsetLabel = function (text) {
		$scope.mindsetLabel = text;
	};

	$scope.mousedown = function (e) {
		$scope.mouse.isButtonDown = true;
		$scope.mouse.isButtonUp = false;
		$scope.mouse.dragStartPoint = {
			x: e.pageX,
			y: e.pageY
		};
		$scope.$broadcast('mouseDown', e);
	};

	$scope.mouseup = function (e) {
		$scope.mouse.isButtonDown = false;
		$scope.mouse.isButtonUp = true;
		$scope.$broadcast('mouseUp', e);
	};

	$scope.mousemove = function (e) {
		$scope.mouse.previousPosition = $scope.mouse.position;
		$scope.mouse.position = {
			x: e.pageX,
			y: e.pageY
		};

		if ($scope.mouse.position && $scope.mouse.previousPosition) {
			var previous = $scope.mouse.previousPosition;
			var position = $scope.mouse.position;

			if (previous.y > position.y) {
				$scope.mouse.isMovingUp = true;
				$scope.mouse.isMovingDown = false;
				$scope.mouse.direction = 'up';
			}

			if (previous.y < position.y) {
				$scope.mouse.isMovingUp = false;
				$scope.mouse.isMovingDown = true;
				$scope.mouse.direction = 'down';
			}
		}

		if ($scope.mouse.isButtonDown) {
			$scope.$broadcast('mouseDrag', e);
		}
	};

	$scope.mouseleave = function (e) {
		$scope.$broadcast('mouseLeave', e);
	}

	$scope.keydown = function(e) {
		if (e.keyCode === 27) { // esc
			$scope.$broadcast('keyEscape', e);
		}

		if (e.target.type === "textarea" ||
			e.target.type === "text" ||
			e.target.type === "email" ||
			e.target.type === "password") {
			// Ignore things while we're actually typing.
			return;
		}

		if (e.keyCode === 16) {
			$scope.keyboard.isShiftDown = true;
		}
		if (e.keyCode === 224 ||
			e.keyCode === 17 ||
			e.keyCode === 91 ||
			e.keyCode === 93) {
			$scope.keyboard.isCommandDown = true;
		}

		if (e.keyCode === 49) { // "1"
			$scope.setMindset('detailed');
		}
		if (e.keyCode === 50) { // "2"
			$scope.setMindset('bump');
		}
		if (e.keyCode === 51) { // "3"
			$scope.setMindset('roadmap');
		}
		if (e.keyCode === 52) { // "4"
			$scope.setMindset('mine');
		}

		// Keys for interacting with the highlighted story
		if (e.keyCode === 13) { // enter key
			$scope.$broadcast('keyEnter', e);
		}
		if (e.keyCode === 38) { // up arrow
			$scope.$broadcast('keyUpArrow', e);
		}
		if (e.keyCode === 40) { // down arrow
			$scope.$broadcast('keyDownArrow', e);
		}

		if (e.keyCode === 88 && $scope.keyboard.isCommandDown) {
			// Cmd-X
			$scope.$broadcast('keyCut', e);
		}
		if (e.keyCode === 86 && $scope.keyboard.isCommandDown) {
			// Cmd-V
			$scope.$broadcast('keyPaste', e);
		}
		if (e.keyCode === 67 && $scope.keyboard.isCommandDown) {
			// Cmd-C
			$scope.$broadcast('keyCopy', e);
		}

		if (e.keyCode === 68) {
			// D
			$scope.$broadcast('keyDone', e);
		}

		if (e.keyCode === 87 || e.keyCode === 65) {
			// W or A
			$scope.$broadcast('keyAssigned', e);
		}

		if (e.keyCode === 79 || e.keyCode === 83) {
			// O or S
			$scope.$broadcast('keyActive', e);
		}

		if (e.keyCode === 191) {
			// ? (question mark)
			$scope.$broadcast('keyClearStatus', e);
		}

		// Scrolling: page up, page down, end, home
		if (e.keyCode === 33
			|| e.keyCode === 34
			|| e.keyCode === 35
			|| e.keyCode === 36) {
			$scope.$broadcast('viewportChanged');
		}
	};

	$scope.keyup = function (e) {
		if (e.keyCode === 16) {
			$scope.keyboard.isShiftDown = false;
		}

		if (e.keyCode === 224 ||
			e.keyCode === 17 ||
			e.keyCode === 91 ||
			e.keyCode === 93) {
			$scope.keyboard.isCommandDown = false;
		}
	};

	$scope.isSignedIn = function() {
		if (session.user && session.user.email) {
			return true;
		}

		return false;
	};

	var isInGroup = function (groupName, circleId) {
		if ($scope.isSignedIn()) {
			var memberships = session.user.memberships;
			for (var membershipKey in memberships) {
				if (circleId && memberships[membershipKey].circle !== circleId) {
					continue;
				}

				if (memberships[membershipKey].name === groupName) {
					return true;
				}
			}
		}
		return false;
	};

	$scope.isAdmin = function () {
		return isInGroup("Administrative", session.activeCircle);
	};

	$scope.hasMainframeAccess = function () {
		return isInGroup("Mainframe");
	};

	$scope.getAccountName = function () {
		if (session && session.user) {
			return session.user.name;
		}
	};

	$scope.setActiveCircle = function (circle, refreshPage) {
		session.activeCircle = circle._id;
		session.save();
		$scope.$broadcast("circleChanged", circle._id);
		if (refreshPage) {
			$window.location.href = "/#/";
		}
	};

	$scope.isCurrentPath = function (val) {
		var path = $location.path();
		if (!path) {
			return false;
		}

		return path === val || (path.indexOf(val) === 0);
	};

	var getActiveCircle = function () {
		var activeCircle = undefined;
		angular.forEach(session.circleList, function (circle) {
			if (circle.id === session.activeCircle) {
				activeCircle = circle;
			}
		});
		return activeCircle;
	};
	$scope.getActiveCircle = getActiveCircle;

	$scope.$on('setActiveCircle', function (e, circle, refreshPage, callback) {
		$scope.setActiveCircle(circle, refreshPage);
		if (!refreshPage && callback) {
			callback();
		}
	});

	$scope.getActiveCircleName = function () {
		if (!session || !session.activeCircle || !session.circleList) {
			return;
		}

		var activeCircle = getActiveCircle();
		if (activeCircle) {
			return activeCircle.name;
		}
	};

	$scope.getActiveCircleColors = function () {
		var activeCircle = getActiveCircle();
		if (activeCircle) {
			return activeCircle.colors;
		}
	};

	$scope.isActiveCircle = function (circle) {
		if (!circle || !session) {
			return false;
		}
		return session.activeCircle === circle.id;
	};

	$scope.circleList = function () {
		if ($scope.isSignedIn()) {
			return session.circleList;
		}
	};

	var resetSession = function () {
		// clear out the logged in user
		session.user = {};
		session.save();
	};

	$scope.signOut = function() {
		$http.get('/auth/signout')
		.success(function () {
			resetSession();
			$window.location.href = "/signin";
		})
		.error(function (data, status, headers, config) {
			// TODO: Is there anything to do?
			console.log(data);
		});
	};

	$scope.isEqual = function (a, b) {
		return a === b;
	};

	// HACK: This is a workaround for Safari not scrolling to
	// the top of the page when we change location / views.
	var scrollToTop = function () {
		$('html, body').animate({
			scrollTop: $("#topLevel").offset().top
		}, 100);
	};

	var collapseNavbar = function () {
		$timeout(function () {
			var navbar = $('#navigation-circles');
			if (navbar.hasClass('collapse') && navbar.hasClass('in')) {
				navbar.collapse('hide');
			}
		}, 100);
	};

	// This is very brittle. Please feel free to create
	// a better solution.
	$scope.$on('$viewContentLoaded', function() {
		scrollToTop();
		collapseNavbar();
	});

	$scope.showErrorModal = function () {
		var options = {
			// In defaults we trust!
		};
		$('#errorModal').modal(options);
	};

	$scope.reload = function () {
		$route.reload();
	};

	$scope.$on('circleblvd-error', function (e, err) {
		$scope.showErrorModal();
	});

	var enableAnalytics = function () {
		// Send a few basic things up to Google Analytics,
		// to measure site activity.
		$scope.$on('storySelected', function () {
			analytics.trackEvent('story', 'opened');
		});

		$scope.$on('storyMoved', function () {
			analytics.trackEvent('story', 'moved');
		});

		$scope.$on('storyBlockMoved', function () {
			analytics.trackEvent('story', 'block moved');
		});

		$scope.$on('storyMovedToTop', function () {
			analytics.trackEvent('story', 'movedToTop');
		});

		$scope.$on('storySaved', function () {
			analytics.trackEvent('story', 'saved');
		});

		$scope.$on('storyArchived', function () {
			analytics.trackEvent('story', 'archived');
		});
	}(); // closure

	$scope.isPagePublic = function (path) {
		if (path === '/docs'
			|| path === '/tour'
			|| path.indexOf('/tour') === 0
			|| path === '/sponsor'
			|| path === '/donate'
			|| path === '/about'
			|| path === '/privacy'
			|| path === '/invite'
			|| path.indexOf('/invite') === 0) {
			return true;
		}

		return false;
	};

	var initSocketIO = function () {
		if (typeof(io) !== 'undefined') {
			var socket = io();

			socket.on('o', function () {
				$scope.$broadcast('o');
			});

			socket.on('story-save', function (data) {
				$scope.$broadcast('ioStory', data);
			});

			socket.on('story-move-block', function (data) {
				$scope.$broadcast('ioMoveBlock', data);
			});

			socket.on('story-add', function (data) {
				$scope.$broadcast('ioStoryAdded', data);
			});

			socket.on('story-remove', function (data) {
				$scope.$broadcast('ioStoryRemoved', data);
			});

			socket.on('story-highlighted', function (data) {
				$scope.$broadcast('ioStoryHighlighted', data);
			});

			socket.on('connect', function (data) {
				socket.emit('join-circle', { circle: session.activeCircle });
			});

			socket.on('disconnect', function (data) {
				console.log("IO DISCONNECT")
				console.log(data)
				// TODO: Refresh our data when we reconnect?
			});

			$scope.$on('storyHighlighted', function (e, story) {
				socket.emit('story-highlighted', {
					circle: story.projectId,
					storyId: story.id
				});
			});

			io.connect();

		}
	}(); // closure

	var init = function() {
		if ($window.location.hash.length === 0) {
			analytics.trackPage();
		}

		$scope.$on('$routeChangeSuccess', function () {
			$scope.isHeaderVisible = true;
			var path = $location.path();
			if (path === '/initialize') {
				return;
			}
			// Don't save /signin or /invite paths
			if (path.indexOf('/signin') !== 0 && path.indexOf('/invite') !== 0) {
				session.lastLocationPath = $location.path();
			}

			analytics.trackPage();

			$http.get('/data/user')
			.success(function (user) {
				session.user = user;

				var buildCircleList = function() {
					var circleList = {};
					var memberships = user.memberships;
					for (var key in memberships) {
						if (memberships[key].circle) {
							circleList[memberships[key].circle] = {
								_id: memberships[key].circle,
								id: memberships[key].circle,
								name: memberships[key].circleName,
								colors: memberships[key].circleColors
							};
						}
					}

					// Sort the circle list by name
					var circleArray = [];
					for (var key in circleList) {
						circleArray.push(circleList[key]);
					}
					circleArray.sort(function (a, b) {
						if (a.name < b.name) {
							return -1;
						}
						if (a.name > b.name) {
							return 1;
						}
						return 0;
					});

					session.circleList = circleArray;
				}(); // closure

				session.save();
			})
			.error(function (data, status, headers, config) {
				if (status === 401) {
					// TODO: This is not cool.
					if ($scope.isPagePublic(path)) {
						// We're fine.
					}
					else {
						// We need to be signed in to access
						// whatever page we're at, but we're
						// not, so redirect to the signin page.
						$scope.signOut();
					}
				}
				else {
					// Either the server timed out or something bad.
					// Either way, we don't know who is signed in.
					// However, if we get here, there will likely
					// be a bunch of other errors in the system, so
					// let them be handled elsewhere.
				}
			});
		});

		// TODO: What's an efficient way of doing this?
		// Does CouchDB take care of that? (ish?)
		$http.get('/data/settings')
		.success(function (settings) {
			session.settings = settings;
		})
		.error(function (data, status) {
			// Do nothing.
		});
	};

	init();
}
TopLevelCtrl.$inject = ['session', 'lib', '$scope', '$http', '$location', '$route', '$window', '$timeout', 'analytics'];<|endoftext|>


<commit_before>/*
 * This file needs to be renamed as app.js when we want to run performance tests
 * in order this to get loaded by the jxcore ready event.
 * This effectively acts as main entry point to the performance test app
*/

"use strict";

var testUtils = require("./lib/testUtils");
var fs = require('fs');
var serverAddress = require('server-address.js');

testUtils.toggleRadios(true);

var TestFrameworkClient = require('./perf_tests/PerfTestFrameworkClient');

/*----------------------------------------------------------------------------------
 code for connecting to the coordinator server
 -----------------------------------------------------------------------------------*/

function getDeviceCharacteristics(cb) {

  if (typeof jxcore == 'undefined') {
    cb("PERF_TEST-" + Math.random(), null);
  }
  else if (jxcore.utils.OSInfo().isAndroid) {

    Mobile('GetBluetoothAddress').callNative(function (err, address) {

      if (err) {
        console.log("GetBluetoothAddress returned error: " + err + ", address : " + address);
        return;
      }

      var bluetoothAddress = address;
      console.log("Got Device Bluetooth address: " + bluetoothAddress);
      Mobile('GetDeviceName').callNative(function (name) {

        var deviceName = name + "_PT" + Math.round((Math.random() * (10000)));
        testUtils.setMyName(deviceName);

        // once we have had the BT off and we just turned it on,
        // we need to wait untill the BLE support is reported rigth way
        // seen with LG G4, Not seen with Motorola Nexus 6
        setTimeout(function () {
          Mobile('IsBLESupported').callNative(function (err) {
            if (err) {
              console.log("BLE advertisement not supported: " + err );
              return;
            }
            console.log("BLE supported!!");
          });
        }, 5000);

        cb(deviceName, bluetoothAddress);
      });
    });

  } else {
    var bluetoothAddress = "C0:FF:FF:EE:42:00";
    Mobile('GetDeviceName').callNative(function (name) {
      var deviceName = name + "_PT" + Math.round((Math.random() * (10000)));
      testUtils.setMyName(deviceName);
      cb(deviceName, bluetoothAddress);
    });
  }
}

/*----------------------------------------------------------------------------------
 code for handling test communications
 -----------------------------------------------------------------------------------*/

var testFramework;
getDeviceCharacteristics(function(deviceName, bluetoothAddresss) {
  // The test framework client will coordinate everything from here..
  testFramework = new TestFrameworkClient(deviceName, bluetoothAddress);
});

// Log that the app.js file was loaded.
console.log('Perf Test app loaded loaded');
<commit_msg>Tidy up requires<commit_after>/*
 * This file needs to be renamed as app.js when we want to run performance tests
 * in order this to get loaded by the jxcore ready event.
 * This effectively acts as main entry point to the performance test app
*/

"use strict";

var testUtils = require("./lib/testUtils");
var TestFrameworkClient = require('./perf_tests/PerfTestFrameworkClient');

testUtils.toggleRadios(true);

/*----------------------------------------------------------------------------------
 code for connecting to the coordinator server
 -----------------------------------------------------------------------------------*/

function getDeviceCharacteristics(cb) {

  if (typeof jxcore == 'undefined') {
    cb("PERF_TEST-" + Math.random(), null);
  }
  else if (jxcore.utils.OSInfo().isAndroid) {

    Mobile('GetBluetoothAddress').callNative(function (err, address) {

      if (err) {
        console.log("GetBluetoothAddress returned error: " + err + ", address : " + address);
        return;
      }

      var bluetoothAddress = address;
      console.log("Got Device Bluetooth address: " + bluetoothAddress);
      Mobile('GetDeviceName').callNative(function (name) {

        var deviceName = name + "_PT" + Math.round((Math.random() * (10000)));
        testUtils.setMyName(deviceName);

        // once we have had the BT off and we just turned it on,
        // we need to wait untill the BLE support is reported rigth way
        // seen with LG G4, Not seen with Motorola Nexus 6
        setTimeout(function () {
          Mobile('IsBLESupported').callNative(function (err) {
            if (err) {
              console.log("BLE advertisement not supported: " + err );
              return;
            }
            console.log("BLE supported!!");
          });
        }, 5000);

        cb(deviceName, bluetoothAddress);
      });
    });

  } else {
    var bluetoothAddress = "C0:FF:FF:EE:42:00";
    Mobile('GetDeviceName').callNative(function (name) {
      var deviceName = name + "_PT" + Math.round((Math.random() * (10000)));
      testUtils.setMyName(deviceName);
      cb(deviceName, bluetoothAddress);
    });
  }
}

/*----------------------------------------------------------------------------------
 code for handling test communications
 -----------------------------------------------------------------------------------*/

var testFramework;
getDeviceCharacteristics(function(deviceName, bluetoothAddresss) {
  // The test framework client will coordinate everything from here..
  testFramework = new TestFrameworkClient(deviceName, bluetoothAddress);
});

// Log that the app.js file was loaded.
console.log('Perf Test app loaded loaded');
<|endoftext|>


<commit_before>/**
 * React component to display a line graph (e.g. time series)
 */

import { List as list } from 'immutable';
import React, { useState, useRef, useMemo } from 'react';
import PropTypes from 'prop-types';
import { genPixelCompute } from './helpers';
import LineGraphDumb from './LineGraphDumb';

import { useZoom } from './hooks/zoom';
import { useHover } from './hooks/hover';

const defaultPadding = [0, 0, 0, 0];

export default function LineGraph(props) {
    const {
        lines,
        width,
        height,
        padding: customPadding,
        minX,
        maxX,
        minY,
        maxY
    } = props;

    const padding = customPadding || defaultPadding;

    const graph = useRef(null);

    const [calc, setCalc] = useState(genPixelCompute({
        padding,
        width,
        height,
        lines,
        minY,
        maxY,
        minX,
        maxX
    }));

    const range = useMemo(() => maxX - minX, [minX, maxX]);

    const [hlPoint, onMouseMove, onMouseLeave] = useHover({ props });

    const [zoom, onWheel] = useZoom({ props, padding, range, graph, hlPoint, setCalc });

    if (!calc) {
        return null;
    }

    const outerProperties = useMemo(() => ({
        onMouseMove,
        onMouseLeave,
        ...(props.outerProperties || {})
    }), [onMouseMove, onMouseLeave, props.outerProperties]);

    const svgProperties = useMemo(() => ({
        onWheel,
        ...(props.svgProperties || {})
    }), [onWheel, props.svgProperties]);

    return (
        <LineGraphDumb
            svgRef={graph}
            {...props}
            {...zoom}
            calc={calc}
            outerProperties={outerProperties}
            svgProperties={svgProperties}
            hlPoint={hlPoint}
        />
    );
}

LineGraph.propTypes = {
    lines: PropTypes.instanceOf(list).isRequired,
    width: PropTypes.number.isRequired,
    height: PropTypes.number.isRequired,
    padding: PropTypes.array,
    minX: PropTypes.number,
    maxX: PropTypes.number,
    minY: PropTypes.number,
    maxY: PropTypes.number,
    isMobile: PropTypes.bool,
    hoverEffect: PropTypes.object,
    outerProperties: PropTypes.object,
    svgProperties: PropTypes.object,
    zoomEffect: PropTypes.func
};

<commit_msg>Rerender graph SVGs when dimensions and other attributes change<commit_after>/**
 * React component to display a line graph (e.g. time series)
 */

import { List as list } from 'immutable';
import React, { useState, useEffect, useRef, useMemo } from 'react';
import PropTypes from 'prop-types';
import { genPixelCompute } from './helpers';
import LineGraphDumb from './LineGraphDumb';

import { useZoom } from './hooks/zoom';
import { useHover } from './hooks/hover';

const defaultPadding = [0, 0, 0, 0];

export default function LineGraph(props) {
    const {
        lines,
        width,
        height,
        padding: customPadding,
        minX,
        maxX,
        minY,
        maxY
    } = props;

    const padding = customPadding || defaultPadding;

    const graph = useRef(null);

    const [calc, setCalc] = useState(null);

    useEffect(() => {
        setCalc(genPixelCompute({
            padding,
            width,
            height,
            lines,
            minY,
            maxY,
            minX,
            maxX
        }));
    }, [padding, width, height, lines, minY, maxY, minX, maxX]);

    const range = useMemo(() => maxX - minX, [minX, maxX]);

    const [hlPoint, onMouseMove, onMouseLeave] = useHover({ props });

    const [zoom, onWheel] = useZoom({ props, padding, range, graph, hlPoint, setCalc });

    const outerProperties = useMemo(() => ({
        onMouseMove,
        onMouseLeave,
        ...(props.outerProperties || {})
    }), [onMouseMove, onMouseLeave, props.outerProperties]);

    const svgProperties = useMemo(() => ({
        onWheel,
        ...(props.svgProperties || {})
    }), [onWheel, props.svgProperties]);

    if (!calc) {
        return null;
    }

    return (
        <LineGraphDumb
            svgRef={graph}
            {...props}
            {...zoom}
            calc={calc}
            outerProperties={outerProperties}
            svgProperties={svgProperties}
            hlPoint={hlPoint}
        />
    );
}

LineGraph.propTypes = {
    lines: PropTypes.instanceOf(list).isRequired,
    width: PropTypes.number.isRequired,
    height: PropTypes.number.isRequired,
    padding: PropTypes.array,
    minX: PropTypes.number,
    maxX: PropTypes.number,
    minY: PropTypes.number,
    maxY: PropTypes.number,
    isMobile: PropTypes.bool,
    hoverEffect: PropTypes.object,
    outerProperties: PropTypes.object,
    svgProperties: PropTypes.object,
    zoomEffect: PropTypes.func
};

<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { formatTime } from 'mastodon/features/video';
import Icon from 'mastodon/components/icon';
import classNames from 'classnames';
import { throttle } from 'lodash';
import { getPointerPosition, fileNameFromURL } from 'mastodon/features/video';
import { debounce } from 'lodash';
import Visualizer from './visualizer';

const messages = defineMessages({
  play: { id: 'video.play', defaultMessage: 'Play' },
  pause: { id: 'video.pause', defaultMessage: 'Pause' },
  mute: { id: 'video.mute', defaultMessage: 'Mute sound' },
  unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
  download: { id: 'video.download', defaultMessage: 'Download file' },
});

const TICK_SIZE = 10;
const PADDING   = 180;

export default @injectIntl
class Audio extends React.PureComponent {

  static propTypes = {
    src: PropTypes.string.isRequired,
    alt: PropTypes.string,
    poster: PropTypes.string,
    duration: PropTypes.number,
    width: PropTypes.number,
    height: PropTypes.number,
    editable: PropTypes.bool,
    fullscreen: PropTypes.bool,
    intl: PropTypes.object.isRequired,
    cacheWidth: PropTypes.func,
    backgroundColor: PropTypes.string,
    foregroundColor: PropTypes.string,
    accentColor: PropTypes.string,
    autoPlay: PropTypes.bool,
  };

  state = {
    width: this.props.width,
    currentTime: 0,
    buffer: 0,
    duration: null,
    paused: true,
    muted: false,
    volume: 0.5,
    dragging: false,
  };

  constructor (props) {
    super(props);
    this.visualizer = new Visualizer(TICK_SIZE);
  }

  setPlayerRef = c => {
    this.player = c;

    if (this.player) {
      this._setDimensions();
    }
  }

  _setDimensions () {
    const width  = this.player.offsetWidth;
    const height = this.props.fullscreen ? this.player.offsetHeight : (width / (16/9));

    if (this.props.cacheWidth) {
      this.props.cacheWidth(width);
    }

    this.setState({ width, height });
  }

  setSeekRef = c => {
    this.seek = c;
  }

  setVolumeRef = c => {
    this.volume = c;
  }

  setAudioRef = c => {
    this.audio = c;

    if (this.audio) {
      this.setState({ volume: this.audio.volume, muted: this.audio.muted });
    }
  }

  setCanvasRef = c => {
    this.canvas = c;

    this.visualizer.setCanvas(c);
  }

  componentDidMount () {
    window.addEventListener('scroll', this.handleScroll);
    window.addEventListener('resize', this.handleResize, { passive: true });
  }

  componentDidUpdate (prevProps, prevState) {
    if (prevProps.src !== this.props.src || this.state.width !== prevState.width || this.state.height !== prevState.height || prevProps.accentColor !== this.props.accentColor) {
      this._clear();
      this._draw();
    }
  }

  componentWillUnmount () {
    window.removeEventListener('scroll', this.handleScroll);
    window.removeEventListener('resize', this.handleResize);
  }

  togglePlay = () => {
    if (this.state.paused) {
      this.setState({ paused: false }, () => this.audio.play());
    } else {
      this.setState({ paused: true }, () => this.audio.pause());
    }
  }

  handleResize = debounce(() => {
    if (this.player) {
      this._setDimensions();
    }
  }, 250, {
    trailing: true,
  });

  handlePlay = () => {
    this.setState({ paused: false });

    if (this.canvas && !this.audioContext) {
      this._initAudioContext();
    }

    if (this.audioContext && this.audioContext.state === 'suspended') {
      this.audioContext.resume();
    }

    this._renderCanvas();
  }

  handlePause = () => {
    this.setState({ paused: true });

    if (this.audioContext) {
      this.audioContext.suspend();
    }
  }

  handleProgress = () => {
    const lastTimeRange = this.audio.buffered.length - 1;

    if (lastTimeRange > -1) {
      this.setState({ buffer: Math.ceil(this.audio.buffered.end(lastTimeRange) / this.audio.duration * 100) });
    }
  }

  toggleMute = () => {
    const muted = !this.state.muted;

    this.setState({ muted }, () => {
      this.audio.muted = muted;
    });
  }

  handleVolumeMouseDown = e => {
    document.addEventListener('mousemove', this.handleMouseVolSlide, true);
    document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
    document.addEventListener('touchmove', this.handleMouseVolSlide, true);
    document.addEventListener('touchend', this.handleVolumeMouseUp, true);

    this.handleMouseVolSlide(e);

    e.preventDefault();
    e.stopPropagation();
  }

  handleVolumeMouseUp = () => {
    document.removeEventListener('mousemove', this.handleMouseVolSlide, true);
    document.removeEventListener('mouseup', this.handleVolumeMouseUp, true);
    document.removeEventListener('touchmove', this.handleMouseVolSlide, true);
    document.removeEventListener('touchend', this.handleVolumeMouseUp, true);
  }

  handleMouseDown = e => {
    document.addEventListener('mousemove', this.handleMouseMove, true);
    document.addEventListener('mouseup', this.handleMouseUp, true);
    document.addEventListener('touchmove', this.handleMouseMove, true);
    document.addEventListener('touchend', this.handleMouseUp, true);

    this.setState({ dragging: true });
    this.audio.pause();
    this.handleMouseMove(e);

    e.preventDefault();
    e.stopPropagation();
  }

  handleMouseUp = () => {
    document.removeEventListener('mousemove', this.handleMouseMove, true);
    document.removeEventListener('mouseup', this.handleMouseUp, true);
    document.removeEventListener('touchmove', this.handleMouseMove, true);
    document.removeEventListener('touchend', this.handleMouseUp, true);

    this.setState({ dragging: false });
    this.audio.play();
  }

  handleMouseMove = throttle(e => {
    const { x } = getPointerPosition(this.seek, e);
    const currentTime = this.audio.duration * x;

    if (!isNaN(currentTime)) {
      this.setState({ currentTime }, () => {
        this.audio.currentTime = currentTime;
      });
    }
  }, 15);

  handleTimeUpdate = () => {
    this.setState({
      currentTime: this.audio.currentTime,
      duration: Math.floor(this.audio.duration),
    });
  }

  handleMouseVolSlide = throttle(e => {
    const { x } = getPointerPosition(this.volume, e);

    if(!isNaN(x)) {
      this.setState({ volume: x }, () => {
        this.audio.volume = x;
      });
    }
  }, 15);

  handleScroll = throttle(() => {
    if (!this.canvas || !this.audio) {
      return;
    }

    const { top, height } = this.canvas.getBoundingClientRect();
    const inView = (top <= (window.innerHeight || document.documentElement.clientHeight)) && (top + height >= 0);

    if (!this.state.paused && !inView) {
      this.setState({ paused: true }, () => this.audio.pause());
    }
  }, 150, { trailing: true });

  handleMouseEnter = () => {
    this.setState({ hovered: true });
  }

  handleMouseLeave = () => {
    this.setState({ hovered: false });
  }

  handleLoadedData = () => {
    const { autoPlay } = this.props;

    if (autoPlay) {
      this.audio.play();
    }
  }

  _initAudioContext () {
    const AudioContext = window.AudioContext || window.webkitAudioContext;
    const context      = new AudioContext();
    const source       = context.createMediaElementSource(this.audio);

    this.visualizer.setAudioContext(context, source);
    source.connect(context.destination);

    this.audioContext = context;
  }

  handleDownload = () => {
    fetch(this.props.src).then(res => res.blob()).then(blob => {
      const element   = document.createElement('a');
      const objectURL = URL.createObjectURL(blob);

      element.setAttribute('href', objectURL);
      element.setAttribute('download', fileNameFromURL(this.props.src));

      document.body.appendChild(element);
      element.click();
      document.body.removeChild(element);

      URL.revokeObjectURL(objectURL);
    }).catch(err => {
      console.error(err);
    });
  }

  _renderCanvas () {
    requestAnimationFrame(() => {
      if (!this.audio) return;

      this.handleTimeUpdate();
      this._clear();
      this._draw();

      if (!this.state.paused) {
        this._renderCanvas();
      }
    });
  }

  _clear() {
    this.visualizer.clear(this.state.width, this.state.height);
  }

  _draw() {
    this.visualizer.draw(this._getCX(), this._getCY(), this._getAccentColor(), this._getRadius(), this._getScaleCoefficient());
  }

  _getRadius () {
    return parseInt(((this.state.height || this.props.height) - (PADDING * this._getScaleCoefficient()) * 2) / 2);
  }

  _getScaleCoefficient () {
    return (this.state.height || this.props.height) / 982;
  }

  _getCX() {
    return Math.floor(this.state.width / 2);
  }

  _getCY() {
    return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient()));
  }

  _getAccentColor () {
    return this.props.accentColor || '#ffffff';
  }

  _getBackgroundColor () {
    return this.props.backgroundColor || '#000000';
  }

  _getForegroundColor () {
    return this.props.foregroundColor || '#ffffff';
  }

  render () {
    const { src, intl, alt, editable, autoPlay } = this.props;
    const { paused, muted, volume, currentTime, duration, buffer, dragging } = this.state;
    const progress = (currentTime / duration) * 100;

    return (

        <audio
          src={src}
          ref={this.setAudioRef}
          preload={autoPlay ? 'auto' : 'none'}
          onPlay={this.handlePlay}
          onPause={this.handlePause}
          onProgress={this.handleProgress}
          onLoadedData={this.handleLoadedData}
          crossOrigin='anonymous'
        />

        <canvas
          role='button'
          className='audio-player__canvas'
          width={this.state.width}
          height={this.state.height}
          style={{ width: '100%', position: 'absolute', top: 0, left: 0 }}
          ref={this.setCanvasRef}
          onClick={this.togglePlay}
          title={alt}
          aria-label={alt}
        />

          <div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} />
          <div className='video-player__seek__progress' style={{ width: `${progress}%`, backgroundColor: this._getAccentColor() }} />

          <span
            className={classNames('video-player__seek__handle', { active: dragging })}
            tabIndex='0'
            style={{ left: `${progress}%`, backgroundColor: this._getAccentColor() }}
          />

              <button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
              <button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>

                <div className='video-player__volume__current' style={{ width: `${volume * 100}%`, backgroundColor: this._getAccentColor() }} />

                <span
                  className={classNames('video-player__volume__handle')}
                  tabIndex='0'
                  style={{ left: `${volume * 100}%`, backgroundColor: this._getAccentColor() }}
                />

                {formatTime(Math.floor(currentTime))}
                /
                {formatTime(this.state.duration || Math.floor(this.props.duration))}

              <button type='button' title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} onClick={this.handleDownload}><Icon id='download' fixedWidth /></button>

    );
  }

}
<commit_msg>Fix audio player on Safari (#14485)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { formatTime } from 'mastodon/features/video';
import Icon from 'mastodon/components/icon';
import classNames from 'classnames';
import { throttle } from 'lodash';
import { getPointerPosition, fileNameFromURL } from 'mastodon/features/video';
import { debounce } from 'lodash';
import Visualizer from './visualizer';

const messages = defineMessages({
  play: { id: 'video.play', defaultMessage: 'Play' },
  pause: { id: 'video.pause', defaultMessage: 'Pause' },
  mute: { id: 'video.mute', defaultMessage: 'Mute sound' },
  unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
  download: { id: 'video.download', defaultMessage: 'Download file' },
});

const TICK_SIZE = 10;
const PADDING   = 180;

export default @injectIntl
class Audio extends React.PureComponent {

  static propTypes = {
    src: PropTypes.string.isRequired,
    alt: PropTypes.string,
    poster: PropTypes.string,
    duration: PropTypes.number,
    width: PropTypes.number,
    height: PropTypes.number,
    editable: PropTypes.bool,
    fullscreen: PropTypes.bool,
    intl: PropTypes.object.isRequired,
    cacheWidth: PropTypes.func,
    backgroundColor: PropTypes.string,
    foregroundColor: PropTypes.string,
    accentColor: PropTypes.string,
    autoPlay: PropTypes.bool,
  };

  state = {
    width: this.props.width,
    currentTime: 0,
    buffer: 0,
    duration: null,
    paused: true,
    muted: false,
    volume: 0.5,
    dragging: false,
  };

  constructor (props) {
    super(props);
    this.visualizer = new Visualizer(TICK_SIZE);
  }

  setPlayerRef = c => {
    this.player = c;

    if (this.player) {
      this._setDimensions();
    }
  }

  _setDimensions () {
    const width  = this.player.offsetWidth;
    const height = this.props.fullscreen ? this.player.offsetHeight : (width / (16/9));

    if (this.props.cacheWidth) {
      this.props.cacheWidth(width);
    }

    this.setState({ width, height });
  }

  setSeekRef = c => {
    this.seek = c;
  }

  setVolumeRef = c => {
    this.volume = c;
  }

  setAudioRef = c => {
    this.audio = c;

    if (this.audio) {
      this.setState({ volume: this.audio.volume, muted: this.audio.muted });
    }
  }

  setCanvasRef = c => {
    this.canvas = c;

    this.visualizer.setCanvas(c);
  }

  componentDidMount () {
    window.addEventListener('scroll', this.handleScroll);
    window.addEventListener('resize', this.handleResize, { passive: true });
  }

  componentDidUpdate (prevProps, prevState) {
    if (prevProps.src !== this.props.src || this.state.width !== prevState.width || this.state.height !== prevState.height || prevProps.accentColor !== this.props.accentColor) {
      this._clear();
      this._draw();
    }
  }

  componentWillUnmount () {
    window.removeEventListener('scroll', this.handleScroll);
    window.removeEventListener('resize', this.handleResize);
  }

  togglePlay = () => {
    if (!this.audioContext) {
      this._initAudioContext();
    }

    if (this.state.paused) {
      this.setState({ paused: false }, () => this.audio.play());
    } else {
      this.setState({ paused: true }, () => this.audio.pause());
    }
  }

  handleResize = debounce(() => {
    if (this.player) {
      this._setDimensions();
    }
  }, 250, {
    trailing: true,
  });

  handlePlay = () => {
    this.setState({ paused: false });

    if (this.audioContext && this.audioContext.state === 'suspended') {
      this.audioContext.resume();
    }

    this._renderCanvas();
  }

  handlePause = () => {
    this.setState({ paused: true });

    if (this.audioContext) {
      this.audioContext.suspend();
    }
  }

  handleProgress = () => {
    const lastTimeRange = this.audio.buffered.length - 1;

    if (lastTimeRange > -1) {
      this.setState({ buffer: Math.ceil(this.audio.buffered.end(lastTimeRange) / this.audio.duration * 100) });
    }
  }

  toggleMute = () => {
    const muted = !this.state.muted;

    this.setState({ muted }, () => {
      this.audio.muted = muted;
    });
  }

  handleVolumeMouseDown = e => {
    document.addEventListener('mousemove', this.handleMouseVolSlide, true);
    document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
    document.addEventListener('touchmove', this.handleMouseVolSlide, true);
    document.addEventListener('touchend', this.handleVolumeMouseUp, true);

    this.handleMouseVolSlide(e);

    e.preventDefault();
    e.stopPropagation();
  }

  handleVolumeMouseUp = () => {
    document.removeEventListener('mousemove', this.handleMouseVolSlide, true);
    document.removeEventListener('mouseup', this.handleVolumeMouseUp, true);
    document.removeEventListener('touchmove', this.handleMouseVolSlide, true);
    document.removeEventListener('touchend', this.handleVolumeMouseUp, true);
  }

  handleMouseDown = e => {
    document.addEventListener('mousemove', this.handleMouseMove, true);
    document.addEventListener('mouseup', this.handleMouseUp, true);
    document.addEventListener('touchmove', this.handleMouseMove, true);
    document.addEventListener('touchend', this.handleMouseUp, true);

    this.setState({ dragging: true });
    this.audio.pause();
    this.handleMouseMove(e);

    e.preventDefault();
    e.stopPropagation();
  }

  handleMouseUp = () => {
    document.removeEventListener('mousemove', this.handleMouseMove, true);
    document.removeEventListener('mouseup', this.handleMouseUp, true);
    document.removeEventListener('touchmove', this.handleMouseMove, true);
    document.removeEventListener('touchend', this.handleMouseUp, true);

    this.setState({ dragging: false });
    this.audio.play();
  }

  handleMouseMove = throttle(e => {
    const { x } = getPointerPosition(this.seek, e);
    const currentTime = this.audio.duration * x;

    if (!isNaN(currentTime)) {
      this.setState({ currentTime }, () => {
        this.audio.currentTime = currentTime;
      });
    }
  }, 15);

  handleTimeUpdate = () => {
    this.setState({
      currentTime: this.audio.currentTime,
      duration: Math.floor(this.audio.duration),
    });
  }

  handleMouseVolSlide = throttle(e => {
    const { x } = getPointerPosition(this.volume, e);

    if(!isNaN(x)) {
      this.setState({ volume: x }, () => {
        this.audio.volume = x;
      });
    }
  }, 15);

  handleScroll = throttle(() => {
    if (!this.canvas || !this.audio) {
      return;
    }

    const { top, height } = this.canvas.getBoundingClientRect();
    const inView = (top <= (window.innerHeight || document.documentElement.clientHeight)) && (top + height >= 0);

    if (!this.state.paused && !inView) {
      this.setState({ paused: true }, () => this.audio.pause());
    }
  }, 150, { trailing: true });

  handleMouseEnter = () => {
    this.setState({ hovered: true });
  }

  handleMouseLeave = () => {
    this.setState({ hovered: false });
  }

  handleLoadedData = () => {
    const { autoPlay } = this.props;

    if (autoPlay) {
      this.audio.play();
    }
  }

  _initAudioContext () {
    const AudioContext = window.AudioContext || window.webkitAudioContext;
    const context      = new AudioContext();
    const source       = context.createMediaElementSource(this.audio);

    this.visualizer.setAudioContext(context, source);
    source.connect(context.destination);

    this.audioContext = context;
  }

  handleDownload = () => {
    fetch(this.props.src).then(res => res.blob()).then(blob => {
      const element   = document.createElement('a');
      const objectURL = URL.createObjectURL(blob);

      element.setAttribute('href', objectURL);
      element.setAttribute('download', fileNameFromURL(this.props.src));

      document.body.appendChild(element);
      element.click();
      document.body.removeChild(element);

      URL.revokeObjectURL(objectURL);
    }).catch(err => {
      console.error(err);
    });
  }

  _renderCanvas () {
    requestAnimationFrame(() => {
      if (!this.audio) return;

      this.handleTimeUpdate();
      this._clear();
      this._draw();

      if (!this.state.paused) {
        this._renderCanvas();
      }
    });
  }

  _clear() {
    this.visualizer.clear(this.state.width, this.state.height);
  }

  _draw() {
    this.visualizer.draw(this._getCX(), this._getCY(), this._getAccentColor(), this._getRadius(), this._getScaleCoefficient());
  }

  _getRadius () {
    return parseInt(((this.state.height || this.props.height) - (PADDING * this._getScaleCoefficient()) * 2) / 2);
  }

  _getScaleCoefficient () {
    return (this.state.height || this.props.height) / 982;
  }

  _getCX() {
    return Math.floor(this.state.width / 2);
  }

  _getCY() {
    return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient()));
  }

  _getAccentColor () {
    return this.props.accentColor || '#ffffff';
  }

  _getBackgroundColor () {
    return this.props.backgroundColor || '#000000';
  }

  _getForegroundColor () {
    return this.props.foregroundColor || '#ffffff';
  }

  render () {
    const { src, intl, alt, editable, autoPlay } = this.props;
    const { paused, muted, volume, currentTime, duration, buffer, dragging } = this.state;
    const progress = (currentTime / duration) * 100;

    return (

        <audio
          src={src}
          ref={this.setAudioRef}
          preload={autoPlay ? 'auto' : 'none'}
          onPlay={this.handlePlay}
          onPause={this.handlePause}
          onProgress={this.handleProgress}
          onLoadedData={this.handleLoadedData}
          crossOrigin='anonymous'
        />

        <canvas
          role='button'
          className='audio-player__canvas'
          width={this.state.width}
          height={this.state.height}
          style={{ width: '100%', position: 'absolute', top: 0, left: 0 }}
          ref={this.setCanvasRef}
          onClick={this.togglePlay}
          title={alt}
          aria-label={alt}
        />

          <div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} />
          <div className='video-player__seek__progress' style={{ width: `${progress}%`, backgroundColor: this._getAccentColor() }} />

          <span
            className={classNames('video-player__seek__handle', { active: dragging })}
            tabIndex='0'
            style={{ left: `${progress}%`, backgroundColor: this._getAccentColor() }}
          />

              <button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
              <button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>

                <div className='video-player__volume__current' style={{ width: `${volume * 100}%`, backgroundColor: this._getAccentColor() }} />

                <span
                  className={classNames('video-player__volume__handle')}
                  tabIndex='0'
                  style={{ left: `${volume * 100}%`, backgroundColor: this._getAccentColor() }}
                />

                {formatTime(Math.floor(currentTime))}
                /
                {formatTime(this.state.duration || Math.floor(this.props.duration))}

              <button type='button' title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} onClick={this.handleDownload}><Icon id='download' fixedWidth /></button>

    );
  }

}
<|endoftext|>


<commit_before>jest.setTimeout(30000)

const create = require('@vue/cli-test-utils/createTestProject')
const path = require('path')

const cwd = path.resolve(__dirname, '../../../test/cli-packages')
const serve = require('@vue/cli-test-utils/serveWithPuppeteer')

async function createAndInstall(name) {
	const project = await create(name, { plugins: { 'vue-cli-plugin-styleguidist': {} } }, cwd, false)
	// mock install
	const pkg = JSON.parse(await project.read('package.json'))
	pkg.devDependencies['vue-cli-plugin-styleguidist'] = '*'
	pkg.devDependencies['@vue/cli-plugin-babel'] = '*'
	pkg.devDependencies['@vue/cli-plugin-eslint'] = '*'
	pkg.devDependencies['babel-eslint'] = '*'
	pkg.devDependencies['eslint'] = '*'
	pkg.devDependencies['eslint-plugin-vue'] = '*'
	pkg['eslintConfig'] = {
		root: true,
		env: {
			node: true
		},
		extends: ['plugin:vue/essential', 'eslint:recommended'],
		rules: {},
		parserOptions: {
			parser: 'babel-eslint'
		}
	}
	pkg['browserslist'] = ['> 1%', 'last 2 versions', 'not ie <= 8']
	await project.write('package.json', JSON.stringify(pkg, null, 2))
	await project.write(
		'babel.config.js',
		['module.exports = {', "	presets: ['@vue/app']", '}'].join('\n')
	)
	return project
}

test('serve with babel', async () => {
	const project = await createAndInstall(`serve-babel`)
	await serve(
		() => project.run('vue-cli-service styleguidist'),
		async ({ helpers }) => {
			expect(await helpers.getText('h1[class^=rsg--logo]')).toMatch('Default Style Guide')
		}
	)
})
<commit_msg>test(plugin): extend babel test timeout to 80 seconds<commit_after>jest.setTimeout(80000)

const create = require('@vue/cli-test-utils/createTestProject')
const path = require('path')

const cwd = path.resolve(__dirname, '../../../test/cli-packages')
const serve = require('@vue/cli-test-utils/serveWithPuppeteer')

async function createAndInstall(name) {
	const project = await create(name, { plugins: { 'vue-cli-plugin-styleguidist': {} } }, cwd, false)
	// mock install
	const pkg = JSON.parse(await project.read('package.json'))
	pkg.devDependencies['vue-cli-plugin-styleguidist'] = '*'
	pkg.devDependencies['@vue/cli-plugin-babel'] = '*'
	pkg.devDependencies['@vue/cli-plugin-eslint'] = '*'
	pkg.devDependencies['babel-eslint'] = '*'
	pkg.devDependencies['eslint'] = '*'
	pkg.devDependencies['eslint-plugin-vue'] = '*'
	pkg['eslintConfig'] = {
		root: true,
		env: {
			node: true
		},
		extends: ['plugin:vue/essential', 'eslint:recommended'],
		rules: {},
		parserOptions: {
			parser: 'babel-eslint'
		}
	}
	pkg['browserslist'] = ['> 1%', 'last 2 versions', 'not ie <= 8']
	await project.write('package.json', JSON.stringify(pkg, null, 2))
	await project.write(
		'babel.config.js',
		['module.exports = {', "	presets: ['@vue/app']", '}'].join('\n')
	)
	return project
}

test('serve with babel', async () => {
	const project = await createAndInstall(`serve-babel`)
	await serve(
		() => project.run('vue-cli-service styleguidist'),
		async ({ helpers }) => {
			expect(await helpers.getText('h1[class^=rsg--logo]')).toMatch('Default Style Guide')
		}
	)
})
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';

import { panel } from './styles.scss';

const Layout = (props) => {
    const {
        children,
        toggle,
        position = 'left'
    } = props;

    const style = {
        opacity: toggle,
        [position]: (toggle * 100) - 100
    };

    return (

                { children }

    );
};

Layout.propTypes = {
    children: PropTypes.node,
    toggle: PropTypes.number,
    position: PropTypes.oneOf(['left', 'right', 'top', 'bottom'])
};

export default Layout;
<commit_msg>removed extra html<commit_after>import React, { PropTypes } from 'react';

import { panel } from './styles.scss';

const Layout = (props) => {
    const {
        children,
        toggle,
        position = 'left'
    } = props;

    const style = {
        opacity: toggle,
        [position]: (toggle * 100) - 100
    };

    return (

            { children }

    );
};

Layout.propTypes = {
    children: PropTypes.node,
    toggle: PropTypes.number,
    position: PropTypes.oneOf(['left', 'right', 'top', 'bottom'])
};

export default Layout;
<|endoftext|>


<commit_before>// JavaScript Document promisehdr.js: Keyboard management for KeymanWeb demonstration pages

/*
    The keyboard name and/or BCP-47 language code must be specified for each keyboard that is to be available.
    If the same keyboard is used for several languages, it must be listed for each
    language, but the keyboard itself will only be loaded once.
    If two (or more) keyboards are to be available for a given language, both must be listed.
    Any number of keyboards may be specified in one or more calls.
    Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path.
    The actual keyboard object will be downloaded asynchronously when first selected for use.

    Each argument to addKeyboards() is a string, for example:
      european2         loads the current version of the Eurolatin 2 keyboard (for its default language)
      european2@fr      loads the current version of the Eurolatin 2 keyboard for French
      european2@fr@1.2  loads version 1.2 of the Eurolatin 2 keyboard for French

    Argument syntax also supports the following extensions:
      @fr               load the current version of the default keyboard for French
      @fr$              load all available keyboards (current version) for French

    Each call to addKeyboards() requires a single call to the remote server,
    (unless all keyboards listed are local and fully specified) so it is better
    to use multiple arguments rather than separate function calls.

    Calling addKeyboards() with no arguments returns a list of *all* available keyboards.
    The Toolbar (desktop browser) UI is best suited for allowing users to select
    the appropriate language and keyboard in this case.

    Keyboards may also be specified by language name using addKeyboardsForLanguage()
    for example:
      keymanweb.addKeyboardsForLanguage('Burmese');

    Appending $ to the language name will again cause all available keyboards for that
    language to be loaded rather than the default keyboard.

    The first call to addKeyboardsForLanguage() makes an additional call to the
    keyman API to load the current list of keyboard/language associations.

    In this example, the following function loads the indicated keyboards,
    and is called when the page loads.
*/

/**
 * Add keyboards
 * @param {string | KeyboardStub | (string|KeyboardStub)[]} obj Info for adding a keyboard
 */
  function addKeyboards(...args) {
    var kmw=keyman;
    var promise = kmw.addKeyboards(args);
    promise.then(result => {
      if (result.length > 0 && !result[0].error) {
        console.log('Adding: ', result);
      } else {
        console.log('Nothing added');
      }
    }).catch(errorStubs => {
      // Consumer decides how to handle errors
      errorStubs.forEach(e => console.error(e));

      // We'll also concat the error messages to an alert
      var errorMessages = errorStubs.map(e => e.error.message).join('\n');
      alert(errorMessages);
    });
  }

  /**
   * Add a keyboard by language name.  Note that the name must be spelled
     correctly, or the keyboard will not be found.  (Using BCP-47 codes is
     usually easier.)
   * @param {string} languages. Could be comma-separated languages
   */
  function addKeyboardsForLanguage(languages) {
    var kmw=keyman;
    kmw.addKeyboardsForLanguage(languages.split(',').map(item => item.trim())).then(result => {
      console.log('Adding ' + languages + ': ', result);
    }).catch(errorStubs => {
      // Consumer decides how to handle errors
      errorStubs.forEach(e => console.error(e));

      // We'll also concat the error messages to an alert
      var errorMessages = errorStubs.map(e => e.error.message).join('\n');
      alert(errorMessages);
    });
  }

  function loadKeyboards()
  {
    // No parameter adds entire cloud keyboard catalog
    //addKeyboards();

    // The first keyboard added will be the default keyboard for touch devices.
    // For faster loading, it may be best for the default keyboard to be
    // locally sourced.
    addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'},
      filename:'../us-1.0.js'});

    // Add more keyboards to the language menu, by keyboard name,
    // keyboard name and language code, or just the BCP-47 language code.
    // We use a different loading pattern here than in the samples version to provide a slightly different set of test cases.
    addKeyboards('spanish','@nqo');

    // One keyboard - 2 languages
    addKeyboards({id:'sil_euro_latin', name:'SIL EuroLatin', languages: [{id:'no'}, {id:'sv'}]}); // Loads from partial stub instead of the compact string.

    // One invalid keyboard
    addKeyboards({id: 'invalid', name:'Invalid'});

    // Two keyboards, 1 invalid
    addKeyboards({id:'sil_cameroon_qwerty', name:'SIL Cameroon Qwerty', languages:[{id:'aal-latn'}]},
                 {id:'khmer_angkor', name: 'Khmer Angkor', languages:[{id:'km'}]},
                 {id: 'invalid2', name:'Invalid2'});

    // Add a keyboard by language name.  Note that the name must be spelled
    // correctly, or the keyboard will not be found.  (Using BCP-47 codes is
    // usually easier.)
    addKeyboardsForLanguage('Dzongkha');

    // Add a fully-specified, locally-sourced, keyboard with custom font
    addKeyboards({id:'lao_2008_basic',name:'Lao Basic',
      languages: {
          id:'lo',name:'Lao',region:'Asia'
        },
      filename:'../lao_2008_basic-1.2.js'
      });
    // The following two optional calls should be delayed until language menus are fully loaded:
    //  (a) a specific mapped input element input is focused, to ensure that the OSK appears
    //  (b) a specific keyboard is loaded, rather than the keyboard last used.
    //window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500);
    //window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fr');},3000);

    // Note that locally specified keyboards will be listed before keyboards
    // requested from the remote server by user interfaces that do not order
    // keyboards alphabetically by language.
  }

  // Script to allow a user to add any keyboard to the keyboard menu
  function addKeyboard(n)
  {
    var sKbd,kmw=keyman;
    switch(n)
    {
      case 1:
        sKbd=document.getElementById('kbd_id1').value;
        addKeyboards(sKbd);
        break;
      case 2:
        sKbd=document.getElementById('kbd_id2').value.toLowerCase();
        kmw.addKeyboards('@'+sKbd);
        break;
      case 3:
        // Add keyboard for comma-separated language name(s)
        sKbd=document.getElementById('kbd_id3').value;
        addKeyboardsForLanguage(sKbd);
        break;
    }
  }

  // Add keyboard on Enter (as well as pressing button)
  function clickOnEnter(e,id)
  {
    e = e || window.event;
    if(e.keyCode == 13) addKeyboard(id);
  }

<commit_msg>chore(web): Test single language object<commit_after>// JavaScript Document promisehdr.js: Keyboard management for KeymanWeb demonstration pages

/*
    The keyboard name and/or BCP-47 language code must be specified for each keyboard that is to be available.
    If the same keyboard is used for several languages, it must be listed for each
    language, but the keyboard itself will only be loaded once.
    If two (or more) keyboards are to be available for a given language, both must be listed.
    Any number of keyboards may be specified in one or more calls.
    Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path.
    The actual keyboard object will be downloaded asynchronously when first selected for use.

    Each argument to addKeyboards() is a string, for example:
      european2         loads the current version of the Eurolatin 2 keyboard (for its default language)
      european2@fr      loads the current version of the Eurolatin 2 keyboard for French
      european2@fr@1.2  loads version 1.2 of the Eurolatin 2 keyboard for French

    Argument syntax also supports the following extensions:
      @fr               load the current version of the default keyboard for French
      @fr$              load all available keyboards (current version) for French

    Each call to addKeyboards() requires a single call to the remote server,
    (unless all keyboards listed are local and fully specified) so it is better
    to use multiple arguments rather than separate function calls.

    Calling addKeyboards() with no arguments returns a list of *all* available keyboards.
    The Toolbar (desktop browser) UI is best suited for allowing users to select
    the appropriate language and keyboard in this case.

    Keyboards may also be specified by language name using addKeyboardsForLanguage()
    for example:
      keymanweb.addKeyboardsForLanguage('Burmese');

    Appending $ to the language name will again cause all available keyboards for that
    language to be loaded rather than the default keyboard.

    The first call to addKeyboardsForLanguage() makes an additional call to the
    keyman API to load the current list of keyboard/language associations.

    In this example, the following function loads the indicated keyboards,
    and is called when the page loads.
*/

/**
 * Add keyboards
 * @param {string | KeyboardStub | (string|KeyboardStub)[]} obj Info for adding a keyboard
 */
  function addKeyboards(...args) {
    var kmw=keyman;
    var promise = kmw.addKeyboards(args);
    promise.then(result => {
      if (result.length > 0 && !result[0].error) {
        console.log('Adding: ', result);
      } else {
        console.log('Nothing added');
      }
    }).catch(errorStubs => {
      // Consumer decides how to handle errors
      errorStubs.forEach(e => console.error(e));

      // We'll also concat the error messages to an alert
      var errorMessages = errorStubs.map(e => e.error.message).join('\n');
      alert(errorMessages);
    });
  }

  /**
   * Add a keyboard by language name.  Note that the name must be spelled
     correctly, or the keyboard will not be found.  (Using BCP-47 codes is
     usually easier.)
   * @param {string} languages. Could be comma-separated languages
   */
  function addKeyboardsForLanguage(languages) {
    var kmw=keyman;
    kmw.addKeyboardsForLanguage(languages.split(',').map(item => item.trim())).then(result => {
      console.log('Adding ' + languages + ': ', result);
    }).catch(errorStubs => {
      // Consumer decides how to handle errors
      errorStubs.forEach(e => console.error(e));

      // We'll also concat the error messages to an alert
      var errorMessages = errorStubs.map(e => e.error.message).join('\n');
      alert(errorMessages);
    });
  }

  function loadKeyboards()
  {
    // No parameter adds entire cloud keyboard catalog
    //addKeyboards();

    // The first keyboard added will be the default keyboard for touch devices.
    // For faster loading, it may be best for the default keyboard to be
    // locally sourced.
    addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'},
      filename:'../us-1.0.js'});

    // Add more keyboards to the language menu, by keyboard name,
    // keyboard name and language code, or just the BCP-47 language code.
    // We use a different loading pattern here than in the samples version to provide a slightly different set of test cases.
    addKeyboards('spanish','@nqo');

    // One keyboard - 2 languages
    addKeyboards({id:'sil_euro_latin', name:'SIL EuroLatin', languages: [{id:'no'}, {id:'sv'}]}); // Loads from partial stub instead of the compact string.

    // One invalid keyboard
    addKeyboards({id: 'invalid', name:'Invalid'});

    // Two keyboards, 1 invalid
    addKeyboards({id:'sil_cameroon_qwerty', name:'SIL Cameroon Qwerty', languages:[{id:'aal-latn'}]},
                 {id:'khmer_angkor', name: 'Khmer Angkor', languages:{id:'km'}},
                 {id: 'invalid2', name:'Invalid2'});

    // Add a keyboard by language name.  Note that the name must be spelled
    // correctly, or the keyboard will not be found.  (Using BCP-47 codes is
    // usually easier.)
    addKeyboardsForLanguage('Dzongkha');

    // Add a fully-specified, locally-sourced, keyboard with custom font
    addKeyboards({id:'lao_2008_basic',name:'Lao Basic',
      languages: {
          id:'lo',name:'Lao',region:'Asia'
        },
      filename:'../lao_2008_basic-1.2.js'
      });
    // The following two optional calls should be delayed until language menus are fully loaded:
    //  (a) a specific mapped input element input is focused, to ensure that the OSK appears
    //  (b) a specific keyboard is loaded, rather than the keyboard last used.
    //window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500);
    //window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fr');},3000);

    // Note that locally specified keyboards will be listed before keyboards
    // requested from the remote server by user interfaces that do not order
    // keyboards alphabetically by language.
  }

  // Script to allow a user to add any keyboard to the keyboard menu
  function addKeyboard(n)
  {
    var sKbd,kmw=keyman;
    switch(n)
    {
      case 1:
        sKbd=document.getElementById('kbd_id1').value;
        addKeyboards(sKbd);
        break;
      case 2:
        sKbd=document.getElementById('kbd_id2').value.toLowerCase();
        kmw.addKeyboards('@'+sKbd);
        break;
      case 3:
        // Add keyboard for comma-separated language name(s)
        sKbd=document.getElementById('kbd_id3').value;
        addKeyboardsForLanguage(sKbd);
        break;
    }
  }

  // Add keyboard on Enter (as well as pressing button)
  function clickOnEnter(e,id)
  {
    e = e || window.event;
    if(e.keyCode == 13) addKeyboard(id);
  }

<|endoftext|>


<commit_before>import tinycolor from 'tinycolor2';

import View from 'girder/views/View';

import StyleModel from '../models/StyleModel';
import editStyleGroups from '../templates/dialogs/editStyleGroups.pug';
import 'girder/utilities/jquery/girderModal';
import '../stylesheets/dialogs/editStyleGroups.styl';

/**
 * Create a modal dialog with fields to edit and create annotation
 * style groups.
 */
const EditStyleGroups = View.extend({
    events: {
        'click .h-create-new-style': '_createNewStyle',
        'click .h-save-new-style': '_saveNewStyle',
        'change .h-style-def': '_updateStyle',
        'changeColor .h-colorpicker': '_updateStyle',
        'change select': '_setStyle'
    },

    render() {
        this.$('.h-colorpicker').colorpicker('destroy');
        this.$el.html(
            editStyleGroups({
                collection: this.collection,
                model: this.model,
                newStyle: this._newStyle
            })
        );
        this.$('.h-colorpicker').colorpicker();
        return this;
    },

    _setStyle(evt) {
        evt.preventDefault();
        this.model.set(
            this.collection.get(this.$('.h-style-selector').val()).toJSON()
        );
        this.render();
    },

    _updateStyle(evt) {
        evt.preventDefault();

        const data = {};
        const label = this.$('#h-element-label').val();
        let validation = '';

        data.id = this.$('.h-style-selector :selected').val() || this.$('.h-new-style-name').val();
        if (!data.id) {
            validation += 'A style name is required';
            this.$('.h-new-style-name').parent().addClass('has-error');
        }

        if (label) {
            data.label = {
                value: label
            };
        }

        const lineWidth = this.$('#h-element-line-width').val();
        if (lineWidth) {
            data.lineWidth = parseFloat(lineWidth);
            if (data.lineWidth < 0 || !isFinite(data.lineWidth)) {
                validation += 'Invalid line width. ';
                this.$('#h-element-line-width').parent().addClass('has-error');
            }
        }

        const lineColor = this.$('#h-element-line-color').val();
        if (lineColor) {
            data.lineColor = this.convertColor(lineColor);
        }

        const fillColor = this.$('#h-element-fill-color').val();
        if (fillColor) {
            data.fillColor = this.convertColor(fillColor);
        }

        if (validation) {
            this.$('.g-validation-failed-message').text(validation)
                .removeClass('hidden');
        }

        this.model.set(data);
    },

    /**
     * A helper function converting a string into normalized rgb/rgba
     * color value.  If no value is given, then it returns a color
     * with opacity 0.
     */
    convertColor(val) {
        if (!val) {
            return 'rgba(0,0,0,0)';
        }
        return tinycolor(val).toRgbString();
    },

    _createNewStyle(evt) {
        evt.preventDefault();
        this._newStyle = true;
        this.render();
    },

    _saveNewStyle(evt) {
        this._updateStyle(evt);
        this._newStyle = false;
        this.collection.create(this.model.toJSON());
        this.render();
    }
});

const EditStyleGroupsDialog = View.extend({
    events: {
        'click .h-submit': '_submit'
    },

    initialize() {
        this.form = new EditStyleGroups({
            parentView: this,
            model: new StyleModel(this.model.toJSON()),
            collection: this.collection
        });
    },

    render() {
        this.$el.html('
');
        this.form.setElement(this.$('.h-style-editor')).render();
        this.$el.girderModal(this);
        return this;
    },

    _submit(evt) {
        evt.preventDefault();
        this.model.set(this.form.model.toJSON());
        this.collection.add(this.form.model.toJSON(), {merge: true});
        this.collection.get(this.model.id).save();
        this.$el.modal('hide');
    }
});

/**
 * Show the edit dialog box.  Watch for change events on the passed
 * `ElementModel` to respond to user submission of the form.
 *
 * @param {StyleGroupCollection} collection
 * @returns {EditStyleGroup} The dialog's view
 */
function show(style, groups) {
    const dialog = new EditStyleGroupsDialog({
        parentView: null,
        collection: groups,
        model: style,
        el: $('#g-dialog-container')
    });
    return dialog.render();
}

export default show;
<commit_msg>Allow unsetting a label in the style group editor<commit_after>import tinycolor from 'tinycolor2';

import View from 'girder/views/View';

import StyleModel from '../models/StyleModel';
import editStyleGroups from '../templates/dialogs/editStyleGroups.pug';
import 'girder/utilities/jquery/girderModal';
import '../stylesheets/dialogs/editStyleGroups.styl';

/**
 * Create a modal dialog with fields to edit and create annotation
 * style groups.
 */
const EditStyleGroups = View.extend({
    events: {
        'click .h-create-new-style': '_createNewStyle',
        'click .h-save-new-style': '_saveNewStyle',
        'change .h-style-def': '_updateStyle',
        'changeColor .h-colorpicker': '_updateStyle',
        'change select': '_setStyle'
    },

    render() {
        this.$('.h-colorpicker').colorpicker('destroy');
        this.$el.html(
            editStyleGroups({
                collection: this.collection,
                model: this.model,
                newStyle: this._newStyle
            })
        );
        this.$('.h-colorpicker').colorpicker();
        return this;
    },

    _setStyle(evt) {
        evt.preventDefault();
        this.model.set(
            this.collection.get(this.$('.h-style-selector').val()).toJSON()
        );
        this.render();
    },

    _updateStyle(evt) {
        evt.preventDefault();

        const data = {};
        const label = this.$('#h-element-label').val();
        let validation = '';

        data.id = this.$('.h-style-selector :selected').val() || this.$('.h-new-style-name').val();
        if (!data.id) {
            validation += 'A style name is required';
            this.$('.h-new-style-name').parent().addClass('has-error');
        }

        if (label) {
            data.label = {
                value: label
            };
        } else {
            data.label = {};
        }

        const lineWidth = this.$('#h-element-line-width').val();
        if (lineWidth) {
            data.lineWidth = parseFloat(lineWidth);
            if (data.lineWidth < 0 || !isFinite(data.lineWidth)) {
                validation += 'Invalid line width. ';
                this.$('#h-element-line-width').parent().addClass('has-error');
            }
        }

        const lineColor = this.$('#h-element-line-color').val();
        if (lineColor) {
            data.lineColor = this.convertColor(lineColor);
        }

        const fillColor = this.$('#h-element-fill-color').val();
        if (fillColor) {
            data.fillColor = this.convertColor(fillColor);
        }

        if (validation) {
            this.$('.g-validation-failed-message').text(validation)
                .removeClass('hidden');
        }

        this.model.set(data);
    },

    /**
     * A helper function converting a string into normalized rgb/rgba
     * color value.  If no value is given, then it returns a color
     * with opacity 0.
     */
    convertColor(val) {
        if (!val) {
            return 'rgba(0,0,0,0)';
        }
        return tinycolor(val).toRgbString();
    },

    _createNewStyle(evt) {
        evt.preventDefault();
        this._newStyle = true;
        this.render();
    },

    _saveNewStyle(evt) {
        this._updateStyle(evt);
        this._newStyle = false;
        this.collection.create(this.model.toJSON());
        this.render();
    }
});

const EditStyleGroupsDialog = View.extend({
    events: {
        'click .h-submit': '_submit'
    },

    initialize() {
        this.form = new EditStyleGroups({
            parentView: this,
            model: new StyleModel(this.model.toJSON()),
            collection: this.collection
        });
    },

    render() {
        this.$el.html('
');
        this.form.setElement(this.$('.h-style-editor')).render();
        this.$el.girderModal(this);
        return this;
    },

    _submit(evt) {
        evt.preventDefault();
        this.model.set(this.form.model.toJSON());
        this.collection.add(this.form.model.toJSON(), {merge: true});
        this.collection.get(this.model.id).save();
        this.$el.modal('hide');
    }
});

/**
 * Show the edit dialog box.  Watch for change events on the passed
 * `ElementModel` to respond to user submission of the form.
 *
 * @param {StyleGroupCollection} collection
 * @returns {EditStyleGroup} The dialog's view
 */
function show(style, groups) {
    const dialog = new EditStyleGroupsDialog({
        parentView: null,
        collection: groups,
        model: style,
        el: $('#g-dialog-container')
    });
    return dialog.render();
}

export default show;
<|endoftext|>


<commit_before>/**
 * Author: Vova Zaytsev <zaytsev@usc.edu>
 */

app.controller("NlcdClientController", ["$scope", "$location", "$sce", "NcldApiFactory",
    function ($scope, $location, $sce, NcldApiFactory) {

        /**
          *
          **/

        var graphId = $location.search().g;

        $scope.central              = null;
        $scope.related              = [];
        $scope.meta                 = null;
        $scope.selection            = [];
        $scope.textSelection        = null;
        $scope.dateDistr            = null;
        $scope.selectedDateEntry    = null;

        $scope.display = {

            // Tab 'ALL'
            relatedTabAllReversed:        false,
            relatedTabAllPredicate:       "inSelection",

            // Tab 'SOURCES'
            relatedTabSourcesReversed:    false,
            relatedTabSourcesPredicate:   "referenceCount",

            // Tab 'AUTHORS'
            relatedTabAuthorsReversed:    false,
            relatedTabAuthorsPredicate:   "referenceCount",

            // Tab 'DATES'
            relatedTabDatesReversed:      false,
            relatedTabDatesPredicate:     "date"

        };

        // Init bootstrap javascript
        $("#relatedTabs a").click(function (e) {
            e.preventDefault();
            $(this).tab("show");
        });
        $("#relatedTabs a:first").tab("show");

        // Init tooltipe
        $(".tip-tooltip").tooltip({
            "animation": true,
            "placement": "top",
        });

        $("body").popover({
              "container": "body",
              "trigger": "hover",
              "delay": 1,
              "html": true,
              "placement": "right",
              "selector": ".tip-popover"
        });

        NcldApiFactory.getTestGraph(graphId).success(function(data){

            $scope.sg = new StoryGraph(data);
            $scope.central = $scope.sg.getCentralNode();
            $scope.related = $scope.sg.getNodes();
            $scope.meta = data.meta;

            var distrPlaceId = "storyDistribution";
            var nwPlaceId = "storyNetwork";

            var distrWidth = document.getElementById(distrPlaceId).offsetWidth;
            var nwWidth = document.getElementById(nwPlaceId).offsetWidth;

            var height = 300;

            $scope.sg.drawDistribution(distrPlaceId, distrWidth, height);
            $scope.sg.drawNetwork(nwPlaceId, nwWidth, height);
            $scope.dateDistr = $scope.sg.distr.dateDistr;

        });

        //
        $scope.RelatedAllTabOrderBy = function(predicate) {
            if ($scope.display.relatedTabAllPredicate == predicate) {
                $scope.display.relatedTabAllReversed = !$scope.display.relatedTabAllReversed;
            }
            $scope.display.relatedTabAllPredicate = predicate;
        }

        //
        $scope.RelatedDatesTabOrderBy = function(predicate) {
            if ($scope.display.relatedTabDatesPredicate == predicate) {
                $scope.display.relatedTabDatesReversed = !$scope.display.relatedTabDatesReversed;
            }
            $scope.display.relatedTabDatesPredicate = predicate;
        }

        //
        $scope.SetSelection = function(referencesList) {

            for (var i in $scope.selection) {
                $scope.selection[i].inSelection = null;
            };
            $scope.selection = [];
            for (var i in referencesList) {
                var refId = referencesList[i];
                var item = $scope.sg.getNode(refId);
                item.inSelection = true;
                $scope.selection.push(item);
            };

            if (referenceList !== 0) {
                $scope.sg.gfx.SetDistributionSelection(referencesList);
                $scope.sg.gfx.SetNetworkSelection(referencesList);
            }

        };

        //
        $scope.TextSelection = function(chunk, referencesList) {

            $scope.SetSelection(0);

            if ($scope.selectedDateEntry) {
                $scope.SelectDate($scope.selectedDateEntry);
            }

            if ($scope.textSelection) {
                $scope.textSelection.isSelected = false;
                if ($scope.textSelection.tagId == chunk.tagId) {
                    $scope.textSelection = null;
                    return;
                }
            }
            $scope.textSelection = chunk;
            $scope.textSelection.isSelected = true;
            $scope.SetSelection(referencesList);

        };

        //
        $scope.SelectDate = function(dateEntry) {

            if ($scope.textSelection) {
                $scope.TextSelection($scope.textSelection, []);
            }

            $scope.SetSelection(0);

            if ($scope.selectedDateEntry == dateEntry) {

                $scope.selectedDateEntry.selected = false;
                $scope.selectedDateEntry = null;

            } else {

                $scope.SetSelection(dateEntry.selection);

                if ($scope.selectedDateEntry)
                    $scope.selectedDateEntry.selected = false;

                $scope.selectedDateEntry = dateEntry;
                $scope.selectedDateEntry.selected = true;

            }

        };

        //
        $scope.toolPopoverContent = function(node) {

            var authors = node.authors.join(", ").toTitleCase();
            var source = "";
            var len = 10000;
            for (var i in node.sources) {
                if (node.sources[i].length < len) {
                    source = node.sources[i];
                    len = node.sources[i].length;
                }
            }

            return "

authror: "
                   + authors
                   + "

source: "
                   + source
                   + "

"
                   + String.prototype.CutStr(node.body, true, 256, "...")
                   + "
";
        }

}]);<commit_msg>update contrib data<commit_after>/**
 * Author: Vova Zaytsev <zaytsev@usc.edu>
 */

app.controller("NlcdClientController", ["$scope", "$location", "$sce", "NcldApiFactory",
    function ($scope, $location, $sce, NcldApiFactory) {

        /**
          *
          **/

        var graphId = $location.search().g;

        $scope.central              = null;
        $scope.related              = [];
        $scope.meta                 = null;
        $scope.selection            = [];
        $scope.textSelection        = null;
        $scope.dateDistr            = null;
        $scope.selectedDateEntry    = null;

        $scope.display = {

            // Tab 'ALL'
            relatedTabAllReversed:        false,
            relatedTabAllPredicate:       "inSelection",

            // Tab 'SOURCES'
            relatedTabSourcesReversed:    false,
            relatedTabSourcesPredicate:   "referenceCount",

            // Tab 'AUTHORS'
            relatedTabAuthorsReversed:    false,
            relatedTabAuthorsPredicate:   "referenceCount",

            // Tab 'DATES'
            relatedTabDatesReversed:      false,
            relatedTabDatesPredicate:     "date"

        };

        // Init bootstrap javascript
        $("#relatedTabs a").click(function (e) {
            e.preventDefault();
            $(this).tab("show");
        });
        $("#relatedTabs a:first").tab("show");

        // Init tooltipe
        $(".tip-tooltip").tooltip({
            "animation": true,
            "placement": "top",
        });

        $("body").popover({
              "container": "body",
              "trigger": "hover",
              "delay": 1,
              "html": true,
              "placement": "right",
              "selector": ".tip-popover"
        });

        NcldApiFactory.getTestGraph(graphId).success(function(data){

            $scope.sg = new StoryGraph(data);
            $scope.central = $scope.sg.getCentralNode();
            $scope.related = $scope.sg.getNodes();
            $scope.meta = data.meta;

            var distrPlaceId = "storyDistribution";
            var nwPlaceId = "storyNetwork";

            var distrWidth = document.getElementById(distrPlaceId).offsetWidth;
            var nwWidth = document.getElementById(nwPlaceId).offsetWidth;

            var height = 300;

            $scope.sg.drawDistribution(distrPlaceId, distrWidth, height);
            $scope.sg.drawNetwork(nwPlaceId, nwWidth, height);
            $scope.dateDistr = $scope.sg.distr.dateDistr;

        });

        //
        $scope.RelatedAllTabOrderBy = function(predicate) {
            if ($scope.display.relatedTabAllPredicate == predicate) {
                $scope.display.relatedTabAllReversed = !$scope.display.relatedTabAllReversed;
            }
            $scope.display.relatedTabAllPredicate = predicate;
        }

        //
        $scope.RelatedDatesTabOrderBy = function(predicate) {
            if ($scope.display.relatedTabDatesPredicate == predicate) {
                $scope.display.relatedTabDatesReversed = !$scope.display.relatedTabDatesReversed;
            }
            $scope.display.relatedTabDatesPredicate = predicate;
        }

        //
        $scope.SetSelection = function(referencesList) {

            for (var i in $scope.selection) {
                $scope.selection[i].inSelection = null;
            };
            $scope.selection = [];
            for (var i in referencesList) {
                var refId = referencesList[i];
                var item = $scope.sg.getNode(refId);
                item.inSelection = true;
                $scope.selection.push(item);
            };

            if (referencesList !== 0) {
                $scope.sg.gfx.SetDistributionSelection(referencesList);
                $scope.sg.gfx.SetNetworkSelection(referencesList);
            }

        };

        //
        $scope.TextSelection = function(chunk, referencesList) {

            $scope.SetSelection(0);

            if ($scope.selectedDateEntry) {
                $scope.SelectDate($scope.selectedDateEntry);
            }

            if ($scope.textSelection) {
                $scope.textSelection.isSelected = false;
                if ($scope.textSelection.tagId == chunk.tagId) {
                    $scope.textSelection = null;
                    return;
                }
            }
            $scope.textSelection = chunk;
            $scope.textSelection.isSelected = true;
            $scope.SetSelection(referencesList);

        };

        //
        $scope.SelectDate = function(dateEntry) {

            if ($scope.textSelection) {
                $scope.TextSelection($scope.textSelection, []);
            }

            $scope.SetSelection(0);

            if ($scope.selectedDateEntry == dateEntry) {

                $scope.selectedDateEntry.selected = false;
                $scope.selectedDateEntry = null;

            } else {

                $scope.SetSelection(dateEntry.selection);

                if ($scope.selectedDateEntry)
                    $scope.selectedDateEntry.selected = false;

                $scope.selectedDateEntry = dateEntry;
                $scope.selectedDateEntry.selected = true;

            }

        };

        //
        $scope.toolPopoverContent = function(node) {

            var authors = node.authors.join(", ").toTitleCase();
            var source = "";
            var len = 10000;
            for (var i in node.sources) {
                if (node.sources[i].length < len) {
                    source = node.sources[i];
                    len = node.sources[i].length;
                }
            }

            return "

authror: "
                   + authors
                   + "

source: "
                   + source
                   + "

"
                   + String.prototype.CutStr(node.body, true, 256, "...")
                   + "
";
        }

}]);<|endoftext|>