<commit_before>/* global DOMPurify */
(function ($, OC, OCA, t) {
	"use strict";
	/**
	 * Slideshow featuring zooming
	 *
	 * @constructor
	 */
	var SlideShow = function () {
	};

	SlideShow.prototype = {
		slideshowTemplate: null,
		container: null,
		zoomablePreviewContainer: null,
		controls: null,
		imageCache: {},
		/** {Image} */
		currentImage: null,
		errorLoadingImage: false,
		onStop: null,
		zoomablePreview: null,
		active: false,
		backgroundToggle: false,

		/**
		 * Initialises the slideshow
		 *
		 * @param {boolean} autoPlay
		 * @param {number} interval
		 * @param {Array} features
		 */
		init: function (autoPlay, interval, features) {
			if (features.indexOf('background_colour_toggle') > -1) {
				this.backgroundToggle = true;
			}

			return $.when(this._getSlideshowTemplate()).then(function ($tmpl) {
				// Move the slideshow outside the content so we can hide the content
				$('body').append($tmpl);
				this.container = $('#slideshow');
				this.zoomablePreviewContainer = this.container.find('.bigshotContainer');
				this.zoomablePreview = new SlideShow.ZoomablePreview(this.container);
				this.controls =
					new SlideShow.Controls(
						this,
						this.container,
						this.zoomablePreview,
						interval,
						features);
				this.controls.init();

				this._initControlsAutoFader();

				// Replace all Owncloud svg images with png images for ancient browsers
				if (!OC.Util.hasSVGSupport()) {
					OC.Util.replaceSVG(this.$el);
				}

				// Only modern browsers can manipulate history
				if (history && history.pushState) {
					// Stop the slideshow when backing out.
					$(window).bind('popstate.slideshow', function () {
						if (this.active === true) {
							this.active = false;
							this.controls.stop();
						}
					}.bind(this));
				}
			}.bind(this)).fail(function () {
				OC.Notification.show(t('core', 'Error loading slideshow template'));
			});
		},

		/**
		 * Refreshes the slideshow's data
		 *
		 * @param {{name:string, url: string, path: string, fallBack: string}[]} images
		 * @param {boolean} autoPlay
		 */
		setImages: function (images, autoPlay) {
			this._hideImage();
			this.images = images;
			this.controls.update(images, autoPlay);
		},

		/**
		 * Launches the slideshow
		 *
		 * @param {number} index
		 *
		 * @returns {*}
		 */
		show: function (index) {
			this.hideErrorNotification();
			this.active = true;
			this.container.show();
			this.container.css('background-position', 'center');
			this._hideImage();
			var currentImageId = index;
			return this.loadImage(this.images[index]).then(function (img) {
				this.container.css('background-position', '-10000px 0');

				// check if we moved along while we were loading
				if (currentImageId === index) {
					var image = this.images[index];
					var transparent = this._isTransparent(image.mimeType);
					this.controls.showActionButtons(transparent, Gallery.token, image.permissions);
					this.errorLoadingImage = false;
					this.currentImage = img;

					var backgroundColour = '#000';
					if (transparent) {
						backgroundColour = '#fff';
					}
					img.setAttribute('alt', image.name);
					$(img).css('position', 'absolute');
					$(img).css('background-color', backgroundColour);
					if (transparent && this.backgroundToggle === true) {
						var $border = 30 / window.devicePixelRatio;
						$(img).css('outline', $border + 'px solid ' + backgroundColour);
					}

					this.zoomablePreview.startBigshot(img, this.currentImage, image.mimeType);

					this._setUrl(image.path);
					this.controls.show(currentImageId);
				}
			}.bind(this), function () {
				// Don't do anything if the user has moved along while we were loading as it would
				// mess up the index
				if (currentImageId === index) {
					this.errorLoadingImage = true;
					this.showErrorNotification(null);
					this._setUrl(this.images[index].path);
					this.images.splice(index, 1);
					this.controls.updateControls(this.images, this.errorLoadingImage);
				}
			}.bind(this));
		},

		/**
		 * Loads the image to show in the slideshow and preloads the next one
		 *
		 * @param {Object} preview
		 *
		 * @returns {*}
		 */
		loadImage: function (preview) {
			var url = preview.url;
			var mimeType = preview.mimeType;

			if (!this.imageCache[url]) {
				this.imageCache[url] = new $.Deferred();
				var image = new Image();

				image.onload = function () {
					if (this.imageCache[url]) {
						this.imageCache[url].resolve(image);
					}
				}.bind(this);
				image.onerror = function () {
					if (this.imageCache[url]) {
						this.imageCache[url].reject(url);
					}
				}.bind(this);
				if (mimeType === 'image/svg+xml') {
					image.src = this._getSVG(url);
				} else {
					image.src = url;
				}
			}
			return this.imageCache[url];
		},

		/**
		 * Shows a new image in the slideshow and preloads the next in the list
		 *
		 * @param {number} current
		 * @param {Object} next
		 */
		next: function (current, next) {
			this.show(current).then(function () {
				// Preloads the next image in the list
				this.loadImage(next);
			}.bind(this));
		},

		/**
		 * Stops the slideshow
		 */
		stop: function () {
			this.active = false;
			this.images = null;
			this._hideImage();
			if (this.onStop) {
				this.onStop();
			}
		},

		/**
		 * Sends the current image as a download
		 *
		 * @param {string} downloadUrl
		 *
		 * @returns {boolean}
		 */
		getImageDownload: function (downloadUrl) {
			OC.redirect(downloadUrl);
			return false;
		},

		/**
		 * Changes the colour of the background of the image
		 */
		toggleBackground: function () {
			var toHex = function (x) {
				return ("0" + parseInt(x).toString(16)).slice(-2);
			};
			var container = this.zoomablePreviewContainer.children('img');
			var rgb = container.css('background-color').match(/\d+/g);
			var hex = "#" + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
			var $border = 30 / window.devicePixelRatio;

			// Grey #363636
			if (hex === "#000000") {
				container.css('background-color', '#FFF');
				if (this.backgroundToggle === true) {
					container.css('outline', $border + 'px solid #FFF');
				}
			} else {
				container.css('background-color', '#000');
				if (this.backgroundToggle === true) {
					container.css('outline', $border + 'px solid #000');
				}
			}
		},

		/**
		 * Shows an error notification
		 *
		 * @param {string} message
		 */
		showErrorNotification: function (message) {
			if ($.isEmptyObject(message)) {
				message = t('gallery',
					'Error! Could not generate a preview of this file.
' +
					'Please go to the next slide while we remove this image from the slideshow');
			}
			this.container.find('.notification').html(message);
			this.container.find('.notification').show();
			this.controls.hideButton('.changeBackground');
		},

		/**
		 * Hides the error notification
		 */
		hideErrorNotification: function () {
			this.container.find('.notification').hide();
			this.container.find('.notification').html('');
		},

		/**
		 * Removes a specific button from the interface
		 *
		 * @param button
		 */
		removeButton: function (button) {
			this.controls.removeButton(button);
		},

		/**
		 * Deletes an image from the slideshow
		 *
		 * @param {object} image
		 * @param {number} currentIndex
		 */
		deleteImage: function (image, currentIndex) {
			// These are Gallery specific commands to be replaced
			// which should sit somewhere else
			if (!window.galleryFileAction) {
				delete Gallery.imageMap[image.path];
				delete Thumbnails.map[image.file];
				Gallery.albumMap[Gallery.currentAlbum].images.splice(currentIndex, 1);
				Gallery.view.init(Gallery.currentAlbum);
			}
		},

		/**
		 * Automatically fades the controls after 3 seconds
		 *
		 * @private
		 */
		_initControlsAutoFader: function () {
			var inactiveCallback = function () {
				this.container.addClass('inactive');
			}.bind(this);
			var inactiveTimeout = setTimeout(inactiveCallback, 3000);

			this.container.on('mousemove touchstart', function () {
				this.container.removeClass('inactive');
				clearTimeout(inactiveTimeout);
				inactiveTimeout = setTimeout(inactiveCallback, 3000);
			}.bind(this));
		},

		/**
		 * Simplest way to detect if image is transparent.
		 *
		 * That's very inaccurate since it doesn't include images which support transparency
		 *
		 * @param mimeType
		 * @returns {boolean}
		 * @private
		 */
		_isTransparent: function (mimeType) {
			return !(mimeType === 'image/jpeg'
				|| mimeType === 'image/x-dcraw'
				|| mimeType === 'application/font-sfnt'
				|| mimeType === 'application/x-font'
			);
		},

		/**
		 * Changes the browser Url, based on the current image
		 *
		 * @param {string} path
		 * @private
		 */
		_setUrl: function (path) {
			if (history && history.replaceState) {
				history.replaceState('', '', '#' + encodeURI(path));
			}
		},

		/**
		 * Hides the current image (before loading the next)
		 *
		 * @private
		 */
		_hideImage: function () {
			this.zoomablePreviewContainer.empty();
			this.controls.hideActionButtons();
		},

		/**
		 * Retrieves an SVG
		 *
		 * An SVG can't be simply attached to a src attribute like a bitmap image
		 *
		 * @param {string} source
		 *
		 * @returns {*}
		 * @private
		 */
		_getSVG: function (source) {
			var svgPreview = null;
			// DOMPurify only works with IE10+ and we load SVGs in the IMG tag
			if (window.btoa &&
				document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image",
					"1.1")) {
				var xmlHttp = new XMLHttpRequest();
				xmlHttp.open("GET", source, false);
				xmlHttp.send(null);
				if (xmlHttp.status === 200) {
					var pureSvg = DOMPurify.sanitize(xmlHttp.responseText, {ADD_TAGS: ['filter']});
					// Remove XML comment garbage left in the purified data
					var badTag = pureSvg.indexOf(']>');
					var fixedPureSvg = pureSvg.substring(badTag < 0 ? 0 : 5, pureSvg.length);
					svgPreview = "data:image/svg+xml;base64," + window.btoa(fixedPureSvg);
				}
			}

			return svgPreview;
		},

		/**
		 * Retrieves the slideshow's template
		 *
		 * @returns {*}
		 * @private
		 */
		_getSlideshowTemplate: function () {
			var defer = $.Deferred();
			if (!this.$slideshowTemplate) {
				var self = this;
				var url = OC.generateUrl('apps/gallery/slideshow', null);
				$.get(url, function (tmpl) {
						var template = $(tmpl);
						var tmplButton;
						var buttonsArray = [
							{
								el: '.next',
								trans: t('gallery', 'Next')
							},
							{
								el: '.play',
								trans: t('gallery', 'Play')
							},
							{
								el: '.pause',
								trans: t('gallery', 'Pause')
							},
							{
								el: '.previous',
								trans: t('gallery', 'Previous')
							},
							{
								el: '.exit',
								trans: t('gallery', 'Close')
							},
							{
								el: '.downloadImage',
								trans: t('gallery', 'Download'),
								toolTip: true
							},
							{
								el: '.changeBackground',
								trans: t('gallery', 'Toggle background'),
								toolTip: true
							},
							{
								el: '.deleteImage',
								trans: t('gallery', 'Delete'),
								toolTip: true
							}
						];
						for (var i = 0; i < buttonsArray.length; i++) {
							var button = buttonsArray[i];

							tmplButton = template.find(button.el);
							tmplButton.val(button.trans);
							if (button.toolTip) {
								tmplButton.attr("title", button.trans);
							}
						}
						self.$slideshowTemplate = template;
						defer.resolve(self.$slideshowTemplate);
					})
					.fail(function () {
						defer.reject();
					});
			} else {
				defer.resolve(this.$slideshowTemplate);
			}
			return defer.promise();
		}
	};

	window.SlideShow = SlideShow;
})(jQuery, OC, OCA, t);
<commit_msg>Hide icon-loading-dark after loading the image<commit_after>/* global DOMPurify */
(function ($, OC, OCA, t) {
	"use strict";
	/**
	 * Slideshow featuring zooming
	 *
	 * @constructor
	 */
	var SlideShow = function () {
	};

	SlideShow.prototype = {
		slideshowTemplate: null,
		container: null,
		zoomablePreviewContainer: null,
		controls: null,
		imageCache: {},
		/** {Image} */
		currentImage: null,
		errorLoadingImage: false,
		onStop: null,
		zoomablePreview: null,
		active: false,
		backgroundToggle: false,

		/**
		 * Initialises the slideshow
		 *
		 * @param {boolean} autoPlay
		 * @param {number} interval
		 * @param {Array} features
		 */
		init: function (autoPlay, interval, features) {
			if (features.indexOf('background_colour_toggle') > -1) {
				this.backgroundToggle = true;
			}

			return $.when(this._getSlideshowTemplate()).then(function ($tmpl) {
				// Move the slideshow outside the content so we can hide the content
				$('body').append($tmpl);
				this.container = $('#slideshow');
				this.zoomablePreviewContainer = this.container.find('.bigshotContainer');
				this.zoomablePreview = new SlideShow.ZoomablePreview(this.container);
				this.controls =
					new SlideShow.Controls(
						this,
						this.container,
						this.zoomablePreview,
						interval,
						features);
				this.controls.init();

				this._initControlsAutoFader();

				// Replace all Owncloud svg images with png images for ancient browsers
				if (!OC.Util.hasSVGSupport()) {
					OC.Util.replaceSVG(this.$el);
				}

				// Only modern browsers can manipulate history
				if (history && history.pushState) {
					// Stop the slideshow when backing out.
					$(window).bind('popstate.slideshow', function () {
						if (this.active === true) {
							this.active = false;
							this.controls.stop();
						}
					}.bind(this));
				}
			}.bind(this)).fail(function () {
				OC.Notification.show(t('core', 'Error loading slideshow template'));
			});
		},

		/**
		 * Refreshes the slideshow's data
		 *
		 * @param {{name:string, url: string, path: string, fallBack: string}[]} images
		 * @param {boolean} autoPlay
		 */
		setImages: function (images, autoPlay) {
			this._hideImage();
			this.images = images;
			this.controls.update(images, autoPlay);
		},

		/**
		 * Launches the slideshow
		 *
		 * @param {number} index
		 *
		 * @returns {*}
		 */
		show: function (index) {
			this.hideErrorNotification();
			this.active = true;
			this.container.show();
			this.container.css('background-position', 'center');
			this._hideImage();
			this.container.find('.icon-loading-dark').show();
			var currentImageId = index;
			return this.loadImage(this.images[index]).then(function (img) {
				this.container.css('background-position', '-10000px 0');

				// check if we moved along while we were loading
				if (currentImageId === index) {
					var image = this.images[index];
					var transparent = this._isTransparent(image.mimeType);
					this.controls.showActionButtons(transparent, Gallery.token, image.permissions);
					this.errorLoadingImage = false;
					this.currentImage = img;

					var backgroundColour = '#000';
					if (transparent) {
						backgroundColour = '#fff';
					}
					img.setAttribute('alt', image.name);
					$(img).css('position', 'absolute');
					$(img).css('background-color', backgroundColour);
					if (transparent && this.backgroundToggle === true) {
						var $border = 30 / window.devicePixelRatio;
						$(img).css('outline', $border + 'px solid ' + backgroundColour);
					}

					this.zoomablePreview.startBigshot(img, this.currentImage, image.mimeType);

					this._setUrl(image.path);
					this.controls.show(currentImageId);
					this.container.find('.icon-loading-dark').hide();
				}
			}.bind(this), function () {
				// Don't do anything if the user has moved along while we were loading as it would
				// mess up the index
				if (currentImageId === index) {
					this.errorLoadingImage = true;
					this.showErrorNotification(null);
					this._setUrl(this.images[index].path);
					this.images.splice(index, 1);
					this.controls.updateControls(this.images, this.errorLoadingImage);
				}
			}.bind(this));
		},

		/**
		 * Loads the image to show in the slideshow and preloads the next one
		 *
		 * @param {Object} preview
		 *
		 * @returns {*}
		 */
		loadImage: function (preview) {
			var url = preview.url;
			var mimeType = preview.mimeType;

			if (!this.imageCache[url]) {
				this.imageCache[url] = new $.Deferred();
				var image = new Image();

				image.onload = function () {
					if (this.imageCache[url]) {
						this.imageCache[url].resolve(image);
					}
				}.bind(this);
				image.onerror = function () {
					if (this.imageCache[url]) {
						this.imageCache[url].reject(url);
					}
				}.bind(this);
				if (mimeType === 'image/svg+xml') {
					image.src = this._getSVG(url);
				} else {
					image.src = url;
				}
			}
			return this.imageCache[url];
		},

		/**
		 * Shows a new image in the slideshow and preloads the next in the list
		 *
		 * @param {number} current
		 * @param {Object} next
		 */
		next: function (current, next) {
			this.show(current).then(function () {
				// Preloads the next image in the list
				this.loadImage(next);
			}.bind(this));
		},

		/**
		 * Stops the slideshow
		 */
		stop: function () {
			this.active = false;
			this.images = null;
			this._hideImage();
			if (this.onStop) {
				this.onStop();
			}
		},

		/**
		 * Sends the current image as a download
		 *
		 * @param {string} downloadUrl
		 *
		 * @returns {boolean}
		 */
		getImageDownload: function (downloadUrl) {
			OC.redirect(downloadUrl);
			return false;
		},

		/**
		 * Changes the colour of the background of the image
		 */
		toggleBackground: function () {
			var toHex = function (x) {
				return ("0" + parseInt(x).toString(16)).slice(-2);
			};
			var container = this.zoomablePreviewContainer.children('img');
			var rgb = container.css('background-color').match(/\d+/g);
			var hex = "#" + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
			var $border = 30 / window.devicePixelRatio;

			// Grey #363636
			if (hex === "#000000") {
				container.css('background-color', '#FFF');
				if (this.backgroundToggle === true) {
					container.css('outline', $border + 'px solid #FFF');
				}
			} else {
				container.css('background-color', '#000');
				if (this.backgroundToggle === true) {
					container.css('outline', $border + 'px solid #000');
				}
			}
		},

		/**
		 * Shows an error notification
		 *
		 * @param {string} message
		 */
		showErrorNotification: function (message) {
			if ($.isEmptyObject(message)) {
				message = t('gallery',
					'Error! Could not generate a preview of this file.
' +
					'Please go to the next slide while we remove this image from the slideshow');
			}
			this.container.find('.notification').html(message);
			this.container.find('.notification').show();
			this.controls.hideButton('.changeBackground');
		},

		/**
		 * Hides the error notification
		 */
		hideErrorNotification: function () {
			this.container.find('.notification').hide();
			this.container.find('.notification').html('');
		},

		/**
		 * Removes a specific button from the interface
		 *
		 * @param button
		 */
		removeButton: function (button) {
			this.controls.removeButton(button);
		},

		/**
		 * Deletes an image from the slideshow
		 *
		 * @param {object} image
		 * @param {number} currentIndex
		 */
		deleteImage: function (image, currentIndex) {
			// These are Gallery specific commands to be replaced
			// which should sit somewhere else
			if (!window.galleryFileAction) {
				delete Gallery.imageMap[image.path];
				delete Thumbnails.map[image.file];
				Gallery.albumMap[Gallery.currentAlbum].images.splice(currentIndex, 1);
				Gallery.view.init(Gallery.currentAlbum);
			}
		},

		/**
		 * Automatically fades the controls after 3 seconds
		 *
		 * @private
		 */
		_initControlsAutoFader: function () {
			var inactiveCallback = function () {
				this.container.addClass('inactive');
			}.bind(this);
			var inactiveTimeout = setTimeout(inactiveCallback, 3000);

			this.container.on('mousemove touchstart', function () {
				this.container.removeClass('inactive');
				clearTimeout(inactiveTimeout);
				inactiveTimeout = setTimeout(inactiveCallback, 3000);
			}.bind(this));
		},

		/**
		 * Simplest way to detect if image is transparent.
		 *
		 * That's very inaccurate since it doesn't include images which support transparency
		 *
		 * @param mimeType
		 * @returns {boolean}
		 * @private
		 */
		_isTransparent: function (mimeType) {
			return !(mimeType === 'image/jpeg'
				|| mimeType === 'image/x-dcraw'
				|| mimeType === 'application/font-sfnt'
				|| mimeType === 'application/x-font'
			);
		},

		/**
		 * Changes the browser Url, based on the current image
		 *
		 * @param {string} path
		 * @private
		 */
		_setUrl: function (path) {
			if (history && history.replaceState) {
				history.replaceState('', '', '#' + encodeURI(path));
			}
		},

		/**
		 * Hides the current image (before loading the next)
		 *
		 * @private
		 */
		_hideImage: function () {
			this.zoomablePreviewContainer.empty();
			this.controls.hideActionButtons();
		},

		/**
		 * Retrieves an SVG
		 *
		 * An SVG can't be simply attached to a src attribute like a bitmap image
		 *
		 * @param {string} source
		 *
		 * @returns {*}
		 * @private
		 */
		_getSVG: function (source) {
			var svgPreview = null;
			// DOMPurify only works with IE10+ and we load SVGs in the IMG tag
			if (window.btoa &&
				document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image",
					"1.1")) {
				var xmlHttp = new XMLHttpRequest();
				xmlHttp.open("GET", source, false);
				xmlHttp.send(null);
				if (xmlHttp.status === 200) {
					var pureSvg = DOMPurify.sanitize(xmlHttp.responseText, {ADD_TAGS: ['filter']});
					// Remove XML comment garbage left in the purified data
					var badTag = pureSvg.indexOf(']>');
					var fixedPureSvg = pureSvg.substring(badTag < 0 ? 0 : 5, pureSvg.length);
					svgPreview = "data:image/svg+xml;base64," + window.btoa(fixedPureSvg);
				}
			}

			return svgPreview;
		},

		/**
		 * Retrieves the slideshow's template
		 *
		 * @returns {*}
		 * @private
		 */
		_getSlideshowTemplate: function () {
			var defer = $.Deferred();
			if (!this.$slideshowTemplate) {
				var self = this;
				var url = OC.generateUrl('apps/gallery/slideshow', null);
				$.get(url, function (tmpl) {
						var template = $(tmpl);
						var tmplButton;
						var buttonsArray = [
							{
								el: '.next',
								trans: t('gallery', 'Next')
							},
							{
								el: '.play',
								trans: t('gallery', 'Play')
							},
							{
								el: '.pause',
								trans: t('gallery', 'Pause')
							},
							{
								el: '.previous',
								trans: t('gallery', 'Previous')
							},
							{
								el: '.exit',
								trans: t('gallery', 'Close')
							},
							{
								el: '.downloadImage',
								trans: t('gallery', 'Download'),
								toolTip: true
							},
							{
								el: '.changeBackground',
								trans: t('gallery', 'Toggle background'),
								toolTip: true
							},
							{
								el: '.deleteImage',
								trans: t('gallery', 'Delete'),
								toolTip: true
							}
						];
						for (var i = 0; i < buttonsArray.length; i++) {
							var button = buttonsArray[i];

							tmplButton = template.find(button.el);
							tmplButton.val(button.trans);
							if (button.toolTip) {
								tmplButton.attr("title", button.trans);
							}
						}
						self.$slideshowTemplate = template;
						defer.resolve(self.$slideshowTemplate);
					})
					.fail(function () {
						defer.reject();
					});
			} else {
				defer.resolve(this.$slideshowTemplate);
			}
			return defer.promise();
		}
	};

	window.SlideShow = SlideShow;
})(jQuery, OC, OCA, t);
<|endoftext|>


<commit_before>define(['jquery', 'lib/jquery.transit.min'], function() {

	function Robot(grid) {
		this.position = { x:0, y:0 };
		this.grid = grid;
		this.grid.robot = this;
		this.$grid = this.grid.$grid;
		this.addListeners();
		this.directions = ['up', 'right', 'down', 'left'];
		this.direction = 'up';
		this.itemCount = 0;
		this.validCommands = ['up', 'down', 'left', 'right', 'move'];
		this.executeCommandsInterval = null;
		this.isGoalReached = false;
	}

	Robot.prototype.addListeners = function() {
		$(window).on('grid.refresh', $.proxy(function() {
			this.init();
		}, this));
	};

	Robot.prototype.init = function() {
		var cell = this.getCell();
		this.$grid.find('.robot').remove();
		cell.append('');
		cell.addClass('active');
		this.collectItem();
	};

	Robot.prototype.move = function() {

		var newPosition = this.getPositionForDirection(this.direction);
		var $cell = this.getCell();

		if (this.isGoalReached) {
			return this.makeMove(newPosition);
		}

		if (this.doesCellContainGoalInDirection($cell, this.direction)) {
			this.goalReached();
		}

		if (!this.canMoveToPosition(newPosition, this.direction)) return;

		this.makeMove(newPosition);
	};

	Robot.prototype.makeMove = function(newPosition) {
		this.position = newPosition;
		var transition = this.getTransitionForDirection(this.direction);
		$('.robot').transition(transition, $.proxy(function() {
			this.init();
		}, this));
	}

	Robot.prototype.up = function() {
		if (!this.shouldTurnToDirection('up')) return;
		var direction = 'up';
		this.turnToDirection(direction);
	};

	Robot.prototype.down = function() {
		if (!this.shouldTurnToDirection('down')) return;
		var direction = 'down';
		this.turnToDirection(direction);
	};

	Robot.prototype.left = function() {
		if (!this.shouldTurnToDirection('left')) return;
		var direction = 'left';
		this.turnToDirection(direction);
	};

	Robot.prototype.right = function() {
		if (!this.shouldTurnToDirection('right')) return;
		var direction = 'right';
		this.turnToDirection(direction);
	};

	Robot.prototype.dance = function() {
		this.executeCommands([
			'up',
			'down',
			'left',
			'right',
			'left',
			'right',
			'up',
			'left',
			'up',
			'left',
			'right',
			'move',
			'move'
		]);
	};

	Robot.prototype.goalReached = function() {
		$(window).trigger('robot.wins');
		this.isGoalReached = true;
		this.dance();
	};

	Robot.prototype.getCell = function() {
		return this.grid.getCellForPosition(this.position);
	};

	Robot.prototype.shouldTurnToDirection = function(direction) {
		return (direction !== this.direction);
	};

	Robot.prototype.turnToDirection = function(direction) {
		var currentDirection = this.direction;
		var degrees = this.getDegreesToFromDirection(direction, currentDirection);
		$('.robot').transition({ rotate:degrees + 'deg' }, $.proxy(function() {
			this.direction = direction;
			this.init();
		}, this));
	};

	Robot.prototype.getDegreesToFromDirection = function(toDirection, fromDirection) {
		var toIndex = this.directions.indexOf(toDirection);
		var fromIndex = this.directions.indexOf(fromDirection);

		if (fromDirection === 'left') {
			return ((fromIndex + toIndex) - 2) * 90;
		}

		if (fromDirection === 'right') {
			var turns = toIndex - fromIndex;
			turns = (turns < 0) ? 3 : turns;
			return turns * 90;
		}

		if (toIndex > fromIndex) {
			return (toIndex - fromIndex)  * 90;
		} else {
			return (fromIndex + toIndex)  * 90;
		}
	}

	Robot.prototype.getTransitionForDirection = function(direction) {
		if (this.direction === 'up') {
			return { x:0, y:'-=65' };
		} else if (this.direction === 'down') {
			return { x:0, y:'+=65' };
		} else if (this.direction === 'left') {
			return { x:'-=65', y:0 };
		} else if (this.direction === 'right') {
			return { x:'+=65', y:0 };
		}
	};

	Robot.prototype.getPositionForDirection = function(direction) {

		var newPosition = $.extend({}, this.position);

		if (direction === 'up') {
			newPosition.x--;
		}

		if (direction === 'down') {
			newPosition.x++;
		}

		if (direction === 'left') {
			newPosition.y--;
		}

		if (direction === 'right') {
			newPosition.y++;
		}

		return newPosition;
	};

	Robot.prototype.canMoveToPosition = function(position, direction) {

		console.log(this.grid.dimensions);
		console.log(position);
		console.log(direction);
		if (!this.grid.isPositionWithinBounds(position)) return false;

		var cell = this.getCell();
		if (!this.checkCellInDirection(cell, direction)) {
			return false;
		}

		var newCell = this.grid.getCellForPosition(position);
		var oppositeDirection = this.getOppositeDirection(direction);
		if (!this.checkCellInDirection(newCell, oppositeDirection)) {
			return false;
		}

		return true;
	}

	Robot.prototype.checkCellInDirection = function(cell, direction) {
		return (this.doesCellContainGoalInDirection(cell, direction)
				|| !this.doesCellContainWallInDirection(cell, direction));
	};

	Robot.prototype.doesCellContainWallInDirection = function(cell, direction) {
		return (cell.hasClass('wall-' + direction));
	};

	Robot.prototype.doesCellContainGoalInDirection = function(cell, direction) {
		return cell.find('span.wall').hasClass('goal');
	};

	Robot.prototype.getOppositeDirection = function(direction) {
		var index = this.directions.indexOf(direction);

		if (index > -1) {
			var oppositeDirectionIndex = (index > 1) ? index - 2 : index + 2;
			return this.directions[oppositeDirectionIndex];
		}
	};

	Robot.prototype.collectItem = function() {
		this.itemCount++;
		var $cell = this.getCell();
		$cell.find('span.item').remove();
	};

	Robot.prototype.executeCommands = function(commands) {

		clearInterval(this.executeCommandsInterval);
		this.executeCommandsInterval = setInterval($.proxy(function() {
			if (commands.length === 0) {
				clearInterval(this.executeCommandsInterval);
				$(window).trigger('commands.complete');
				return;
			}
			var command = commands.shift();
			if (this.validCommands.indexOf(command) > -1) {
				this[command]();
			}
		}, this), 500);
	};

	return Robot;
});
<commit_msg>Amending dance moves<commit_after>define(['jquery', 'lib/jquery.transit.min'], function() {

	function Robot(grid) {
		this.position = { x:0, y:0 };
		this.grid = grid;
		this.grid.robot = this;
		this.$grid = this.grid.$grid;
		this.addListeners();
		this.directions = ['up', 'right', 'down', 'left'];
		this.direction = 'up';
		this.itemCount = 0;
		this.validCommands = ['up', 'down', 'left', 'right', 'move'];
		this.executeCommandsInterval = null;
		this.isGoalReached = false;
	}

	Robot.prototype.addListeners = function() {
		$(window).on('grid.refresh', $.proxy(function() {
			this.init();
		}, this));
	};

	Robot.prototype.init = function() {
		var cell = this.getCell();
		this.$grid.find('.robot').remove();
		cell.append('');
		cell.addClass('active');
		this.collectItem();
	};

	Robot.prototype.move = function() {

		var newPosition = this.getPositionForDirection(this.direction);
		var $cell = this.getCell();

		if (this.isGoalReached) {
			return this.makeMove(newPosition);
		}

		if (this.doesCellContainGoalInDirection($cell, this.direction)) {
			this.goalReached();
		}

		if (!this.canMoveToPosition(newPosition, this.direction)) return;

		this.makeMove(newPosition);
	};

	Robot.prototype.makeMove = function(newPosition) {
		this.position = newPosition;
		var transition = this.getTransitionForDirection(this.direction);
		$('.robot').transition(transition, $.proxy(function() {
			this.init();
		}, this));
	}

	Robot.prototype.up = function() {
		if (!this.shouldTurnToDirection('up')) return;
		var direction = 'up';
		this.turnToDirection(direction);
	};

	Robot.prototype.down = function() {
		if (!this.shouldTurnToDirection('down')) return;
		var direction = 'down';
		this.turnToDirection(direction);
	};

	Robot.prototype.left = function() {
		if (!this.shouldTurnToDirection('left')) return;
		var direction = 'left';
		this.turnToDirection(direction);
	};

	Robot.prototype.right = function() {
		if (!this.shouldTurnToDirection('right')) return;
		var direction = 'right';
		this.turnToDirection(direction);
	};

	Robot.prototype.dance = function() {
		this.executeCommands([
			'up',
			'down',
			'left',
			'right',
			'left',
			'right',
			'up',
			'left',
			'right',
			'move',
			'move'
		]);
	};

	Robot.prototype.goalReached = function() {
		$(window).trigger('robot.wins');
		this.isGoalReached = true;
		this.dance();
	};

	Robot.prototype.getCell = function() {
		return this.grid.getCellForPosition(this.position);
	};

	Robot.prototype.shouldTurnToDirection = function(direction) {
		return (direction !== this.direction);
	};

	Robot.prototype.turnToDirection = function(direction) {
		var currentDirection = this.direction;
		var degrees = this.getDegreesToFromDirection(direction, currentDirection);
		$('.robot').transition({ rotate:degrees + 'deg' }, $.proxy(function() {
			this.direction = direction;
			this.init();
		}, this));
	};

	Robot.prototype.getDegreesToFromDirection = function(toDirection, fromDirection) {
		var toIndex = this.directions.indexOf(toDirection);
		var fromIndex = this.directions.indexOf(fromDirection);

		if (fromDirection === 'left') {
			return ((fromIndex + toIndex) - 2) * 90;
		}

		if (fromDirection === 'right') {
			var turns = toIndex - fromIndex;
			turns = (turns < 0) ? 3 : turns;
			return turns * 90;
		}

		if (toIndex > fromIndex) {
			return (toIndex - fromIndex)  * 90;
		} else {
			return (fromIndex + toIndex)  * 90;
		}
	}

	Robot.prototype.getTransitionForDirection = function(direction) {
		if (this.direction === 'up') {
			return { x:0, y:'-=65' };
		} else if (this.direction === 'down') {
			return { x:0, y:'+=65' };
		} else if (this.direction === 'left') {
			return { x:'-=65', y:0 };
		} else if (this.direction === 'right') {
			return { x:'+=65', y:0 };
		}
	};

	Robot.prototype.getPositionForDirection = function(direction) {

		var newPosition = $.extend({}, this.position);

		if (direction === 'up') {
			newPosition.x--;
		}

		if (direction === 'down') {
			newPosition.x++;
		}

		if (direction === 'left') {
			newPosition.y--;
		}

		if (direction === 'right') {
			newPosition.y++;
		}

		return newPosition;
	};

	Robot.prototype.canMoveToPosition = function(position, direction) {

		console.log(this.grid.dimensions);
		console.log(position);
		console.log(direction);
		if (!this.grid.isPositionWithinBounds(position)) return false;

		var cell = this.getCell();
		if (!this.checkCellInDirection(cell, direction)) {
			return false;
		}

		var newCell = this.grid.getCellForPosition(position);
		var oppositeDirection = this.getOppositeDirection(direction);
		if (!this.checkCellInDirection(newCell, oppositeDirection)) {
			return false;
		}

		return true;
	}

	Robot.prototype.checkCellInDirection = function(cell, direction) {
		return (this.doesCellContainGoalInDirection(cell, direction)
				|| !this.doesCellContainWallInDirection(cell, direction));
	};

	Robot.prototype.doesCellContainWallInDirection = function(cell, direction) {
		return (cell.hasClass('wall-' + direction));
	};

	Robot.prototype.doesCellContainGoalInDirection = function(cell, direction) {
		return cell.find('span.wall').hasClass('goal');
	};

	Robot.prototype.getOppositeDirection = function(direction) {
		var index = this.directions.indexOf(direction);

		if (index > -1) {
			var oppositeDirectionIndex = (index > 1) ? index - 2 : index + 2;
			return this.directions[oppositeDirectionIndex];
		}
	};

	Robot.prototype.collectItem = function() {
		this.itemCount++;
		var $cell = this.getCell();
		$cell.find('span.item').remove();
	};

	Robot.prototype.executeCommands = function(commands) {

		clearInterval(this.executeCommandsInterval);
		this.executeCommandsInterval = setInterval($.proxy(function() {
			if (commands.length === 0) {
				clearInterval(this.executeCommandsInterval);
				$(window).trigger('commands.complete');
				return;
			}
			var command = commands.shift();
			if (this.validCommands.indexOf(command) > -1) {
				this[command]();
			}
		}, this), 500);
	};

	return Robot;
});
<|endoftext|>


<commit_before>var Stacksvis = function (el, options) {
    var defaults = {
        vertical_padding: 30,
        horizontal_padding: 30,
        bar_height: 20,
        bar_width: 5,
        cluster_legend_height: 20,
        highlight_bar_height: 7,
        label_width: 100,
        label_fontsize: 12,
        highlight_fill: "red",
        all_columns: "All Columns",
        spacing: {
            column: 2,
            row: 10,
            cluster: 10
        },
        colorscales: {},
        selectors: {}
    };

    return {
        $el: (el),
        options: _.extend(defaults, options),
        dimensions: {
            row: [],
            column: [],
            clusterBy: undefined
        },
        data: [],
        columns_by_cluster: {},
        svg_elements: {},

        draw: function (inputs) {
            this.$el.empty();

            inputs = inputs || {};

            if (_.has(inputs, "dimensions")) _.extend(this.dimensions, inputs.dimensions);
            if (_.has(inputs, "data")) this.data = inputs.data || [];

            this._cluster_data();
            this._render_html();
            this._render_dimensions();
            this._render_svg();
            this._refresh_spacing();
            this._refresh_fills();
        },

        spacing: function (optns) {
            if (optns) {
                _.extend(this.options.spacing, optns);
                this._refresh_spacing();
            }
        },

        // private methods
        _cluster_data: function () {
            var unsorted_columns = _.map(this.dimensions.column, function (column_name, col_idx) {
                var column_cluster = this.options.all_columns;
                if (this.dimensions.clusterBy) {
                    var clusterIdx = this.dimensions.row.indexOf(this.dimensions.clusterBy);
                    column_cluster = this.data[clusterIdx][col_idx];
                }

                var column = { "name": column_name.trim(), "cluster": column_cluster, "values": [] };

                _.each(_.without(this.dimensions.row, this.dimensions.clusterBy), function (row_label, row_idx) {
                    column.values.push(this.data[row_idx][col_idx]);
                }, this);
                return column;
            }, this);

            var sorted_columns = _.sortBy(unsorted_columns, "values");
            var grouped_columns = _.groupBy(sorted_columns, "cluster");

            this.columns_by_cluster = {};
            _.each(grouped_columns, function (values, key) {
                this.columns_by_cluster[key] = _.pluck(values, "name");
            }, this);
        },

        _render_html: function () {
            this.$el.append("

");
//            this.$el.append("

");
//            this.$el.append("

");
            this.$el.append("

");
        },

        _render_dimensions: function () {
            var _append_labels = function (container, labels) {
                $(container).empty();
                _.each(labels, function (label) {
                    $(container).append("
" + label + "
")
                });
            };
//            _append_labels(this.$el.find(".stacksvis-cluster"), _.keys(this.columns_by_cluster));
//            _append_labels(this.$el.find(".stacksvis-column"), this.dimensions.column);
            _append_labels(this.$el.find(".stacksvis-row"), _.without(this.dimensions.row, this.dimensions.clusterBy));
            this.$el.trigger("render-dimensions");
        },

        _render_svg: function () {
            var visEl = this.$el.find(".stacksvis-heatmap").empty();
            var plotWidth = this._plot_width();
            var plotHeight = this._plot_height();

            this.svg_elements.svg = d3.select(visEl[0])
                .append("svg")
                .attr("width", plotWidth + (2 * this.options.horizontal_padding))
                .attr("height", plotHeight + (2 * this.options.vertical_padding));

            var rowlabels = this.dimensions.row;
            var clusterIdx = (this.dimensions.clusterBy) ? this.dimensions.row.indexOf(this.dimensions.clusterBy) : -1;

            var clusters = _.map(_.keys(this.columns_by_cluster), function (clusterlabel) {
                var columns = this.columns_by_cluster[clusterlabel] || [];
                return {
                    "label": clusterlabel,
                    "values": _.map(columns, function (label, idx) {
                        return {
                            "label": label,
                            "values": _.compact(_.map(this.data, function (row, rowidx) {
                                if (rowidx == clusterIdx) return null;

                                return {
                                    "label": label + "\n" + rowlabels[rowidx] + "\n" + row[idx],
                                    "values": row[idx]
                                };
                            }))
                        }
                    }, this)
                }
            }, this);

            this.svg_elements.g_cluster = this.svg_elements.svg.selectAll("g.cluster")
                .data(clusters).enter().insert("g").attr("class", "cluster");

            this.svg_elements.g_column = this.svg_elements.g_cluster.selectAll("g.column")
                .data(function (d) {
                    return d.values;
                })
                .enter().append("g").attr("class", "column");

            this.svg_elements.rect_bar = this.svg_elements.g_column.selectAll("rect.bar")
                .data(function (d) {
                    return d.values;
                })
                .enter()
                .append("rect")
                .attr("class", "bar")
                .append("title")
                .text(function (d) {
                    return d.label;
                });

            this.svg_elements.rect_highlight = this.svg_elements.g_column.selectAll("rect.highlight")
                .data(function (d) {
                    var mutated = [];
                    _.each(d.values, function (cellvalue, index) {
                        if (cellvalue !== undefined && cellvalue.isMutated) {
                            mutated.push(index);
                        }
                    });
                    return mutated;
                })
                .enter()
                .append("rect")
                .attr("class", "highlight")
                .style("fill", this.options.highlight_fill)
                .attr("width", this.options.bar_width)
                .attr("x", 0)
                .attr("height", this.options.highlight_bar_height);
        },

        _plot_height: function () {
            return (this.options.bar_height + this.options.spacing.row) * (this.dimensions.row.length + 1);
        },

        _plot_width: function () {
            var columnCounts = _.map(this.columns_by_cluster, function (columns) {
                return columns.length;
            });
            var numberOfColumns = _.reduce(columnCounts, function (memo, num) {
                return memo + num;
            }, 0);
            return ((this.options.bar_width + this.options.spacing.column) * numberOfColumns) + (this.options.spacing.cluster * _.keys(this.columns_by_cluster).length);
        },

        _refresh_spacing: function () {
            var ygap = this.options.bar_height + this.options.spacing.row;
            var xgap = this.options.bar_width + this.options.spacing.column;
            var cluster_spacing = this.options.spacing.cluster;

            // SVG
            this.svg_elements.g_column.selectAll("rect.bar")
                .attr("y", function (d, i) {
                    return i * ygap;
                })
                .attr("x", function (d, i, a) {
                    return a * xgap;
                })
                .attr("width", this.options.bar_width)
                .attr("height", this.options.bar_height);

            this.svg_elements.svg.selectAll("g.cluster")
                .attr("transform", function (d, i) {
                    return "translate(" + i * cluster_spacing + ",0)";
                });

            // ROW
            this.$el.find(".stacksvis-row").css({
                "margin-top": 0, //  (2 * this.options.cluster_legend_height),
                "width": this.options["label_width"],
                "display": this.options.disableRowLabels ? "none" : "block"
            });

            if (!this.options.disableRowLabels) {
                this.$el.find(".stacksvis-row").css({ "width": this.options["label_width"] });
                this.$el.find(".stacksvis-row li").css({
                    "font-size": this.options["label_fontsize"],
                    "padding-bottom": this.options.spacing.row
                });
//                this.$el.find(".stacksvis-row li").css({ "line-height": this.options.spacing.row });
            }

            // COLUMNS
            this.$el.find(".stacksvis-column").parent().css({
                "height": this.options.cluster_legend_height,
                "margin-left": this.options["label_width"]
            });
            this.$el.find(".stacksvis-column li").css({ "width": this.options.bar_width + this.options.spacing.column });

            // CLUSTERS
            this.$el.find(".stacksvis-cluster").parent().css({
                "height": this.options.cluster_legend_height,
                "margin-left": this.options["label_width"]
            });
            this.$el.find(".stacksvis-cluster li").css({ "padding-right": cluster_spacing });

            // MUTATIONS
            this.svg_elements.g_column.selectAll("rect.highlight")
                .attr("width", this.options.bar_width)
                .attr("height", this.options.highlight_bar_height);
        },

        _refresh_fills: function () {
            var colorscaleFns = _.map(this.dimensions.row, this._get_colorscale_fn, this);
            this.svg_elements.g_column.selectAll("rect.bar").style("fill", function (d, i) {
                return colorscaleFns[i](d.values) || "white";
            });

            this.svg_elements.g_column.selectAll("rect.highlight").style("fill", this.options.highlight_fill);

        },

        _get_colorscale_fn: function (rowlabel, rowidx) {
            var row_colorscale = (this.options.colorscales[rowlabel]) ? this.options.colorscales[rowlabel] : ["yellow", "green"];
            var values = this.data[rowidx];
            var colorrangeMap = d3.scale.linear().domain([0, values.length - 1]).range(row_colorscale);
            var colorrangeFn = d3.range(values.length).map(colorrangeMap);
            var colorscaleFn = d3.scale.ordinal().domain(values).range(colorrangeFn);
            return function (cell) {
                return colorscaleFn(values.indexOf(cell));
            };
        }
    }
};<commit_msg>simplified logic<commit_after>var Stacksvis = function (el, options) {
    var defaults = {
        vertical_padding: 30,
        horizontal_padding: 30,
        bar_height: 20,
        bar_width: 5,
        cluster_legend_height: 20,
        highlight_bar_height: 7,
        highlight_fill: "red",
        all_columns: "All Columns",
        spacing: {
            column: 0,
            row: 0,
            cluster: 0
        },
        colorscales: {},
        selectors: {}
    };

    return {
        $el: (el),
        options: _.extend(defaults, options),
        dimensions: {
            row: [],
            column: []
        },
        data: [],
        columns_by_cluster: {},
        svg_elements: {},

        draw: function (inputs) {
            this.$el.empty();

            inputs = inputs || {};

            if (_.has(inputs, "dimensions")) _.extend(this.dimensions, inputs.dimensions);
            if (_.has(inputs, "data")) this.data = inputs.data || [];

            this._cluster_data();
            this._render_svg();
        },

        // private methods
        _cluster_data: function () {
            var unsorted_columns = _.map(this.dimensions.column, function (column_name, col_idx) {
                var column = { "name": column_name.trim(), "cluster": this.options.all_columns, "values": [] };
                _.each(this.dimensions.row, function (row_label) {
                    column.values.push(this.data[column_name][row_label]);
                }, this);
                return column;
            }, this);

            var sorted_columns = _.sortBy(unsorted_columns, "values");
            var grouped_columns = _.groupBy(sorted_columns, "cluster");

            this.columns_by_cluster = {};
            _.each(grouped_columns, function (values, key) {
                this.columns_by_cluster[key] = _.pluck(values, "name");
            }, this);
        },

        _render_svg: function () {
//            var plotWidth = this._plot_width();
            var plotHeight = this._plot_height();
            var ygap = this.options.bar_height + this.options.spacing.row;
            var xgap = this.options.bar_width + this.options.spacing.column;

            _.each(this.options.row_labels, function (row_label, row_index) {
                var row_selector = this.options.row_selectors[row_label];
                var visEl = $(row_selector).empty();

                var svg = d3.select(visEl[0]).append("svg")
                    .attr("width", 300)
//                    .attr("width", plotWidth)
                    .attr("height", plotHeight);

                var clusters = _.map(_.keys(this.columns_by_cluster), function (clusterlabel) {
                    var columns = this.columns_by_cluster[clusterlabel] || [];
                    return {
                        "cluster_values": _.compact(_.map(columns, function (column) {
                            return this.data[column]
                        }, this))
                    }
                }, this);

                var g_cluster = svg.selectAll("g.cluster").data(clusters).enter().insert("g").attr("class", "cluster");

                var g_column = g_cluster.selectAll("g.column")
                    .data(function (d) {
                        return d.cluster_values;
                    })
                    .enter().append("g").attr("class", "column");

                g_column.selectAll("rect.bar")
                    .data(function (d) {
                        return [d[row_label]];
                    })
                    .enter()
                    .append("rect")
                    .attr("class", "bar")
                    .append("title")
                    .text(function (d) {
                        return d.label;
                    });

//                var rect_highlight = g_column.selectAll("rect.highlight")
//                    .data(function (d) {
//                        var mutated = [];
//                        _.each(_.values(d), function (cellvalue, index) {
//                            if (cellvalue !== undefined && cellvalue.isMutated) {
//                                mutated.push(index);
//                            }
//                        });
//                        return mutated;
//                    })
//                    .enter()
//                    .append("rect")
//                    .attr("class", "highlight")
//                    .style("fill", this.options.highlight_fill)
//                    .attr("width", this.options.bar_width)
//                    .attr("x", 0)
//                    .attr("height", this.options.highlight_bar_height);
//                g_column.selectAll("rect.highlight").style("fill", this.options.highlight_fill);

                // spacing
                g_column.selectAll("rect.bar")
                    .attr("y", function (d, i) {
                        return i * ygap;
                    })
                    .attr("x", function (d, i, a) {
                        return a * xgap;
                    })
                    .attr("width", this.options.bar_width)
                    .attr("height", this.options.bar_height);

                g_column.selectAll("rect.bar").style("fill", function (d, i) {
                    return d.colorscale;
                });
            }, this);
        },

        _plot_height: function () {
            return (this.options.bar_height + this.options.spacing.row);
        },

        _plot_width: function () {
            var columnCounts = _.map(this.columns_by_cluster, function (columns) {
                return columns.length;
            });
            var numberOfColumns = _.reduce(columnCounts, function (memo, num) {
                return memo + num;
            }, 0);
            return ((this.options.bar_width + this.options.spacing.column) * numberOfColumns) + (this.options.spacing.cluster * _.keys(this.columns_by_cluster).length);
        },

        _get_colorscale_fn: function (rowlabel, rowidx) {
            var row_colorscale = (this.options.colorscales[rowlabel]) ? this.options.colorscales[rowlabel] : ["yellow", "green"];
            var values = _.map(this.data[rowidx], function (item) {
                return item.value;
            });
            var colorrangeMap = d3.scale.linear().domain([0, values.length - 1]).range(row_colorscale);
            var colorrangeFn = d3.range(values.length).map(colorrangeMap);
            var colorscaleFn = d3.scale.ordinal().domain(values).range(colorrangeFn);
            return function (cell) {
                return colorscaleFn(values.indexOf(cell.value));
            };
        }
    }
};<|endoftext|>


<commit_before>var Stacksvis = function (el, options) {
    var defaults = {
        bar_height: 20,
        bar_width: 0.5,
        highlight_bar_height: 7,
        highlight_fill: "red",
        colorscales: {},
        row_selectors: {}
    };

    return {
        $el: (el),
        options: _.extend(defaults, options),
        data: [],
        columns_by_cluster: {},

        draw: function (inputs) {
            this.$el.empty();

            inputs = inputs || {};
            if (_.has(inputs, "data")) this.data = inputs.data || [];

            var plotWidth = this._plot_width();
            var bar_width = this.options.bar_width;
            if (plotWidth <= 300) {
                plotWidth = 300;
                bar_width = 4;
            }

            _.each(this.options.row_labels, function (row_label) {
                var row_selector = this.options.row_selectors[row_label];
                var visEl = $(row_selector).empty();

                var svg = d3.select(visEl[0]).append("svg")
                    .attr("width", plotWidth)
                    .attr("height", this.options.bar_height);

                var clusters = _.map(_.keys(this.options.columns_by_cluster), function (clusterlabel) {
                    var columns = this.options.columns_by_cluster[clusterlabel] || [];
                    return {
                        "cluster_values": _.compact(_.map(columns, function (column) {
                            return this.data[column]
                        }, this))
                    }
                }, this);

                var g_cluster = svg.selectAll("g.cluster").data(clusters).enter().insert("g").attr("class", "cluster");

                var g_column = g_cluster.selectAll("g.column")
                    .data(function (d) {
                        return d.cluster_values;
                    })
                    .enter().append("g").attr("class", "column");

                var i = 0;
                g_column.selectAll("rect.bar")
                    .data(function (d) {
                        return [d[row_label]];
                    })
                    .enter()
                    .append("rect")
                    .attr("class", "bar")
                    .append("title")
                    .text(function (d) {
                        return d.label + "\n" + i++;
                    });

//                var rect_highlight = g_column.selectAll("rect.highlight")
//                    .data(function (d) {
//                        var mutated = [];
//                        _.each(_.values(d), function (cellvalue, index) {
//                            if (cellvalue !== undefined && cellvalue.isMutated) {
//                                mutated.push(index);
//                            }
//                        });
//                        return mutated;
//                    })
//                    .enter()
//                    .append("rect")
//                    .attr("class", "highlight")
//                    .style("fill", this.options.highlight_fill)
//                    .attr("width", this.options.bar_width)
//                    .attr("x", 0)
//                    .attr("height", this.options.highlight_bar_height);
//                g_column.selectAll("rect.highlight").style("fill", this.options.highlight_fill);

                g_column.selectAll("rect.bar")
                    .attr("x", function (d, i, a) {
                        return a * bar_width;
                    })
                    .attr("width", bar_width)
                    .attr("height", this.options.bar_height);

                g_column.selectAll("rect.bar").style("fill", function (d, i) {
                    return d.colorscale;
                });
            }, this);
        },

        _plot_width: function () {
            var columnCounts = _.map(this.options.columns_by_cluster, function (columns) {
                return columns.length;
            });
            var numberOfColumns = _.reduce(columnCounts, function (memo, num) {
                return memo + num;
            }, 0);
            // TODO : Determine plot and bar width based on a scale
            return (this.options.bar_width * numberOfColumns);
        },

        _get_colorscale_fn: function (rowlabel, rowidx) {
            var row_colorscale = (this.options.colorscales[rowlabel]) ? this.options.colorscales[rowlabel] : ["yellow", "green"];
            var values = _.map(this.data[rowidx], function (item) {
                return item.value;
            });
            var colorrangeMap = d3.scale.linear().domain([0, values.length - 1]).range(row_colorscale);
            var colorrangeFn = d3.range(values.length).map(colorrangeMap);
            var colorscaleFn = d3.scale.ordinal().domain(values).range(colorrangeFn);
            return function (cell) {
                return colorscaleFn(values.indexOf(cell.value));
            };
        }
    }
};<commit_msg>refactoring highlight logic<commit_after>var Stacksvis = function (el, options) {
    var defaults = {
        bar_height: 20,
        bar_width: 0.5,
        colorscales: {},
        row_selectors: {},
        highlight: {
            bar_height: 7,
            fill: "red"
        }
    };

    return {
        $el: (el),
        options: _.extend(defaults, options),
        data: [],
        columns_by_cluster: {},

        draw: function (inputs) {
            this.$el.empty();

            inputs = inputs || {};
            if (_.has(inputs, "data")) this.data = inputs.data || [];

            var plotWidth = this._plot_width();
            var bar_width = this.options.bar_width;
            if (plotWidth <= 300) {
                plotWidth = 300;
                bar_width = 4;
            }

            _.each(this.options.row_labels, function (row_label) {
                var row_selector = this.options.row_selectors[row_label];
                var visEl = $(row_selector).empty();

                var svg = d3.select(visEl[0]).append("svg")
                    .attr("width", plotWidth)
                    .attr("height", this.options.bar_height);

                var clusters = _.map(_.keys(this.options.columns_by_cluster), function (clusterlabel) {
                    var columns = this.options.columns_by_cluster[clusterlabel] || [];
                    return {
                        "cluster_values": _.compact(_.map(columns, function (column) {
                            return this.data[column]
                        }, this))
                    }
                }, this);

                var g_cluster = svg.selectAll("g.cluster").data(clusters).enter().insert("g").attr("class", "cluster");

                var g_column = g_cluster.selectAll("g.column")
                    .data(function (d) {
                        return d.cluster_values;
                    })
                    .enter().append("g").attr("class", "column");

                var i = 0;
                g_column.selectAll("rect.bar")
                    .data(function (d) {
                        return [d[row_label]];
                    })
                    .enter()
                    .append("rect")
                    .attr("class", "bar")
                    .append("title")
                    .text(function (d) {
                        return d.label + "\n" + i++;
                    });

                g_column.selectAll("rect.bar")
                    .attr("x", function (d, i, a) {
                        return a * bar_width;
                    })
                    .attr("width", bar_width)
                    .attr("height", this.options.bar_height);

                g_column.selectAll("rect.bar").style("fill", function (d, i) {
                    return d.colorscale;
                });

                this._highlight_bars(g_column);
            }, this);
        },

        _highlight_bars: function (g_column) {
            var rect_highlight = g_column.selectAll("rect.highlight")
                .data(function (d) {
                    return _.compact(_.map(_.values(d), function (cellvalue, index) {
                        if (cellvalue !== undefined && cellvalue.isHighlighted) {
                            return index;
                        }
                    }));
                })
                .enter()
                .append("rect")
                .attr("class", "highlight")
                .style("fill", this.options.highlight.fill)
                .attr("width", this.options.bar_width)
                .attr("x", 0)
                .attr("height", this.options.highlight.bar_height);
            g_column.selectAll("rect.highlight").style("fill", this.options.highlight.fill);
        },

        _plot_width: function () {
            var columnCounts = _.map(this.options.columns_by_cluster, function (columns) {
                return columns.length;
            });
            var numberOfColumns = _.reduce(columnCounts, function (memo, num) {
                return memo + num;
            }, 0);
            // TODO : Determine plot and bar width based on a scale
            return (this.options.bar_width * numberOfColumns);
        },

        _get_colorscale_fn: function (rowlabel, rowidx) {
            var row_colorscale = (this.options.colorscales[rowlabel]) ? this.options.colorscales[rowlabel] : ["yellow", "green"];
            var values = _.map(this.data[rowidx], function (item) {
                return item.value;
            });
            var colorrangeMap = d3.scale.linear().domain([0, values.length - 1]).range(row_colorscale);
            var colorrangeFn = d3.range(values.length).map(colorrangeMap);
            var colorscaleFn = d3.scale.ordinal().domain(values).range(colorrangeFn);
            return function (cell) {
                return colorscaleFn(values.indexOf(cell.value));
            };
        }
    }
};<|endoftext|>


<commit_before>import _ from 'lodash';
const {knex} = require('../util/database').connect();
import {GCS_CONFIG} from '../util/gcs';
const logger = require('../util/logger')(__filename);

function getFeed(opts) {
  opts = _.merge({
    limit: 20
  }, opts);

  let sqlString = `SELECT
      actions.id as id,
      actions.location as location,
      actions.created_at as created_at,
      actions.image_path as image_path,
      actions.text as text,
      action_types.code as action_type_code,
      users.name as user_name,
      users.uuid as user_uuid,
      teams.name as team_name
    FROM actions
    JOIN action_types ON action_types.id = actions.action_type_id
    JOIN users ON users.id = actions.user_id
    JOIN teams ON teams.id = actions.team_id
    WHERE
      (action_types.code = 'IMAGE' OR
      action_types.code = 'TEXT')`;
  let params = [];

  if (opts.beforeId) {
    sqlString += ` AND actions.id < ? `
    params.push(opts.beforeId);
  }

  sqlString += `ORDER BY actions.id DESC LIMIT ?`;
  params.push(opts.limit);

  return knex.raw(sqlString, params)
  .then(result => {
    const rows = result.rows;

    if (_.isEmpty(rows)) {
      return [];
    }

    return _.map(rows, _actionToFeedObject);
  });
}

function deleteFeedItem(id, clientUuid) {
  return knex('actions').delete().where({
    'id': id,
    'user_id': knex.raw('(SELECT id from users WHERE uuid = ?)', [clientUuid])
  })
  .then(deletedCount => {
    if (deletedCount > 1) {
      logger.error('Deleted feed item', id, 'client uuid:', clientUuid);
      throw new Error('Unexpected amount of deletes happened: ' + deletedCount)
    }

    return deletedCount;
  });
}

function _actionToFeedObject(row) {
  var feedObj = {
    id: row['id'],
    type: row['action_type_code'],
    author: {
      name: row['user_name'],
      team: row['team_name']
    },
    location: {
      latitude: row.location.y,
      longitude: row.location.x
    },
    createdAt: row['created_at']
  };

  if (feedObj.type === 'IMAGE') {
    feedObj.url = GCS_CONFIG.baseUrl + '/' + GCS_CONFIG.bucketName + '/' + row['image_path']
  } else if (feedObj.type === 'TEXT') {
    feedObj.text = row.text;
  }

  return feedObj;
}

export {
  getFeed,
  deleteFeedItem
};
<commit_msg>Fix bug in feed<commit_after>import _ from 'lodash';
const {knex} = require('../util/database').connect();
import {GCS_CONFIG} from '../util/gcs';
const logger = require('../util/logger')(__filename);

function getFeed(opts) {
  opts = _.merge({
    limit: 20,

  }, opts);

  let sqlString = `SELECT
      actions.id as id,
      actions.location as location,
      actions.created_at as created_at,
      actions.image_path as image_path,
      actions.text as text,
      action_types.code as action_type_code,
      users.name as user_name,
      users.uuid as user_uuid,
      teams.name as team_name
    FROM actions
    JOIN action_types ON action_types.id = actions.action_type_id
    JOIN users ON users.id = actions.user_id
    JOIN teams ON teams.id = actions.team_id
    WHERE
      (action_types.code = 'IMAGE' OR
      action_types.code = 'TEXT')`;
  let params = [];

  if (opts.beforeId) {
    sqlString += ` AND actions.id < ? `
    params.push(opts.beforeId);
  }

  sqlString += `ORDER BY actions.id DESC LIMIT ?`;
  params.push(opts.limit);

  return knex.raw(sqlString, params)
  .then(result => {
    const rows = result.rows;

    if (_.isEmpty(rows)) {
      return [];
    }

    return _.map(rows, _actionToFeedObject);
  });
}

function deleteFeedItem(id, clientUuid) {
  return knex('actions').delete().where({
    'id': id,
    'user_id': knex.raw('(SELECT id from users WHERE uuid = ?)', [clientUuid])
  })
  .then(deletedCount => {
    if (deletedCount > 1) {
      logger.error('Deleted feed item', id, 'client uuid:', clientUuid);
      throw new Error('Unexpected amount of deletes happened: ' + deletedCount)
    }

    return deletedCount;
  });
}

function _actionToFeedObject(row) {
  var feedObj = {
    id: row['id'],
    type: row['action_type_code'],
    author: {
      name: row['user_name'],
      team: row['team_name']
    },
    createdAt: row['created_at']
  };

  if (row.location) {
    feedObj.location = {
      latitude: row.location.y,
      longitude: row.location.x
    };
  }

  if (feedObj.type === 'IMAGE') {
    feedObj.url = GCS_CONFIG.baseUrl + '/' + GCS_CONFIG.bucketName + '/' + row['image_path']
  } else if (feedObj.type === 'TEXT') {
    feedObj.text = row.text;
  }

  return feedObj;
}

export {
  getFeed,
  deleteFeedItem
};
<|endoftext|>


<commit_before>class AudioUtils {
    constructor() {
        this.isAudioSupport = this._isBrowserSupportAudio(); // 브라우저 지원 확인
        this.isAudioInitComplete = false; // 유저 인풋 연결 확인
        this.isRecording = false;

        this._userMediaStream = undefined;
        this._mediaRecorder = undefined;
        this._gainNode = undefined;
        this._audioChunks = [];
    }

    async initUserMedia() {
        if (!this.isAudioSupport) {
            console.error('this browser not support input media');
            return false;
        }

        try {
            const mediaStream = await navigator.mediaDevices.getUserMedia({
                audio: true,
            });
            const audioContext = new AudioContext();
            const streamSrc = audioContext.createMediaStreamSource(mediaStream);
            const gainNode = audioContext.createGain();
            const streamDest = audioContext.createMediaStreamDestination();
            const mediaRecorder = new MediaRecorder(streamDest.stream);

            streamSrc.connect(gainNode);
            gainNode.connect(streamDest);

            this._userMediaStream = mediaStream;
            this._gainNode = gainNode;
            this._mediaRecorder = mediaRecorder;
            this.isAudioInitComplete = true;
            return true;
        } catch (e) {
            console.error('error occurred while init audio input', e);
            this.isAudioInitComplete = false;
            return false;
        }
    }

    startRecord(recordMilliSecond = 3000) {
        return new Promise((resolve) => {
            if (!this.isAudioInitComplete) {
                console.error('audio not initialized');
                resolve();
                return;
            }
            if (this.isRecording) {
                console.error('audio is already recording');
                resolve();
                return;
            }
            this._audioChunks = [];
            this.isRecording = true;

            this._mediaRecorder.addEventListener('dataavailable', this._handleRecorderOnData);
            this._mediaRecorder.addEventListener('stop', this._handleRecorderOnStop);

            this._mediaRecorder.start();

            setTimeout(() => {
                this.stopRecord();
                resolve();
            }, recordMilliSecond);
        });
    }

    stopRecord() {
        if (!this.isAudioInitComplete) {
            console.error('audio not initialized');
            return;
        }

        this.isRecording = false;
        this._mediaRecorder.stop();
    }

    isAudioConnected() {
        const tracks = this._userMediaStream.getAudioTracks();
        return tracks && tracks.some((track) => track.enabled);
    }

    setAudioVolume(volume) {

    }

    _isBrowserSupportAudio() {
        return navigator.mediaDevices && navigator.mediaDevices.getUserMedia && MediaRecorder;
    }

    _handleRecorderOnData = (event) => {
        this._audioChunks.push(event.data);
    };

    _handleRecorderOnStop = (event) => {
        this._mediaRecorder.removeEventListener('dataavailable', this._handleRecorderOnData);
        this._mediaRecorder.removeEventListener('stop', this._handleRecorderOnStop);

        const blob = new Blob(this._audioChunks, { type: 'audio/wav' });
        console.log(URL.createObjectURL(blob));
    };
}

// 해당 클래스는 프로젝트 내 싱글인스턴스로 존재한다. Entry 네임스페이스에는 존재하지 않으므로 외부에서 사용할 수 없다.
window.audio = new AudioUtils();
export default window.audio;
<commit_msg>[feature/audio_block] audioInput 의 현재 볼륨을 가져오기 위한 로직 추가<commit_after>/**
 * 언더바가 붙은 함수 및 변수를 외부에서 쓰지 마세요
 * @author extracold1209
 */
class AudioUtils {
    get currentVolume() {
        return this._currentVolume;
    }

    constructor() {
        this.isAudioSupport = this._isBrowserSupportAudio(); // 브라우저 지원 확인
        this.isAudioInitComplete = false; // 유저 인풋 연결 확인
        this.isRecording = false;

        this._userMediaStream = undefined;
        this._mediaRecorder = undefined;
        this._currentVolume = -1;
        this._audioChunks = [];
    }

    /**
     * 웹 오디오 노드 스트림을 정의한다.
     * 각 스트림은 아래와 같은 역할을 한다.
     *
     * - MediaStreamSource : 오디오 인풋 데이터를 컨트롤 가능한 컨텍스트에 넣기 위한 시점
     * - GainNode : 전체 볼륨 세기 조정
     * - AnalyserNode : 현재 버퍼에서 진폭등의 값을 읽어와 표현 가능한 수치로 제공
     * - ScriptProcessorNode: (deprecated) input - output 사이에서 데이터를 가져와 다양한 로직 추가가능
     *   여기서는 analyse 된 음세기의 평균값을 도출한 후 그대로 버퍼를 다음으로 전
     * @returns {Promise<boolean>}
     */
    async initUserMedia() {
        if (!this.isAudioSupport) {
            console.error('this browser not support input media');
            return false;
        }

        try {
            const mediaStream = await navigator.mediaDevices.getUserMedia({
                audio: true,
            });
            const audioContext = new AudioContext();
            const streamSrc = audioContext.createMediaStreamSource(mediaStream);
            const gainNode = audioContext.createGain(); // 볼륨 전체 세팅용 노드
            const analyserNode = audioContext.createAnalyser();
            const scriptNode = audioContext.createScriptProcessor(2048, 1, 1);
            const streamDest = audioContext.createMediaStreamDestination();
            const mediaRecorder = new MediaRecorder(streamDest.stream);

            // 순서대로 노드 커넥션을 맺는다.
            this._connectNodes(streamSrc, gainNode, analyserNode, scriptNode, streamDest);
            scriptNode.onaudioprocess = this._handleScriptProcess(analyserNode);

            this._userMediaStream = mediaStream;
            this._mediaRecorder = mediaRecorder;
            this.isAudioInitComplete = true;
            return true;
        } catch (e) {
            console.error('error occurred while init audio input', e);
            this.isAudioInitComplete = false;
            return false;
        }
    }

    startRecord(recordMilliSecond = 3000) {
        return new Promise((resolve) => {
            if (!this.isAudioInitComplete) {
                console.error('audio not initialized');
                resolve();
                return;
            }
            if (this.isRecording) {
                console.error('audio is already recording');
                resolve();
                return;
            }
            this._audioChunks = [];
            this.isRecording = true;

            this._mediaRecorder.addEventListener('dataavailable', this._handleRecorderOnData);
            this._mediaRecorder.addEventListener('stop', this._handleRecorderOnStop);

            this._mediaRecorder.start();

            setTimeout(() => {
                this.stopRecord();
                resolve();
            }, recordMilliSecond);
        });
    }

    stopRecord() {
        if (!this.isAudioInitComplete) {
            console.error('audio not initialized');
            return;
        }

        this.isRecording = false;
        this._mediaRecorder.stop();
    }

    isAudioConnected() {
        const tracks = this._userMediaStream.getAudioTracks();
        return tracks && tracks.some((track) => track.enabled);
    }

    _connectNodes(...connectableNodes) {
        for (let i = 0; i < connectableNodes.length - 1; i++) {
            if (connectableNodes[i].connect) {
                connectableNodes[i].connect(connectableNodes[i + 1]);
            } else {
                throw new Error('you can not connect node');
            }
        }
    }

    _isBrowserSupportAudio() {
        return navigator.mediaDevices && navigator.mediaDevices.getUserMedia && MediaRecorder;
    }

    _handleRecorderOnData = (event) => {
        this._audioChunks.push(event.data);
    };

    _handleRecorderOnStop = (event) => {
        this._mediaRecorder.removeEventListener('dataavailable', this._handleRecorderOnData);
        this._mediaRecorder.removeEventListener('stop', this._handleRecorderOnStop);

        const blob = new Blob(this._audioChunks, { type: 'audio/wav' });
        console.log(URL.createObjectURL(blob));
    };

    _handleScriptProcess = (analyserNode) => (audioProcessingEvent) => {
        const array = new Uint8Array(analyserNode.frequencyBinCount);
        analyserNode.getByteFrequencyData(array);

        // 현재 input 의 볼륨세기
        this._currentVolume = array.reduce((total, data) => total + data, 0) / array.length;

        // 볼륨 변형 없이 그대로 통과
        const { inputBuffer, outputBuffer } = audioProcessingEvent;
        for (let channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
            const inputData = inputBuffer.getChannelData(channel);
            const outputData = outputBuffer.getChannelData(channel);
            for (let sample = 0; sample < inputBuffer.length; sample++) {
                outputData[sample] = inputData[sample];
            }
        }
    };
}

// 해당 클래스는 프로젝트 내 싱글인스턴스로 존재한다. Entry 네임스페이스에는 존재하지 않으므로 외부에서 사용할 수 없다.
window.audio = new AudioUtils();
export default window.audio;
<|endoftext|>


<commit_before>/**
 * 언더바가 붙은 함수 및 변수를 외부에서 쓰지 마세요
 * @author extracold1209, wodnjs6512
 */

const { voiceApiConnect } = require('./audioSocket');
const toWav = require('audiobuffer-to-wav');

const STATUS_CODE = {
    CONNECTED: 'CONNECTED',
    NOT_RECOGNIZED: 'NOT_RECOGNIZED',
};

const VOICE_SERVER_ADDR = `${window.location.hostname}`;

class AudioUtils {
    get currentVolume() {
        return this._currentVolume;
    }

    constructor() {
        this.isAudioInitComplete = false; // 유저 인풋 연결 확인
        this.isRecording = false;
        this._userMediaStream = undefined;
        this._mediaRecorder = undefined;
        this._currentVolume = -1;
        this._audioChunks = [];
        this.result = null;
    }

    async checkUserMicAvailable() {
        try {
            await navigator.mediaDevices.getUserMedia({ audio: true });
            return true;
        } catch (err) {
            return false;
        }
    }

    async initUserMedia() {
        if (!this.isAudioSupport) {
            this.isAudioSupport = this._isBrowserSupportAudio(); // 브라우저 지원 확인
        }

        if (this.isAudioInitComplete) {
            return;
        }
        if (!this.isAudioSupport) {
            throw new Entry.Utils.IncompatibleError();
        }

        try {
            const mediaStream = await navigator.mediaDevices.getUserMedia({
                audio: true,
            });
            if (!window.AudioContext) {
                if (window.webkitAudioContext) {
                    window.AudioContext = window.webkitAudioContext;
                }
            }
            const audioContext = new window.AudioContext({ sampleRate: 16000 });
            const streamSrc = audioContext.createMediaStreamSource(mediaStream);
            const analyserNode = audioContext.createAnalyser();
            const biquadFilter = audioContext.createBiquadFilter();
            biquadFilter.type = 'highpass';
            biquadFilter.frequency.value = 20;
            const scriptNode = audioContext.createScriptProcessor(4096, 1, 1);
            const streamDest = audioContext.createMediaStreamDestination();
            const mediaRecorder = new MediaRecorder(streamDest.stream);
            // 순서대로 노드 커넥션을 맺는다.
            this._connectNodes(streamSrc, analyserNode, biquadFilter, scriptNode, streamDest);
            scriptNode.onaudioprocess = this._handleScriptProcess(analyserNode);
            console.log(scriptNode);

            this._audioContext = audioContext;
            this._userMediaStream = mediaStream;
            this._mediaRecorder = mediaRecorder;
            this.isAudioInitComplete = true;

            // 음성 인식 api 를 사용하기 위함
            // 음성 인식은 websocket 을 통해서 WAV로 전송하게 되어있음.
            // 첫번째 파라미터는 프로토콜을 제외한 hostname+port 조합
            // ex)'localhost:4001'
            return true;
        } catch (e) {
            console.error('error occurred while init audio input', e);
            this.isAudioInitComplete = false;
            return false;
        }
    }

    async startRecord(recordMilliSecond, language) {
        return await new Promise(async (resolve, reject) => {
            if (!this.isAudioInitComplete) {
                console.log('audio not initialized');
                resolve();
                return;
            }

            // this.isRecording = true;
            if (this._audioContext.state === 'suspended') {
                await this.initUserMedia();
            }

            try {
                const socketClient = await voiceApiConnect(VOICE_SERVER_ADDR, language, (data) => {
                    this.result = data;
                });
                this._socketClient = socketClient;
            } catch (err) {
                console.log(err);
            }

            this._audioChunks = [];

            this._stopMediaRecorder();
            this._mediaRecorder.start();

            Entry.engine.toggleAudioShadePanel();
            this._socketClient.on('message', (e) => {
                switch (e) {
                    case STATUS_CODE.CONNECTED:
                        break;
                    case STATUS_CODE.NOT_RECOGNIZED:
                        this.stopRecord();
                        resolve('');
                        break;
                    default:
                        const parsed = JSON.parse(e);
                        const isArray = Array.isArray(parsed);
                        if (isArray) {
                            this.stopRecord();
                            resolve(parsed[0]);
                        }
                        break;
                }
            });
            this._properStopCall = setTimeout(this.stopRecord, recordMilliSecond);
            this._noInputStopCall = setTimeout(() => {
                this.stopRecord();
                clearTimeout(this._properStopCall);
            }, 3000);
        });
    }

    _stopMediaRecorder() {
        if (this._mediaRecorder.state == 'recording' || this._mediaRecorder.state === 'paused') {
            this._mediaRecorder.stop();
        }
    }
    /**
     * 녹음을 종료한다. silent = true 인 경우 API 콜을 하지 않기 위해 리스너를 먼저 제거하고 stop 한다.
     * @param {object=} option
     * @param {boolean} [option.silent=false]
     */
    async stopRecord(option = { silent: false }) {
        if (this._socketClient) {
            this._socketClient.disconnect();
        }

        if (!this.isAudioInitComplete || !this.isRecording) {
            return;
        }
        Entry.dispatchEvent('audioRecordProcessing');
        Entry.engine.toggleAudioProgressPanel();
        if (option.silent) {
            this._mediaRecorder.onstop = () => {
                console.log('silent stop');
            };
            this._stopMediaRecorder();
        } else {
            this._stopMediaRecorder();
            this._mediaRecorder.onstop = () => {
                console.log('proper stop');
            };
        }
        this._audioContext.suspend();
        this._userMediaStream.getTracks().forEach((track) => {
            track.stop();
        });
        clearTimeout(this._properStopCall);
        clearTimeout(this._noInputStopCall);
        // this.isRecording = false;
    }

    isAudioConnected() {
        if (!this._isBrowserSupportAudio() || !this.isAudioInitComplete || !this._userMediaStream) {
            return false;
        }
        const tracks = this._userMediaStream.getAudioTracks();
        return tracks && tracks.some((track) => track.readyState === 'live');
    }

    _connectNodes(...connectableNodes) {
        for (let i = 0; i < connectableNodes.length - 1; i++) {
            if (connectableNodes[i].connect) {
                connectableNodes[i].connect(connectableNodes[i + 1]);
            } else {
                throw new Error('you can not connect node');
            }
        }
    }

    _isBrowserSupportAudio() {
        if (
            !navigator.mediaDevices ||
            !navigator.mediaDevices.getUserMedia ||
            !window.MediaRecorder
        ) {
            return false;
        }
        return true;
    }

    _handleScriptProcess = (analyserNode) => (audioProcessingEvent) => {
        const array = new Uint8Array(analyserNode.frequencyBinCount);
        analyserNode.getByteFrequencyData(array);

        // 현재 input 의 볼륨세기
        this._currentVolume = array.reduce((total, data) => total + data, 0) / array.length;

        // 볼륨 변형 없이 그대로 통과
        const { inputBuffer, outputBuffer } = audioProcessingEvent;
        for (let channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
            const inputData = inputBuffer.getChannelData(channel);
            const outputData = outputBuffer.getChannelData(channel);
            for (let sample = 0; sample < inputBuffer.length; sample++) {
                outputData[sample] = inputData[sample];
            }
        }
        // console.log(this._currentVolume);
        if (this.isRecording) {
            if (this._currentVolume > 60) {
                clearTimeout(this._noInputStopCall);
            }
            // websocket 으로 서버 전송
            if (this._socketClient && this._socketClient.readyState === this._socketClient.OPEN) {
                this._socketClient.send(toWav(outputBuffer));
            }
        }
    };
}

//Entry 네임스페이스에는 존재하지 않으므로 외부에서 사용할 수 없다.
export default new AudioUtils();
<commit_msg>removed useless console.log<commit_after>/**
 * 언더바가 붙은 함수 및 변수를 외부에서 쓰지 마세요
 * @author extracold1209, wodnjs6512
 */

const { voiceApiConnect } = require('./audioSocket');
const toWav = require('audiobuffer-to-wav');

const STATUS_CODE = {
    CONNECTED: 'CONNECTED',
    NOT_RECOGNIZED: 'NOT_RECOGNIZED',
};

const VOICE_SERVER_ADDR = `${window.location.hostname}`;

class AudioUtils {
    get currentVolume() {
        return this._currentVolume;
    }

    constructor() {
        this.isAudioInitComplete = false; // 유저 인풋 연결 확인
        this.isRecording = false;
        this._userMediaStream = undefined;
        this._mediaRecorder = undefined;
        this._currentVolume = -1;
        this._audioChunks = [];
        this.result = null;
    }

    async checkUserMicAvailable() {
        try {
            await navigator.mediaDevices.getUserMedia({ audio: true });
            return true;
        } catch (err) {
            return false;
        }
    }

    async initUserMedia() {
        if (!this.isAudioSupport) {
            this.isAudioSupport = this._isBrowserSupportAudio(); // 브라우저 지원 확인
        }

        if (this.isAudioInitComplete) {
            return;
        }
        if (!this.isAudioSupport) {
            throw new Entry.Utils.IncompatibleError();
        }

        try {
            const mediaStream = await navigator.mediaDevices.getUserMedia({
                audio: true,
            });
            if (!window.AudioContext) {
                if (window.webkitAudioContext) {
                    window.AudioContext = window.webkitAudioContext;
                }
            }
            const audioContext = new window.AudioContext({ sampleRate: 16000 });
            const streamSrc = audioContext.createMediaStreamSource(mediaStream);
            const analyserNode = audioContext.createAnalyser();
            const biquadFilter = audioContext.createBiquadFilter();
            biquadFilter.type = 'highpass';
            biquadFilter.frequency.value = 20;
            const scriptNode = audioContext.createScriptProcessor(4096, 1, 1);
            const streamDest = audioContext.createMediaStreamDestination();
            const mediaRecorder = new MediaRecorder(streamDest.stream);
            // 순서대로 노드 커넥션을 맺는다.
            this._connectNodes(streamSrc, analyserNode, biquadFilter, scriptNode, streamDest);
            scriptNode.onaudioprocess = this._handleScriptProcess(analyserNode);

            this._audioContext = audioContext;
            this._userMediaStream = mediaStream;
            this._mediaRecorder = mediaRecorder;
            this.isAudioInitComplete = true;

            // 음성 인식 api 를 사용하기 위함
            // 음성 인식은 websocket 을 통해서 WAV로 전송하게 되어있음.
            // 첫번째 파라미터는 프로토콜을 제외한 hostname+port 조합
            // ex)'localhost:4001'
            return true;
        } catch (e) {
            console.error('error occurred while init audio input', e);
            this.isAudioInitComplete = false;
            return false;
        }
    }

    async startRecord(recordMilliSecond, language) {
        return await new Promise(async (resolve, reject) => {
            if (!this.isAudioInitComplete) {
                console.log('audio not initialized');
                resolve();
                return;
            }

            // this.isRecording = true;
            if (this._audioContext.state === 'suspended') {
                await this.initUserMedia();
            }

            try {
                const socketClient = await voiceApiConnect(VOICE_SERVER_ADDR, language, (data) => {
                    this.result = data;
                });
                this._socketClient = socketClient;
            } catch (err) {
                console.log(err);
            }

            this._audioChunks = [];

            this._stopMediaRecorder();
            this._mediaRecorder.start();

            Entry.engine.toggleAudioShadePanel();
            this._socketClient.on('message', (e) => {
                switch (e) {
                    case STATUS_CODE.CONNECTED:
                        break;
                    case STATUS_CODE.NOT_RECOGNIZED:
                        this.stopRecord();
                        resolve('');
                        break;
                    default:
                        const parsed = JSON.parse(e);
                        const isArray = Array.isArray(parsed);
                        if (isArray) {
                            this.stopRecord();
                            resolve(parsed[0]);
                        }
                        break;
                }
            });
            this._properStopCall = setTimeout(this.stopRecord, recordMilliSecond);
            this._noInputStopCall = setTimeout(() => {
                this.stopRecord();
                clearTimeout(this._properStopCall);
            }, 3000);
        });
    }

    _stopMediaRecorder() {
        if (this._mediaRecorder.state == 'recording' || this._mediaRecorder.state === 'paused') {
            this._mediaRecorder.stop();
        }
    }
    /**
     * 녹음을 종료한다. silent = true 인 경우 API 콜을 하지 않기 위해 리스너를 먼저 제거하고 stop 한다.
     * @param {object=} option
     * @param {boolean} [option.silent=false]
     */
    async stopRecord(option = { silent: false }) {
        if (this._socketClient) {
            this._socketClient.disconnect();
        }

        if (!this.isAudioInitComplete || !this.isRecording) {
            return;
        }
        Entry.dispatchEvent('audioRecordProcessing');
        Entry.engine.toggleAudioProgressPanel();
        if (option.silent) {
            this._mediaRecorder.onstop = () => {
                console.log('silent stop');
            };
            this._stopMediaRecorder();
        } else {
            this._stopMediaRecorder();
            this._mediaRecorder.onstop = () => {
                console.log('proper stop');
            };
        }
        this._audioContext.suspend();
        this._userMediaStream.getTracks().forEach((track) => {
            track.stop();
        });
        clearTimeout(this._properStopCall);
        clearTimeout(this._noInputStopCall);
        // this.isRecording = false;
    }

    isAudioConnected() {
        if (!this._isBrowserSupportAudio() || !this.isAudioInitComplete || !this._userMediaStream) {
            return false;
        }
        const tracks = this._userMediaStream.getAudioTracks();
        return tracks && tracks.some((track) => track.readyState === 'live');
    }

    _connectNodes(...connectableNodes) {
        for (let i = 0; i < connectableNodes.length - 1; i++) {
            if (connectableNodes[i].connect) {
                connectableNodes[i].connect(connectableNodes[i + 1]);
            } else {
                throw new Error('you can not connect node');
            }
        }
    }

    _isBrowserSupportAudio() {
        if (
            !navigator.mediaDevices ||
            !navigator.mediaDevices.getUserMedia ||
            !window.MediaRecorder
        ) {
            return false;
        }
        return true;
    }

    _handleScriptProcess = (analyserNode) => (audioProcessingEvent) => {
        const array = new Uint8Array(analyserNode.frequencyBinCount);
        analyserNode.getByteFrequencyData(array);

        // 현재 input 의 볼륨세기
        this._currentVolume = array.reduce((total, data) => total + data, 0) / array.length;

        // 볼륨 변형 없이 그대로 통과
        const { inputBuffer, outputBuffer } = audioProcessingEvent;
        for (let channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
            const inputData = inputBuffer.getChannelData(channel);
            const outputData = outputBuffer.getChannelData(channel);
            for (let sample = 0; sample < inputBuffer.length; sample++) {
                outputData[sample] = inputData[sample];
            }
        }
        // console.log(this._currentVolume);
        if (this.isRecording) {
            if (this._currentVolume > 60) {
                clearTimeout(this._noInputStopCall);
            }
            // websocket 으로 서버 전송
            if (this._socketClient && this._socketClient.readyState === this._socketClient.OPEN) {
                this._socketClient.send(toWav(outputBuffer));
            }
        }
    };
}

//Entry 네임스페이스에는 존재하지 않으므로 외부에서 사용할 수 없다.
export default new AudioUtils();
<|endoftext|>


<commit_before>var Latexdown={
  delay: 150,        // delay after keystroke before updating

  previewid: null,
  eventtapid: null,
  preview: null,     // filled in by Init below

  timeout: null,     // store setTimout id
  mjRunning: false,  // true when MathJax is processing
  oldText: null,     // used to check if an update is needed

  Init: function (pid,eid) {
    this.previewid=pid;
    this.eventtabid=eid;
    this.preview=document.getElementById(this.previewid);
    console.log(this.previewid);
    var id='#'+this.previewid;
    //+' a .active';
    console.log(id);
    var initial=$(id);
    console.log(initial);
    this.UpdateTab(initial.attr("data-url"),initial);
  },
  Update: function () {
    if (this.timeout) {clearTimeout(this.timeout)}
    this.timeout = setTimeout(this.callback,this.delay);
  },
  PreviewHtml: function () {
    this.mjRunning = false;
  },
  PreviewMark: function () {
    this.mjRunning = false;
    text = this.preview.innerHTML;
    text = text.replace(/^>/mg, '>');
    this.preview.innerHTML = marked (text);
  },
  IsMarkdown: function(url){
    ext = url.split('.').pop().toLowerCase();
    if ($.inArray(ext, ['md', 'markdown', 'mdown', 'mkdn', 'mkd', 'mdtxt', 'mdtext']) == -1) {
        return false;
    }
    else{
        return true;
    }
  },
  UpdateTab: function(url , pane){
    var _this=this;
    $.get(url, function(data) {
        //this.timeout = null;
        if (_this.mjRunning) return;
        var text = data;
        if (text === _this.oldtext) return;
        _this.preview.innerHTML = _this.oldtext = text;
        _this.mjRunning = true;
        console.log(url);
        if(_this.IsMarkdown(url)){
            MathJax.Hub.Queue(["Typeset",MathJax.Hub,_this.preview],["PreviewMark",_this]);
        }
        else{
            MathJax.Hub.Queue(["Typeset",MathJax.Hub,_this.preview],["PreviewHTML",_this]);
        }
        makerbutton();
        pane.tab('show');
    });
  }
};
Latexdown.callback=MathJax.Callback(["UpdateTab",Latexdown]);
Latexdown.callback.autoReset = true;
<commit_msg>Update updatetab.js<commit_after>var Latexdown={
  delay: 150,        // delay after keystroke before updating

  previewid: null,
  eventtapid: null,
  preview: null,     // filled in by Init below

  timeout: null,     // store setTimout id
  mjRunning: false,  // true when MathJax is processing
  oldText: null,     // used to check if an update is needed

  Init: function (pid,eid) {
    this.previewid=pid;
    this.eventtabid=eid;
    this.preview=document.getElementById(this.previewid);
    console.log(this.previewid);
    var id='#'+this.previewid;
    id=id+' a .active';
    console.log(id);
    var initial=$(id);
    console.log(initial);
    this.UpdateTab(initial.attr("data-url"),initial);
  },
  Update: function () {
    if (this.timeout) {clearTimeout(this.timeout)}
    this.timeout = setTimeout(this.callback,this.delay);
  },
  PreviewHtml: function () {
    this.mjRunning = false;
  },
  PreviewMark: function () {
    this.mjRunning = false;
    text = this.preview.innerHTML;
    text = text.replace(/^>/mg, '>');
    this.preview.innerHTML = marked (text);
  },
  IsMarkdown: function(url){
    ext = url.split('.').pop().toLowerCase();
    if ($.inArray(ext, ['md', 'markdown', 'mdown', 'mkdn', 'mkd', 'mdtxt', 'mdtext']) == -1) {
        return false;
    }
    else{
        return true;
    }
  },
  UpdateTab: function(url , pane){
    var _this=this;
    $.get(url, function(data) {
        //this.timeout = null;
        if (_this.mjRunning) return;
        var text = data;
        if (text === _this.oldtext) return;
        _this.preview.innerHTML = _this.oldtext = text;
        _this.mjRunning = true;
        console.log(url);
        if(_this.IsMarkdown(url)){
            MathJax.Hub.Queue(["Typeset",MathJax.Hub,_this.preview],["PreviewMark",_this]);
        }
        else{
            MathJax.Hub.Queue(["Typeset",MathJax.Hub,_this.preview],["PreviewHTML",_this]);
        }
        makerbutton();
        pane.tab('show');
    });
  }
};
Latexdown.callback=MathJax.Callback(["UpdateTab",Latexdown]);
Latexdown.callback.autoReset = true;
<|endoftext|>