<commit_before>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2014 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function(){

var ui = angular.module('axelor.ui');
var popoverElem = null;
var popoverTimer = null;

function canDisplayPopover(scope, details) {
	var mode = __appSettings['application.mode'];
	var tech = __appSettings['user.technical'];

	if(mode == 'prod' && !tech) {
		return details ? false : scope.field && scope.field.help;
	}

	return true;
}

function makePopover(scope, element, callback, placement) {

	var mode = __appSettings['application.mode'];
	var tech = __appSettings['user.technical'];
	var doc = $(document);

	var table = null;

	function addRow(label, text, klass) {
		if (table === null) {
			table = $('

');
		}

		var tr = $('

').appendTo(table);
		if (label) {
			$('	').text(label).appendTo(tr);
		}
		if (klass == null) {
			text = '' + text + '';
		}
		var td = $('	').html(text).addClass(klass).appendTo(tr);
		if (!label) {
			td.attr('colspan', 2);
		}
		return table;
	}

	element.popover({
		html: true,
		delay: { show: 1000, hide: 100 },
		animate: true,
		placement: function() {
			if (placement) return placement;
			var coord = $(element.get(0)).offset(),
				viewport = {height: innerHeight, width: window.innerWidth};
			if(viewport.height < (coord.top + 100))
				return 'top';
			if(coord.left > (viewport.width / 2))
				return 'left';
			return 'right';
		},
		trigger: 'manual',
		container: 'body',
		title: function() {
			return element.text();
		},
		content: function() {
			if (table) {
				table.remove();
				table = null;
			}
			callback(scope, addRow);
			if (table) return table;
			return "";
		}
	});

	element.on('mouseenter.popover', enter);
	element.on('mouseleave.popover', leave);

	function enter(e) {
		if (popoverTimer) {
			clearTimeout(popoverTimer);
		}
		popoverTimer = setTimeout(function () {
			if (popoverElem === null) {
				popoverElem = element;
				popoverElem.popover('show');
			}
		}, 1000);
	}

	function leave(e) {

		if (e.ctrlKey) {
			doc.off('mousemove.popover');
			doc.on('mousemove.popover', leave);
			return;
		}

		if (popoverTimer) {
			clearTimeout(popoverTimer);
			popoverTimer = null;
		}
		if (popoverElem) {
			popoverElem.popover('hide');
			popoverElem = null;
			doc.off('mousemove.popover');
		}
	}

	function destroy() {
		if (element) {
			element.off('mouseenter.popover');
			element.off('mouseleave.popover');
			element.popover('destroy');
			element = null;
		}
		if (table) {
			table.remove();
			table = null;
		}
		doc.off('mousemove.popover');
	}

	scope.$on('$destroy', destroy);
}

ui.directive('uiTabPopover', function() {

	function getHelp(scope, addRow) {
		var tab = scope.tab || {};
		var type = tab.viewType;
		var view = _.findWhere(tab.views, {type: type});

		var viewScope = tab.$viewScope;
		if (viewScope && viewScope.schema) {
			view = viewScope.schema;
		}

		if (tab.action) {
			addRow(_t('Action'), tab.action);
		}
		if (tab.model) {
			addRow(_t('Object'), tab.model);
		}
		if (tab.domain) {
			addRow(_t('Domain'), tab.domain);
		}
		if (view && view.name) {
			addRow(_t('View'), view.name);
		}
	}

	return function (scope, element, attrs) {
		if(canDisplayPopover(scope, true)) {
			return makePopover(scope, element, getHelp, 'bottom');
		}
	};
});

ui.directive('uiHelpPopover', function() {

	function getHelp(scope, addRow) {

		var field = scope.field;
		var text = field.help;
		if (text) {
			text = text.replace(/\\n/g, '
');
			addRow(null, text, 'help-text');
		}

		if (text) {
			addRow(null, '
', 'help-text');
		}

		if(!canDisplayPopover(scope, true)) {
			return;
		}

		var model = scope._model;
		if (model === field.target) {
			model = scope.$parent._model;
		}

		addRow(_t('Object'), model);
		addRow(_t('Field Name'), field.name);
		addRow(_t('Field Type'), field.serverType);

		if (field.type === 'text') {
			return;
		}

		if (field.domain) {
			addRow(_t('Filter'), field.domain);
		}

		if (field.target) {
			addRow(_t('Reference'), field.target);
		}

		var value = scope.$eval('$$original.' + field.name);
		if (value && /-one$/.test(field.serverType)) {
			value = value[field.targetName] || value.id;
		}
		if (value && field.type === "password") {
			value = _.str.repeat('*', value.length);
		}
		if (value && /^(string|image|binary)$/.test(field.type)) {
			var length = value.length;
			value = _.first(value, 50);
			if (length > 50) {
				value.push('...');
			}
			value = value.join('');
		}
		if (value && /(panel-related|one-to-many|many-to-many)/.test(field.serverType)) {
			var length = value.length;
			value = _.first(value, 5);
			value = _.map(value, function(v){
				return v.id;
			});
			if (length > 5) {
				value.push('...');
			}
			value = value.join(', ');
		}

		addRow(_t('Orig. Value'), value);
	}

	function doLink(scope, element, attrs) {
		var field = scope.field;
		if (field == null) {
			return;
		}
		if(canDisplayPopover(scope, false)) {
			makePopover(scope, element, getHelp);
		}
	};

	return function(scope, element, attrs) {
		var field = scope.field;
		if (!_.isEmpty(field)) {
			return doLink(scope, element, attrs);
		}
		var unwatch = scope.$watch('field', function(field, old) {
			if (!field) {
				return;
			}
			unwatch();
			doLink(scope, element, attrs);
		}, true);
	};
});

/**
 * The Label widget.
 *
 */
ui.formItem('Label', {

	css: 'label-item',
	cellCss: 'form-label',

	transclude: true,

	link: function(scope, element, attrs) {
		var field = scope.field;
		if (field && field.required) {
			element.addClass('required');
		}
	},

	template: '?'
});

/**
 * The Spacer widget.
 *
 */
ui.formItem('Spacer', {
	css: 'spacer-item',
	template: '

'
});

/**
 * The Separator widget.
 *
 */
ui.formItem('Separator', {
	css: 'separator-item',
	showTitle: false,
	template: '
{{field.title}}

'
});

/**
 * The Static Text widget.
 *
 */
ui.formItem('Static', {
	css: 'static-item',
	transclude: true,
	template: ''
});

/**
 * The button widget.
 */
ui.formItem('Button', {
	css: 'button-item',
	transclude: true,
	link: function(scope, element, attrs, model) {
		var field = scope.field || {};

		var icon = field.icon || "";
		var iconHover = field.iconHover || "";

		var isIcon = icon.indexOf('fa-') === 0;

		if (isIcon || icon) {
			element.prepend(' ');
		}

		if (isIcon) {
			var e = $('').addClass('fa').addClass(icon).prependTo(element);
			if (iconHover) {
				e.hover(function() {
					$(this).removeClass(icon).addClass(iconHover);
				}, function() {
					$(this).removeClass(iconHover).addClass(icon);
				});
			}
		} else if (icon) {
			$('').attr('src', icon).prependTo(element);
		}

		if (!field.title) {
			element.addClass("button-icon");
		}

		if (_.isString(field.link)) {
			element.removeClass('btn');
			element.attr("href", field.link);
		}

		element.tooltip({
			html: true,
			title: function() {
				if (field.help) {
					return field.help;
				}
				if (element.innerWidth() < element[0].scrollWidth) {
					return field.title;
				}
			},
			delay: { show: 1000, hide: 100 },
			container: 'body'
		});

		element.on("click", function(e) {
			if (scope.isReadonlyExclusive()) return;
			if (scope.waitForActions) {
				scope.waitForActions(function () {
					scope.fireAction("onClick");
				});
			} else {
				scope.fireAction("onClick");
			}
		});

		var readonlySet = false;
		scope.$watch('isReadonlyExclusive()', function(readonly, old) {
			if (readonly === old && readonlySet) return;
			readonlySet = true;
			if (readonly) {
				return element.addClass("disabled").attr('tabindex', -1);
			}
			return element.removeClass("disabled").removeAttr('tabindex');
		});

		scope.$watch('attr("title")', function(title, old) {
			if (!title || title === old) return;
			element.children('.btn-text').html(title);
		});
	},
	template: ''+
		''+
	''
});

ui.formItem('ToolButton', 'Button', {

	getViewDef: function(element) {
		return this.btn;
	},

	link: function(scope, element, attrs) {
		this._super.apply(this, arguments);
		var field = scope.field;
		if (field == null) {
			return;
		}

		scope.title = field.title;

		scope.btn.isHidden = function() {
			return scope.isHidden();
		};
	},

	template: '<button class="btn" ui-show="!isHidden()" name="{{btn.name}}" ui-actions ui-widget-states>{{title}}</button>'
});

})(this);
<commit_msg>Prevent double click on button<commit_after>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2014 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function(){

var ui = angular.module('axelor.ui');
var popoverElem = null;
var popoverTimer = null;

function canDisplayPopover(scope, details) {
	var mode = __appSettings['application.mode'];
	var tech = __appSettings['user.technical'];

	if(mode == 'prod' && !tech) {
		return details ? false : scope.field && scope.field.help;
	}

	return true;
}

function makePopover(scope, element, callback, placement) {

	var mode = __appSettings['application.mode'];
	var tech = __appSettings['user.technical'];
	var doc = $(document);

	var table = null;

	function addRow(label, text, klass) {
		if (table === null) {
			table = $('

');
		}

		var tr = $('

').appendTo(table);
		if (label) {
			$('	').text(label).appendTo(tr);
		}
		if (klass == null) {
			text = '' + text + '';
		}
		var td = $('	').html(text).addClass(klass).appendTo(tr);
		if (!label) {
			td.attr('colspan', 2);
		}
		return table;
	}

	element.popover({
		html: true,
		delay: { show: 1000, hide: 100 },
		animate: true,
		placement: function() {
			if (placement) return placement;
			var coord = $(element.get(0)).offset(),
				viewport = {height: innerHeight, width: window.innerWidth};
			if(viewport.height < (coord.top + 100))
				return 'top';
			if(coord.left > (viewport.width / 2))
				return 'left';
			return 'right';
		},
		trigger: 'manual',
		container: 'body',
		title: function() {
			return element.text();
		},
		content: function() {
			if (table) {
				table.remove();
				table = null;
			}
			callback(scope, addRow);
			if (table) return table;
			return "";
		}
	});

	element.on('mouseenter.popover', enter);
	element.on('mouseleave.popover', leave);

	function enter(e) {
		if (popoverTimer) {
			clearTimeout(popoverTimer);
		}
		popoverTimer = setTimeout(function () {
			if (popoverElem === null) {
				popoverElem = element;
				popoverElem.popover('show');
			}
		}, 1000);
	}

	function leave(e) {

		if (e.ctrlKey) {
			doc.off('mousemove.popover');
			doc.on('mousemove.popover', leave);
			return;
		}

		if (popoverTimer) {
			clearTimeout(popoverTimer);
			popoverTimer = null;
		}
		if (popoverElem) {
			popoverElem.popover('hide');
			popoverElem = null;
			doc.off('mousemove.popover');
		}
	}

	function destroy() {
		if (element) {
			element.off('mouseenter.popover');
			element.off('mouseleave.popover');
			element.popover('destroy');
			element = null;
		}
		if (table) {
			table.remove();
			table = null;
		}
		doc.off('mousemove.popover');
	}

	scope.$on('$destroy', destroy);
}

ui.directive('uiTabPopover', function() {

	function getHelp(scope, addRow) {
		var tab = scope.tab || {};
		var type = tab.viewType;
		var view = _.findWhere(tab.views, {type: type});

		var viewScope = tab.$viewScope;
		if (viewScope && viewScope.schema) {
			view = viewScope.schema;
		}

		if (tab.action) {
			addRow(_t('Action'), tab.action);
		}
		if (tab.model) {
			addRow(_t('Object'), tab.model);
		}
		if (tab.domain) {
			addRow(_t('Domain'), tab.domain);
		}
		if (view && view.name) {
			addRow(_t('View'), view.name);
		}
	}

	return function (scope, element, attrs) {
		if(canDisplayPopover(scope, true)) {
			return makePopover(scope, element, getHelp, 'bottom');
		}
	};
});

ui.directive('uiHelpPopover', function() {

	function getHelp(scope, addRow) {

		var field = scope.field;
		var text = field.help;
		if (text) {
			text = text.replace(/\\n/g, '
');
			addRow(null, text, 'help-text');
		}

		if (text) {
			addRow(null, '
', 'help-text');
		}

		if(!canDisplayPopover(scope, true)) {
			return;
		}

		var model = scope._model;
		if (model === field.target) {
			model = scope.$parent._model;
		}

		addRow(_t('Object'), model);
		addRow(_t('Field Name'), field.name);
		addRow(_t('Field Type'), field.serverType);

		if (field.type === 'text') {
			return;
		}

		if (field.domain) {
			addRow(_t('Filter'), field.domain);
		}

		if (field.target) {
			addRow(_t('Reference'), field.target);
		}

		var value = scope.$eval('$$original.' + field.name);
		if (value && /-one$/.test(field.serverType)) {
			value = value[field.targetName] || value.id;
		}
		if (value && field.type === "password") {
			value = _.str.repeat('*', value.length);
		}
		if (value && /^(string|image|binary)$/.test(field.type)) {
			var length = value.length;
			value = _.first(value, 50);
			if (length > 50) {
				value.push('...');
			}
			value = value.join('');
		}
		if (value && /(panel-related|one-to-many|many-to-many)/.test(field.serverType)) {
			var length = value.length;
			value = _.first(value, 5);
			value = _.map(value, function(v){
				return v.id;
			});
			if (length > 5) {
				value.push('...');
			}
			value = value.join(', ');
		}

		addRow(_t('Orig. Value'), value);
	}

	function doLink(scope, element, attrs) {
		var field = scope.field;
		if (field == null) {
			return;
		}
		if(canDisplayPopover(scope, false)) {
			makePopover(scope, element, getHelp);
		}
	};

	return function(scope, element, attrs) {
		var field = scope.field;
		if (!_.isEmpty(field)) {
			return doLink(scope, element, attrs);
		}
		var unwatch = scope.$watch('field', function(field, old) {
			if (!field) {
				return;
			}
			unwatch();
			doLink(scope, element, attrs);
		}, true);
	};
});

/**
 * The Label widget.
 *
 */
ui.formItem('Label', {

	css: 'label-item',
	cellCss: 'form-label',

	transclude: true,

	link: function(scope, element, attrs) {
		var field = scope.field;
		if (field && field.required) {
			element.addClass('required');
		}
	},

	template: '?'
});

/**
 * The Spacer widget.
 *
 */
ui.formItem('Spacer', {
	css: 'spacer-item',
	template: '

'
});

/**
 * The Separator widget.
 *
 */
ui.formItem('Separator', {
	css: 'separator-item',
	showTitle: false,
	template: '
{{field.title}}

'
});

/**
 * The Static Text widget.
 *
 */
ui.formItem('Static', {
	css: 'static-item',
	transclude: true,
	template: ''
});

/**
 * The button widget.
 */
ui.formItem('Button', {
	css: 'button-item',
	transclude: true,
	link: function(scope, element, attrs, model) {
		var field = scope.field || {};

		var icon = field.icon || "";
		var iconHover = field.iconHover || "";

		var isIcon = icon.indexOf('fa-') === 0;

		if (isIcon || icon) {
			element.prepend(' ');
		}

		if (isIcon) {
			var e = $('').addClass('fa').addClass(icon).prependTo(element);
			if (iconHover) {
				e.hover(function() {
					$(this).removeClass(icon).addClass(iconHover);
				}, function() {
					$(this).removeClass(iconHover).addClass(icon);
				});
			}
		} else if (icon) {
			$('').attr('src', icon).prependTo(element);
		}

		if (!field.title) {
			element.addClass("button-icon");
		}

		if (_.isString(field.link)) {
			element.removeClass('btn');
			element.attr("href", field.link);
		}

		element.tooltip({
			html: true,
			title: function() {
				if (field.help) {
					return field.help;
				}
				if (element.innerWidth() < element[0].scrollWidth) {
					return field.title;
				}
			},
			delay: { show: 1000, hide: 100 },
			container: 'body'
		});

		element.on("click", function(e) {

			if (scope.isReadonlyExclusive() || element.hasClass('disabled')) {
				return;
			}

			function enable() {
				scope.ajaxStop(function () {
					setDisabled(false);
				}, 100);
			}

			function setEnable(p) {
				if (p && p.then) {
					p.then(enable, enable);
				} else {
					scope.ajaxStop(enable, 500);
				}
			}

			function doClick() {
				setEnable(scope.fireAction("onClick"));
			}

			setDisabled(true);

			if (scope.waitForActions) {
				return scope.waitForActions(doClick);
			}
			return doClick();
		});

		function setDisabled(disabled) {
			if (disabled || disabled === undefined) {
				return element.addClass("disabled").attr('tabindex', -1);
			}
			return element.removeClass("disabled").removeAttr('tabindex');
		}

		var readonlySet = false;
		scope.$watch('isReadonlyExclusive()', function(readonly, old) {
			if (readonly === old && readonlySet) return;
			readonlySet = true;
			return setDisabled(readonly);
		});

		scope.$watch('attr("title")', function(title, old) {
			if (!title || title === old) return;
			element.children('.btn-text').html(title);
		});
	},
	template: ''+
		''+
	''
});

ui.formItem('ToolButton', 'Button', {

	getViewDef: function(element) {
		return this.btn;
	},

	link: function(scope, element, attrs) {
		this._super.apply(this, arguments);
		var field = scope.field;
		if (field == null) {
			return;
		}

		scope.title = field.title;

		scope.btn.isHidden = function() {
			return scope.isHidden();
		};
	},

	template: '<button class="btn" ui-show="!isHidden()" name="{{btn.name}}" ui-actions ui-widget-states>{{title}}</button>'
});

})(this);
<|endoftext|>


<commit_before>var isZoomedIn = false;
var beforeZoomScrollPos = 0;
var selectedEdition = '201311';
var currentPage = '1';
var isDeleting = false;
var papers;

var smallCanvases = [];
var bigCanvases = [];
var pdf = null;
var rendered = 0;

if(hash.get('e')) selectedEdition = hash.get('e');
if(hash.get('p')) currentPage = hash.get('p');

$("#flipbook").hide();
$("#arrow-left").hide();
$("#arrow-right").hide();
$("<img/>").attr("src", "/pics/ajax-loader.gif").addClass("ajax-loader").appendTo("#zoom-viewport");

$.getJSON( "/json/papers.json", function( data ) {
	papers = data;

	renderCanvases(selectedEdition)
});

function renderCanvases(edition) {
	PDFJS.disableWorker = true;
	smallCanvases = [];
	bigCanvases = [];

	PDFJS.getDocument("/papers/2013/november.pdf").then(function(_pdf) {
		pdf = _pdf;
		//Render all the pages on a single canvas
		for(var i = 0; i < pdf.numPages; i ++){
			renderPage(i + 1);
		}

	});
}

function renderPage(pnum) {
	pdf.getPage(pnum).then(function(page){
		var smallCanvas = document.createElement("canvas");
		var bigCanvas = document.createElement("canvas");
		var smallViewport = page.getViewport(464 / page.getViewport(1.0).width);
		var bigViewport = page.getViewport(1107 / page.getViewport(1.0).width);
		// changing canvas.width and/or canvas.height auto-clears the canvas
		smallCanvas.width = smallViewport.width;
		smallCanvas.height = smallViewport.height;
		bigCanvas.width = bigViewport.width;
		bigCanvas.height = bigViewport.height;

		page.render({canvasContext: smallCanvas.getContext('2d'), viewport: smallViewport});
		page.render({canvasContext: bigCanvas.getContext('2d'), viewport: bigViewport}).then(function(){
			rendered++;
			if(rendered == pdf.numPages - 1) init_flipbook();
		});

		smallCanvases[pnum-1] = smallCanvas;
		bigCanvases[pnum-1] = bigCanvas;
	});
}

function init_flipbook() {
	for (var i = 0; i < smallCanvases.length; i++) {
		$("#flipbook").append(createPage(i));
	}
	$("#zoom-viewport").find("img").remove();
	$("#flipbook").show();
	$("#arrow-left").show();
	$("#arrow-right").show();

	$('#zoom-viewport').css("height", $('#flipbook').height());
	$('#flipbook').css("left", $(window).width()/2 -$("#flipbook").width()/2);

	$("#flipbook").turn({
		width: 927,
		height: 600,
		autoCenter: true,
		page: currentPage,
		when: {
			tap: function(event) {
				$('#flipbook').zoom('zoomIn', e);
			},
			turned: function(event, page, view) {
				if(!isDeleting) hash.add({ p: page });
			}
		}
	});

	// Zoom.js

	$('#zoom-viewport').zoom({
		flipbook: $('#flipbook'),
		max: function() {

			return 2214/$('#flipbook').width();

		},
		when: {
			tap: function(event) {

				if ($(this).zoom('value')==1) {
					$('#flipbook').
						removeClass('animated').
						addClass('zoom-in');
					$(this).zoom('zoomIn', event);
				} else {
					$(this).zoom('zoomOut');
				}
			},

			resize: function(event, scale, page, pageElement) {

				if (scale==1)
					loadSmallPage(page, pageElement);
				else
					loadLargePage(page, pageElement);

			},

			zoomIn: function () {
				isZoomedIn = true;

				$('#flipbook').addClass('zoom-in');

				$("#arrow-left, #arrow-right").addClass("zoomed-in");
				$("#flipbook").css("margin-left", -$(window).width());

				$('#zoom-viewport').css("height", $(window).height());

				$(document).scrollTop($("#zoom-viewport").offset().top);
				beforeZoomScrollPos = $(document).scrollTop();
				$('body').css("overflow", "hidden");
			},

			zoomOut: function () {
				isZoomedIn = false;

				setTimeout(function(){
					$('#flipbook').addClass('animated').removeClass('zoom-in');
				}, 0);

				$("#arrow-left, #arrow-right").removeClass("zoomed-in");
				$('#zoom-viewport').css("height", $("#flipbook").height());

				$(document).scrollTop(beforeZoomScrollPos);
				$('body').css("overflow", "auto");
				$('#flipbook').css("top", "50%");
			},

			swipeLeft: function() {

				$('#flipbook').turn('next');

			},

			swipeRight: function() {

				$('#flipbook').turn('previous');

			}
		}
	});
}

	// Using arrow keys to turn the page

	$(document).keydown(function(e){

		var previous = 37, next = 39, esc = 27, space = 32;

		switch (e.keyCode) {
			case previous:

				// left arrow
				$('#flipbook').turn('previous');
				e.preventDefault();

			break;
			case next:

				//right arrow
				$('#flipbook').turn('next');
				e.preventDefault();

			break;
			case esc:

				$('#zoom-viewport').zoom('zoomOut');
				e.preventDefault();

			break;
			case space:
				if(isZoomedIn) $('#zoom-viewport').zoom('zoomOut');
				else $('#zoom-viewport').zoom('zoomIn');
				e.preventDefault();

			break;
		}
	});

// Load large page

function loadLargePage(page, pageElement) {

	var img = bigCanvases[page-1];
	var prevImg = pageElement.find('canvas');

	$(prevImg).detach();
	$(img).appendTo(pageElement);

}

// Load small page

function loadSmallPage(page, pageElement) {

	var img = smallCanvases[page-1];
	var prevImg = pageElement.find('canvas');

	$(prevImg).detach();
	$(img).appendTo(pageElement);
}

function createPage(page) {
	var div = document.createElement('div');
	$(div).append(smallCanvases[page]);
	return div;
}

function switchEdition(edition) {
	var currentPages = $("#flipbook").turn("pages");
	isDeleting = true;
	for (var i = 0; i < currentPages; i++) {
		$("#flipbook").turn("removePage", 1);
	}

	for (var i = 0; i < papers[edition][1]; i++) {
		var div = createPage(edition, (i + 1));
		$("#flipbook").turn("addPage", div);
	}
	isDeleting = false;
	selectedEdition = edition;
	hash.add({ e: edition });
}

$(window).resize(function() {
	if(isZoomedIn) {
		$('#zoom-viewport').css("height", $(window).height());
		$(document).scrollTop($("#zoom-viewport").offset().top);
	} else {
		$('#zoom-viewport').css("height", $("#flipbook").height());
	}
	$('#flipbook').css("left", $("#zoom-viewport").width()/2 -$("#flipbook").width()/2);
});

$(function(){

	$(window).hashchange( function(){
		if(hash.get('e')) {
			if(hash.get('e') != selectedEdition) {
				 selectedEdition = hash.get('e');
				 switchEdition(selectedEdition)
			}
		}
		if(hash.get('p')) {
			if(hash.get('p') != currentPage) {
				currentPage = hash.get('p');
				$("#flipbook").turn("page", currentPage);
			}
		}
	})

});<commit_msg>fixed the change to pdfs folder<commit_after>var isZoomedIn = false;
var beforeZoomScrollPos = 0;
var selectedEdition = '201311';
var currentPage = '1';
var isDeleting = false;
var papers;

var smallCanvases = [];
var bigCanvases = [];
var pdf = null;
var rendered = 0;

if(hash.get('e')) selectedEdition = hash.get('e');
if(hash.get('p')) currentPage = hash.get('p');

$("#flipbook").hide();
$("#arrow-left").hide();
$("#arrow-right").hide();
$("<img/>").attr("src", "/pics/ajax-loader.gif").addClass("ajax-loader").appendTo("#zoom-viewport");

$.getJSON( "/json/papers.json", function( data ) {
	papers = data;

	renderCanvases(selectedEdition)
});

function renderCanvases(edition) {
	PDFJS.disableWorker = true;
	smallCanvases = [];
	bigCanvases = [];

	PDFJS.getDocument("/pdfs/2013/november.pdf").then(function(_pdf) {
		pdf = _pdf;
		//Render all the pages on a single canvas
		for(var i = 0; i < pdf.numPages; i ++){
			renderPage(i + 1);
		}

	});
}

function renderPage(pnum) {
	pdf.getPage(pnum).then(function(page){
		var smallCanvas = document.createElement("canvas");
		var bigCanvas = document.createElement("canvas");
		var smallViewport = page.getViewport(464 / page.getViewport(1.0).width);
		var bigViewport = page.getViewport(1107 / page.getViewport(1.0).width);
		// changing canvas.width and/or canvas.height auto-clears the canvas
		smallCanvas.width = smallViewport.width;
		smallCanvas.height = smallViewport.height;
		bigCanvas.width = bigViewport.width;
		bigCanvas.height = bigViewport.height;

		page.render({canvasContext: smallCanvas.getContext('2d'), viewport: smallViewport});
		page.render({canvasContext: bigCanvas.getContext('2d'), viewport: bigViewport}).then(function(){
			rendered++;
			if(rendered == pdf.numPages - 1) init_flipbook();
		});

		smallCanvases[pnum-1] = smallCanvas;
		bigCanvases[pnum-1] = bigCanvas;
	});
}

function init_flipbook() {
	for (var i = 0; i < smallCanvases.length; i++) {
		$("#flipbook").append(createPage(i));
	}
	$("#zoom-viewport").find("img").remove();
	$("#flipbook").show();
	$("#arrow-left").show();
	$("#arrow-right").show();

	$('#zoom-viewport').css("height", $('#flipbook').height());
	$('#flipbook').css("left", $(window).width()/2 -$("#flipbook").width()/2);

	$("#flipbook").turn({
		width: 927,
		height: 600,
		autoCenter: true,
		page: currentPage,
		when: {
			tap: function(event) {
				$('#flipbook').zoom('zoomIn', e);
			},
			turned: function(event, page, view) {
				if(!isDeleting) hash.add({ p: page });
			}
		}
	});

	// Zoom.js

	$('#zoom-viewport').zoom({
		flipbook: $('#flipbook'),
		max: function() {

			return 2214/$('#flipbook').width();

		},
		when: {
			tap: function(event) {

				if ($(this).zoom('value')==1) {
					$('#flipbook').
						removeClass('animated').
						addClass('zoom-in');
					$(this).zoom('zoomIn', event);
				} else {
					$(this).zoom('zoomOut');
				}
			},

			resize: function(event, scale, page, pageElement) {

				if (scale==1)
					loadSmallPage(page, pageElement);
				else
					loadLargePage(page, pageElement);

			},

			zoomIn: function () {
				isZoomedIn = true;

				$('#flipbook').addClass('zoom-in');

				$("#arrow-left, #arrow-right").addClass("zoomed-in");
				$("#flipbook").css("margin-left", -$(window).width());

				$('#zoom-viewport').css("height", $(window).height());

				$(document).scrollTop($("#zoom-viewport").offset().top);
				beforeZoomScrollPos = $(document).scrollTop();
				$('body').css("overflow", "hidden");
			},

			zoomOut: function () {
				isZoomedIn = false;

				setTimeout(function(){
					$('#flipbook').addClass('animated').removeClass('zoom-in');
				}, 0);

				$("#arrow-left, #arrow-right").removeClass("zoomed-in");
				$('#zoom-viewport').css("height", $("#flipbook").height());

				$(document).scrollTop(beforeZoomScrollPos);
				$('body').css("overflow", "auto");
				$('#flipbook').css("top", "50%");
			},

			swipeLeft: function() {

				$('#flipbook').turn('next');

			},

			swipeRight: function() {

				$('#flipbook').turn('previous');

			}
		}
	});
}

	// Using arrow keys to turn the page

	$(document).keydown(function(e){

		var previous = 37, next = 39, esc = 27, space = 32;

		switch (e.keyCode) {
			case previous:

				// left arrow
				$('#flipbook').turn('previous');
				e.preventDefault();

			break;
			case next:

				//right arrow
				$('#flipbook').turn('next');
				e.preventDefault();

			break;
			case esc:

				$('#zoom-viewport').zoom('zoomOut');
				e.preventDefault();

			break;
			case space:
				if(isZoomedIn) $('#zoom-viewport').zoom('zoomOut');
				else $('#zoom-viewport').zoom('zoomIn');
				e.preventDefault();

			break;
		}
	});

// Load large page

function loadLargePage(page, pageElement) {

	var img = bigCanvases[page-1];
	var prevImg = pageElement.find('canvas');

	$(prevImg).detach();
	$(img).appendTo(pageElement);

}

// Load small page

function loadSmallPage(page, pageElement) {

	var img = smallCanvases[page-1];
	var prevImg = pageElement.find('canvas');

	$(prevImg).detach();
	$(img).appendTo(pageElement);
}

function createPage(page) {
	var div = document.createElement('div');
	$(div).append(smallCanvases[page]);
	return div;
}

function switchEdition(edition) {
	var currentPages = $("#flipbook").turn("pages");
	isDeleting = true;
	for (var i = 0; i < currentPages; i++) {
		$("#flipbook").turn("removePage", 1);
	}

	for (var i = 0; i < papers[edition][1]; i++) {
		var div = createPage(edition, (i + 1));
		$("#flipbook").turn("addPage", div);
	}
	isDeleting = false;
	selectedEdition = edition;
	hash.add({ e: edition });
}

$(window).resize(function() {
	if(isZoomedIn) {
		$('#zoom-viewport').css("height", $(window).height());
		$(document).scrollTop($("#zoom-viewport").offset().top);
	} else {
		$('#zoom-viewport').css("height", $("#flipbook").height());
	}
	$('#flipbook').css("left", $("#zoom-viewport").width()/2 -$("#flipbook").width()/2);
});

$(function(){

	$(window).hashchange( function(){
		if(hash.get('e')) {
			if(hash.get('e') != selectedEdition) {
				 selectedEdition = hash.get('e');
				 switchEdition(selectedEdition)
			}
		}
		if(hash.get('p')) {
			if(hash.get('p') != currentPage) {
				currentPage = hash.get('p');
				$("#flipbook").turn("page", currentPage);
			}
		}
	})

});<|endoftext|>


<commit_before>"use strict";

var http = require("http");

// Download a page and call callback(body)
var get_page = function(options, callback) {
  http.request(options, function(res) {
    // Somewhat handle redirects (not really, but good enough):
    if (res.statusCode > 300 && res.statusCode < 400 && res.headers.location) {
      if (!options.headers){
        options.headers = {};
      }
      options.headers['cookie'] = res.headers["set-cookie"];
      options.path = res.headers.location;
      return get_page(options, callback);
    }

    res.setEncoding('utf8');
    var ret = "";
    res.on('data', function (chunk) {
      ret += chunk;
    });
    res.on('end', function() {
      callback(ret);
    });
  }).end();
};

// Download a user's inventory and call callback(inventory)
var get_user_inventory = function(id, start, callback) {
  var options = {
    host: 'steamcommunity.com',
    port: 80,
    path: '/id/' + id + '/inventory/json/753/6?start=' + start,
    method: 'GET'
  };
  get_page(options, function(ret) {
    callback(JSON.parse(ret));
  });
};

// Download the inventory page from http://steam.cards
var get_inventory_page = function(callback) {
  var options = {
    host: 'www.steamcardexchange.net',
    port: 80,
    path: '/index.php?inventory',
    method: 'GET'
  };
  get_page(options, callback);
};

var is_card = function(item) {
  var type = item['type'];
  return type.substring(type.length - 4, type.length) === "Card";
};

var parse_inventory_page_sets = function(data) {
  // Parse the game prices:
  var gameprices = data.substr(data.indexOf("var gameprices=")+15);
  gameprices = gameprices.substr(0, gameprices.indexOf(';'));
  gameprices = JSON.parse(gameprices);

  var stocklist = data.substr(data.indexOf("var stocklist=") + 14);
  stocklist = stocklist.substr(0, stocklist.indexOf(';'));
  stocklist = JSON.parse(stocklist);

  // Get a list of all games and the number of cards in each set:
  var sets = {};
  var regexp = new RegExp(/\([^\<]*)\<\/a\>\<\/td\>\<td id=\"price-(\d+)/g);
  var match;
  while (match = regexp.exec(data)) {
    // Convert game_id to an integer
    var game_id = ~~match[1];
    var nr_cards = stocklist[match[3]] || [undefined];
    sets[game_id] = {
      'game_name': match[2],
      'card_price': ~~(gameprices[match[3]]),
      'nr_cards': ~~(nr_cards[0])
    };
  }
  return sets;
}

// Start server
http.createServer(function(request, response) {
  if (request.url === "/favicon.ico") return;
  response.writeHeader(200, {"Content-Type": "text/plain"});
  var result = { };
  var search = request.url.substr(1).split(',')
  // 17710_2,317710_3,284770_6,212680_5,321260_9,72000_6
  for (var card in search) {
    //response.write("Looking for: " + search[card] + "\n");
    result[search[card]] = { 'count': 0, 'game': 'unknown', 'card': 'unknown' }
  }

  //CardExchange
  var search_inventory = function(start) {
    get_user_inventory('CardExchange', start, function(res) {
      var amount = {};
      var rgInventory = res['rgInventory'];
      var key;
      for (key in rgInventory) {
        var item_id = rgInventory[key]["classid"];
        if (amount[item_id] === undefined) {
          amount[item_id] = 0;
        }
        amount[item_id] += 1;
      }

      var descriptions = res['rgDescriptions'];
      for (key in descriptions) {
        var app_data = descriptions[key]['app_data'];
        var card = app_data['appid'] + '_' + app_data['item_type'];
        if (card in result) {
          //response.write("Found card: " + card + "\n");
          result[card]['count'] = amount[descriptions[key]['classid']];
          result[card]['game'] = descriptions[key]['type'];
          result[card]['card'] = descriptions[key]['market_name'];
        } else {
          //response.write(card + "\n");
        }
      }
      if (res['more']) {
        //response.write("Going to next page: " + res['more_start'] + "...\n");
        setTimeout(function() {
          search_inventory(res['more_start'])
        }, 0);
      } else {
        for (var card in result) {
          var link =
            result[card]['count'] > 1 ?
            "http://www.steamcardexchange.net/index.php?inventorygame-appid-" + card :
            "";
          response.write(result[card]['count'] + " : " +
                         result[card]['game'] + " : " +
                         result[card]['card'] + " : " +
                         link + " : " + card + "\n");
        }
        response.end();
      }
    });
  }
  search_inventory(0);
}).listen(8080);

console.log("Server Running on http://localhost:8080");

// Start server
http.createServer(function(request, response) {
  if (request.url === "/favicon.ico") return;
  response.writeHeader(200, {"Content-Type": "text/plain"});
  var user_id = request.url.substr(1);
  if (user_id === "") {
    response.write("No user_id");
    response.end();
    return;
  }

  var parse_inventory_page = function(data) {
    // Get the current queue for the bot:
    var current_cueue = data.substr(data.indexOf("There are currently"));
    current_cueue = current_cueue.substr(0, current_cueue.indexOf('<'));
    response.write(current_cueue + "\n\n");

    // Parse all the card sets from the page:
    var sets = parse_inventory_page_sets(data);
    // Compare that to our inventory:
    get_user_inventory(user_id, 0, function(data) {
      var inventory = [];
      var descriptions = data['rgDescriptions'];
      var last_game_id;
      var key;
      for (key in descriptions) {
        if (!is_card(descriptions[key])) {
          continue;
        }
        var game_id = ~~(descriptions[key]['app_data']['appid']);
        var game;
        if ((game = sets[game_id]) !== undefined) {
          if (last_game_id === undefined || last_game_id !== game_id) {
            inventory.push({
              'game_id': game_id,
              'game_name': game['game_name'],
              'total_price' : game['card_price'] * game['nr_cards']
            });
            last_game_id = game_id;
          }
        } else {
           // response.write("Could not find game: " + game_id + "\n");
        }
      }

      // Sort the list by total price:
      inventory.sort(function(a, b) {
        return b['total_price'] - a['total_price'];
      });

      // Print the result:
      for (var card in inventory) {
        response.write(inventory[card]['game_name'] + " : " + inventory[card]['total_price'] + "\n");
      }

      response.end();
    });
  };
  get_inventory_page(parse_inventory_page);
}).listen(8081);

console.log("Server Running on http://localhost:8081");

<commit_msg>ES6: var -> let/const<commit_after>"use strict";

const http = require("http");

// Download a page and call callback(body)
const get_page = function(options, callback) {
  http.request(options, function(res) {
    // Somewhat handle redirects (not really, but good enough):
    if (res.statusCode > 300 && res.statusCode < 400 && res.headers.location) {
      if (!options.headers){
        options.headers = {};
      }
      options.headers['cookie'] = res.headers["set-cookie"];
      options.path = res.headers.location;
      return get_page(options, callback);
    }

    res.setEncoding('utf8');
    let ret = "";
    res.on('data', function (chunk) {
      ret += chunk;
    });
    res.on('end', function() {
      callback(ret);
    });
  }).end();
};

// Download a user's inventory and call callback(inventory)
const get_user_inventory = function(id, start, callback) {
  const options = {
    host: 'steamcommunity.com',
    port: 80,
    path: '/id/' + id + '/inventory/json/753/6?start=' + start,
    method: 'GET'
  };
  get_page(options, function(ret) {
    callback(JSON.parse(ret));
  });
};

// Download the inventory page from http://steam.cards
const get_inventory_page = function(callback) {
  const options = {
    host: 'www.steamcardexchange.net',
    port: 80,
    path: '/index.php?inventory',
    method: 'GET'
  };
  get_page(options, callback);
};

const is_card = function(item) {
  const type = item['type'];
  return type.substring(type.length - 4, type.length) === "Card";
};

const parse_inventory_page_sets = function(data) {
  // Parse the game prices:
  let gameprices = data.substr(data.indexOf("var gameprices=")+15);
  gameprices = gameprices.substr(0, gameprices.indexOf(';'));
  gameprices = JSON.parse(gameprices);

  let stocklist = data.substr(data.indexOf("var stocklist=") + 14);
  stocklist = stocklist.substr(0, stocklist.indexOf(';'));
  stocklist = JSON.parse(stocklist);

  // Get a list of all games and the number of cards in each set:
  let sets = {};
  const regexp = new RegExp(/\([^\<]*)\<\/a\>\<\/td\>\<td id=\"price-(\d+)/g);
  let match;
  while (match = regexp.exec(data)) {
    // Convert game_id to an integer
    const game_id = ~~match[1];
    const nr_cards = stocklist[match[3]] || [undefined];
    sets[game_id] = {
      'game_name': match[2],
      'card_price': ~~(gameprices[match[3]]),
      'nr_cards': ~~(nr_cards[0])
    };
  }
  return sets;
}

// Start server
http.createServer(function(request, response) {
  if (request.url === "/favicon.ico") return;
  response.writeHeader(200, {"Content-Type": "text/plain"});
  let result = { };
  const search = request.url.substr(1).split(',')
  // 17710_2,317710_3,284770_6,212680_5,321260_9,72000_6
  for (const card in search) {
    //response.write("Looking for: " + search[card] + "\n");
    result[search[card]] = { 'count': 0, 'game': 'unknown', 'card': 'unknown' }
  }

  //CardExchange
  const search_inventory = function(start) {
    get_user_inventory('CardExchange', start, function(res) {
      let amount = {};
      const rgInventory = res['rgInventory'];
      for (const key in rgInventory) {
        const item_id = rgInventory[key]["classid"];
        if (amount[item_id] === undefined) {
          amount[item_id] = 0;
        }
        amount[item_id] += 1;
      }

      const descriptions = res['rgDescriptions'];
      for (const key in descriptions) {
        const app_data = descriptions[key]['app_data'];
        const card = app_data['appid'] + '_' + app_data['item_type'];
        if (card in result) {
          //response.write("Found card: " + card + "\n");
          result[card]['count'] = amount[descriptions[key]['classid']];
          result[card]['game'] = descriptions[key]['type'];
          result[card]['card'] = descriptions[key]['market_name'];
        } else {
          //response.write(card + "\n");
        }
      }
      if (res['more']) {
        //response.write("Going to next page: " + res['more_start'] + "...\n");
        setTimeout(function() {
          search_inventory(res['more_start'])
        }, 0);
      } else {
        for (const card in result) {
          const link =
            result[card]['count'] > 1 ?
            "http://www.steamcardexchange.net/index.php?inventorygame-appid-" + card :
            "";
          response.write(result[card]['count'] + " : " +
                         result[card]['game'] + " : " +
                         result[card]['card'] + " : " +
                         link + " : " + card + "\n");
        }
        response.end();
      }
    });
  }
  search_inventory(0);
}).listen(8080);

console.log("Server Running on http://localhost:8080");

// Start server
http.createServer(function(request, response) {
  if (request.url === "/favicon.ico") return;
  response.writeHeader(200, {"Content-Type": "text/plain"});
  const user_id = request.url.substr(1);
  if (user_id === "") {
    response.write("No user_id");
    response.end();
    return;
  }

  const parse_inventory_page = function(data) {
    // Get the current queue for the bot:
    let current_cueue = data.substr(data.indexOf("There are currently"));
    current_cueue = current_cueue.substr(0, current_cueue.indexOf('<'));
    response.write(current_cueue + "\n\n");

    // Parse all the card sets from the page:
    const sets = parse_inventory_page_sets(data);
    // Compare that to our inventory:
    get_user_inventory(user_id, 0, function(data) {
      let inventory = [];
      const descriptions = data['rgDescriptions'];
      let last_game_id;
      for (const key in descriptions) {
        if (!is_card(descriptions[key])) {
          continue;
        }
        const game_id = ~~(descriptions[key]['app_data']['appid']);
        let game;
        if ((game = sets[game_id]) !== undefined) {
          if (last_game_id === undefined || last_game_id !== game_id) {
            inventory.push({
              'game_id': game_id,
              'game_name': game['game_name'],
              'total_price' : game['card_price'] * game['nr_cards']
            });
            last_game_id = game_id;
          }
        } else {
           // response.write("Could not find game: " + game_id + "\n");
        }
      }

      // Sort the list by total price:
      inventory.sort(function(a, b) {
        return b['total_price'] - a['total_price'];
      });

      // Print the result:
      for (const card in inventory) {
        response.write(inventory[card]['game_name'] + " : " + inventory[card]['total_price'] + "\n");
      }

      response.end();
    });
  };
  get_inventory_page(parse_inventory_page);
}).listen(8081);

console.log("Server Running on http://localhost:8081");

<|endoftext|>


<commit_before>/*
 * Websensor video stabilization project
 * https://github.com/jessenie-intel/websensor-video
 *
 * Copyright (c) 2017 Jesse Nieminen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

'use strict';

var constraints = {audio: true,video: {  width: { min: 640, ideal: 640, max: 640 },  height: { min: 480, ideal: 480, max: 480 }}};
var mediaRecorder = null;
var chunks = [];
var videoData = null;
var interval = null;

var accel_sensor = null;
var orientation_sensor = null;
var sensorfreq = 60;

//var roll = null;
//var pitch = null;
//var yaw = null;
var accel = {"x": null, "y": null, "z": null};
var oriInitial = {"roll": null, "pitch": null, "yaw": null};
var orientationData = [];       //array to store all the orientation data
var initialoriobtained = false;

//canvas
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');

//video element
var videoElement = document.querySelector('video');
videoElement.controls = false;

class AbsOriSensor {
        constructor() {
        const sensor = new AbsoluteOrientationSensor({ frequency: sensorfreq });
        const mat4 = new Float32Array(16);
        const euler = new Float32Array(3);
        sensor.onreading = () => {
                sensor.populateMatrix(mat4);
                toEulerianAngle(sensor.quaternion, euler);      //From quaternion to Eulerian angles
                this.roll = euler[0];
                this.pitch = euler[1];
                this.yaw = euler[2];
                this.timestamp = sensor.timestamp;
                if (this.onreading) this.onreading();
        };
        sensor.onactivate = () => {
                if (this.onactivate) this.onactivate();
        };
        const start = () => sensor.start();
        Object.assign(this, { start });
        }
}

//WINDOWS 10 HAS DIFFERENT CONVENTION: Yaw z, pitch x, roll y
function toEulerianAngle(quat, out)
{
        const ysqr = quat[1] ** 2;

        // Roll (x-axis rotation).
        const t0 = 2 * (quat[3] * quat[0] + quat[1] * quat[2]);
        const t1 = 1 - 2 * (ysqr + quat[0] ** 2);
        out[0] = Math.atan2(t0, t1);
        // Pitch (y-axis rotation).
        let t2 = 2 * (quat[3] * quat[1] - quat[2] * quat[0]);
        t2 = t2 > 1 ? 1 : t2;
        t2 = t2 < -1 ? -1 : t2;
        out[1] = Math.asin(t2);
        // Yaw (z-axis rotation).
        const t3 = 2 * (quat[3] * quat[2] + quat[0] * quat[1]);
        const t4 = 1 - 2 * (ysqr + quat[2] ** 2);
        out[2] = Math.atan2(t3, t4);
        return out;
}

function errorCallback(error){
	console.log("error: ", error);
}

function startDemo () {
		navigator.getUserMedia(constraints, startRecording, errorCallback);
}

function startRecording(stream) {

                try {
                //Initialize sensors
                /*accel_sensor = new Accelerometer();
                accel_sensor.onreading = () => {
                        accel = {"x": accel_sensor.x, "y": accel_sensor.y, "z": accel_sensor.z};
                };
                accel_sensor.onactivate = () => {
                };
                accel_sensor.start();*/
                orientation_sensor = new AbsOriSensor();
                orientation_sensor.onreading = () => {
                        let roll = orientation_sensor.roll;
                        let pitch = orientation_sensor.pitch;
                        let yaw = orientation_sensor.yaw;
                        let timestamp = orientation_sensor.timestamp;
                        if(!initialoriobtained) //obtain initial orientation
                        {
                                oriInitial = {"roll:": orientation_sensor.roll, "pitch:": orientation_sensor.pitch, "yaw:": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                                initialoriobtained = true;
                        }
                        let orientation = {"roll:": orientation_sensor.roll, "pitch:": orientation_sensor.pitch, "yaw:": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                        orientationData.push(orientation);
                };
                orientation_sensor.onactivate = () => {
                };
                orientation_sensor.start();
                }
                catch(err) {
                        console.log(err.message);
                        console.log("Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags.");
                        //this.innerHTML = "Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags";
                }
	        //var options = {mimeType: 'video/webm;codecs=vp9'};
		//mediaRecorder = new MediaRecorder(stream, options);
		mediaRecorder = new MediaRecorder(stream);

	        mediaRecorder.start(10);

	        var url = window.URL;
	        videoElement.src = url ? url.createObjectURL(stream) : stream;
	        videoElement.play();

	        mediaRecorder.ondataavailable = function(e) {
                        //console.log("Data available", e);
		        chunks.push(e.data);
	        };

	        mediaRecorder.onerror = function(e){
		        console.log('Error: ', e);
	        };

	        mediaRecorder.onstart = function(){
                        console.log("Recording started", mediaRecorder.state);
	        };

	        mediaRecorder.onstop = function(){
		        var blob = new Blob(chunks, {type: "video/webm"});
		        chunks = [];

		        var videoURL = window.URL.createObjectURL(blob);

		        videoElement.src = videoURL;

                        //resize canvas
videoElement.addEventListener('loadedmetadata', function() {
  canvas.width = videoElement.videoWidth;
  canvas.height = videoElement.videoHeight;
});

                        //Read blob data so we can stabilize the video
                        /*var reader = new FileReader();
                          reader.onload = function(event){
                                let text = reader.result;
                            console.log(text);
                          };
                        reader.readAsText(blob);*/
                        stabilize();
                        //interval=window.setInterval(stabilize,20);
	        };

	        mediaRecorder.onpause = function(){
	        }

	        mediaRecorder.onresume = function(){
	        }

	        mediaRecorder.onwarning = function(e){
	        };
}

function stopRecording(){
	mediaRecorder.stop();
        //Now stabilize
        //stabilize(blob);
	videoElement.controls = true;
}

function stabilize() {     //Idea: copy video to canvas, operate on the video, and then use the canvas with the stabilized video as source for the video element
        console.log(orientationData);
        console.log(oriInitial);
        let x = 0;
        let y = 0;
        let width = 100;
        let height = 100;
        console.log(videoElement);
        let oriDiff = {"roll": -oriInitial.roll, "pitch": -oriInitial.pitch, "yaw": -oriInitial.yaw};
        ctx.drawImage(videoElement,0,0, videoElement.videoWidth, videoElement.videoHeight);
        ctx.beginPath();
        ctx.rect(x,y,width,height);
        ctx.stroke();

        //ctx.drawImage(videoElement, 0, 0);
        let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        let pixeldataArray = [];
        //loop through every pixel
        for(let i=0; i<imageData.data.length; i = i+4)
        {
                let pixeldata = {"red": imageData.data[i], "green":imageData.data[i+1], "blue": imageData.data[i+2], "alpha":imageData.data[i+3]};
                pixeldataArray.push(pixeldata);
        }
        //console.log(pixeldataArray);
        //newImageData.data = data;
    // Draw the pixels onto the visible canvas
    //ctx.putImageData(newImageData,0,0);
        x = x + oriDiff.roll;
        y = y + oriDiff.pitch;

        requestAnimationFrame(stabilize);
}
<commit_msg>Implementing algorithm - fix timestamp<commit_after>/*
 * Websensor video stabilization project
 * https://github.com/jessenie-intel/websensor-video
 *
 * Copyright (c) 2017 Jesse Nieminen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

'use strict';

var constraints = {audio: true,video: {  width: { min: 640, ideal: 640, max: 640 },  height: { min: 480, ideal: 480, max: 480 }}};
var mediaRecorder = null;
var chunks = [];
var videoData = null;
var interval = null;

var accel_sensor = null;
var orientation_sensor = null;
var sensorfreq = 60;

//var roll = null;
//var pitch = null;
//var yaw = null;
var accel = {"x": null, "y": null, "z": null};
var oriInitial = {"roll": null, "pitch": null, "yaw": null};
var orientationData = [];       //array to store all the orientation data
var initialoriobtained = false;

//canvas
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');

//video element
var videoElement = document.querySelector('video');
videoElement.controls = false;

class AbsOriSensor {
        constructor() {
        const sensor = new AbsoluteOrientationSensor({ frequency: sensorfreq });
        const mat4 = new Float32Array(16);
        const euler = new Float32Array(3);
        sensor.onreading = () => {
                sensor.populateMatrix(mat4);
                toEulerianAngle(sensor.quaternion, euler);      //From quaternion to Eulerian angles
                this.roll = euler[0];
                this.pitch = euler[1];
                this.yaw = euler[2];
                this.timestamp = sensor.timestamp;
                if (this.onreading) this.onreading();
        };
        sensor.onactivate = () => {
                if (this.onactivate) this.onactivate();
        };
        const start = () => sensor.start();
        Object.assign(this, { start });
        }
}

//WINDOWS 10 HAS DIFFERENT CONVENTION: Yaw z, pitch x, roll y
function toEulerianAngle(quat, out)
{
        const ysqr = quat[1] ** 2;

        // Roll (x-axis rotation).
        const t0 = 2 * (quat[3] * quat[0] + quat[1] * quat[2]);
        const t1 = 1 - 2 * (ysqr + quat[0] ** 2);
        out[0] = Math.atan2(t0, t1);
        // Pitch (y-axis rotation).
        let t2 = 2 * (quat[3] * quat[1] - quat[2] * quat[0]);
        t2 = t2 > 1 ? 1 : t2;
        t2 = t2 < -1 ? -1 : t2;
        out[1] = Math.asin(t2);
        // Yaw (z-axis rotation).
        const t3 = 2 * (quat[3] * quat[2] + quat[0] * quat[1]);
        const t4 = 1 - 2 * (ysqr + quat[2] ** 2);
        out[2] = Math.atan2(t3, t4);
        return out;
}

function errorCallback(error){
	console.log("error: ", error);
}

function startDemo () {
		navigator.getUserMedia(constraints, startRecording, errorCallback);
}

function startRecording(stream) {

                try {
                //Initialize sensors
                /*accel_sensor = new Accelerometer();
                accel_sensor.onreading = () => {
                        accel = {"x": accel_sensor.x, "y": accel_sensor.y, "z": accel_sensor.z};
                };
                accel_sensor.onactivate = () => {
                };
                accel_sensor.start();*/
                orientation_sensor = new AbsOriSensor();
                orientation_sensor.onreading = () => {
                        let roll = orientation_sensor.roll;
                        let pitch = orientation_sensor.pitch;
                        let yaw = orientation_sensor.yaw;
                        let timestamp = orientation_sensor.timestamp;
                        if(!initialoriobtained) //obtain initial orientation
                        {
                                oriInitial = {"roll:": orientation_sensor.roll, "pitch:": orientation_sensor.pitch, "yaw:": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                                initialoriobtained = true;
                        }
                        let orientation = {"roll:": orientation_sensor.roll, "pitch:": orientation_sensor.pitch, "yaw:": orientation_sensor.yaw, "time": orientation_sensor.timestamp};
                        orientationData.push(orientation);
                };
                orientation_sensor.onactivate = () => {
                };
                orientation_sensor.start();
                }
                catch(err) {
                        console.log(err.message);
                        console.log("Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags.");
                        //this.innerHTML = "Your browser doesn't seem to support generic sensors. If you are running Chrome, please enable it in about:flags";
                }
	        //var options = {mimeType: 'video/webm;codecs=vp9'};
		//mediaRecorder = new MediaRecorder(stream, options);
		mediaRecorder = new MediaRecorder(stream);

	        mediaRecorder.start(10);

	        var url = window.URL;
	        videoElement.src = url ? url.createObjectURL(stream) : stream;
	        videoElement.play();

	        mediaRecorder.ondataavailable = function(e) {
                        //console.log("Data available", e);
		        chunks.push(e.data);

	        };

	        mediaRecorder.onerror = function(e){
		        console.log('Error: ', e);
	        };

	        mediaRecorder.onstart = function(){
                        console.log("Recording started", mediaRecorder.state);
	        };

	        mediaRecorder.onstop = function(){
		        var blob = new Blob(chunks, {type: "video/webm"});
                        console.log(blob.timestamps);
		        chunks = [];

		        var videoURL = window.URL.createObjectURL(blob);

		        videoElement.src = videoURL;

                        //resize canvas
videoElement.addEventListener('loadedmetadata', function() {
  canvas.width = videoElement.videoWidth;
  canvas.height = videoElement.videoHeight;
});

                        //Read blob data so we can stabilize the video
                        /*var reader = new FileReader();
                          reader.onload = function(event){
                                let text = reader.result;
                            console.log(text);
                          };
                        reader.readAsText(blob);*/
                        stabilize();
                        //interval=window.setInterval(stabilize,20);
	        };

	        mediaRecorder.onpause = function(){
	        }

	        mediaRecorder.onresume = function(){
	        }

	        mediaRecorder.onwarning = function(e){
	        };
}

function stopRecording(){
	mediaRecorder.stop();
        //Now stabilize
        //stabilize(blob);
	videoElement.controls = true;
}

function stabilize() {     //Idea: copy video to canvas, operate on the video, and then use the canvas with the stabilized video as source for the video element
        //console.log(orientationData);
        //console.log(oriInitial);
        let x = 0;
        let y = 0;
        let width = 100;
        let height = 100;
                        //Read blob data so we can stabilize the video
                        /*var reader = new FileReader();
                          reader.onload = function(event){
                                let text = reader.result;
                            console.log(text);
                          };
                        reader.readAsText(blob);*/
        let oriDiff = {"roll": -oriInitial.roll, "pitch": -oriInitial.pitch, "yaw": -oriInitial.yaw};
        ctx.drawImage(videoElement,0,0, videoElement.videoWidth, videoElement.videoHeight);
        ctx.beginPath();
        ctx.rect(x,y,width,height);
        ctx.stroke();

        //ctx.drawImage(videoElement, 0, 0);
        let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        let pixeldataArray = [];
        //loop through every pixel
        for(let i=0; i<imageData.data.length; i = i+4)
        {
                let pixeldata = {"red": imageData.data[i], "green":imageData.data[i+1], "blue": imageData.data[i+2], "alpha":imageData.data[i+3]};
                pixeldataArray.push(pixeldata);
        }
        //console.log(pixeldataArray);
        //newImageData.data = data;
    // Draw the pixels onto the visible canvas
    //ctx.putImageData(newImageData,0,0);
        x = x + oriDiff.roll;
        y = y + oriDiff.pitch;

        requestAnimationFrame(stabilize);
}
<|endoftext|>


<commit_before>define(function(require) {

  var $ = require('jquery');
  var _ = require('underscore');
  var Backbone = require('backbone');
  var EntriesView = require('views/entries');
  var EditorView = require('views/editor');
  var docs = require('collections/docs');
  var cache = require('utils/cache');
  var settings = require('models/settings');

  var noDocs = function() {
    return docs.length < 2 && cache.openDoc.isEmpty();
  };

  var AppView = Backbone.View.extend({

    el: 'body',

    initialize: function() {
      this.editor = new EditorView();
      this.entries = new EntriesView();

      this.$menuButton = $('#menu-button');

      if (cache.isMobile) {

        cache.loading.done(_.bind(function() {
          //only one doc and it is empty
          if ( noDocs() ) {
            this.$menuButton.fadeOut();
          } else {
            this.aside('visible');
          }
        }, this));

      } else {

        this.aside('visible');

        setTimeout(_.bind(function() {
          this.aside('hidden');
        }, this), 3000);

      }

      docs.on('change:title', function() {
          setTimeout(_.bind(function() {
            if (cache.isMobile) {
              this.$menuButton[noDocs() ? 'fadeOut' : 'fadeIn']();
            } else {
              this.aside('hidden');
            }
          }, this), 1000);
      }, this);

      var showAside = function() {
        if (!cache.isMobile) this.aside('visible');
      };
      docs.on('add', showAside, this);
      docs.on('fetch', showAside, this);

      this.editor.$el.scroll(_.bind(this.toggleMenuButton, this));
      this.entries.$el.scroll(_.bind(this.toggleMenuButton, this));

    },

    aside: (function() {
      var $aside = this.$('aside');
      return function(state) {
        if (!state) {
          $aside.toggleClass('visible');
        } else if (state === 'visible') {
          $aside.addClass('visible');
        } else if (state === 'hidden') {
          //hide sidebar when 3 or more docs and the open doc is not empty
          if (docs.length > 2 && !cache.openDoc.isEmpty()) {
            $aside.removeClass('visible');
          }
        }
      };
    })(),

    events: {
      'click #add': 'newDoc',
      'click #aside': 'toggleAside',
      'click #menu-button': 'toggleAside',
      'keydown': 'handleKey',
      'keyup': 'hideAside'
    },

    newDoc: function(e) {
      e.preventDefault();

      if (!cache.openDoc.isEmpty()) {
        docs.addNew();
      } else {
        this.editor.focus();
      }

    },

    toggleAside: function(e) {
      if (cache.isMobile) {
        e.stopImmediatePropagation();
        this.$menuButton.fadeIn();
        this.aside();
      }
    },

    toggleMenuButton: function(e) {
      if (cache.isMobile) {
        var top = $(e.currentTarget).scrollTop();
        this.$menuButton[top > 20 ? 'fadeOut' : 'fadeIn' ]();
      }
    },

    handleKey: function(e) {
      if (e.which === 9) { //tab
        e.preventDefault();
      } else if (e[cache.modKey.name]) {
        this.aside('visible');
        if (e.which === 78) { //n
          this.newDoc(e);
        } else if (e.which === 38) { //up
          this.openPreviousDoc();
          return false;
        } else if (e.which === 40) { //down
          this.openNextDoc();
          return false;
        }
      }
    },

    hideAside: function(e) {
      if (e.which === (cache.modKey.code)) this.aside('hidden');
    },

    openPreviousDoc: function() {
      var previousDoc = docs.at(docs.indexOf(cache.openDoc) - 1);
      if (previousDoc) {
        settings.save('openDocId', previousDoc.id);
      }
    },

    openNextDoc: function() {
      var nextDoc = docs.at(docs.indexOf(cache.openDoc) + 1);
      if (nextDoc) {
        settings.save('openDocId', nextDoc.id);
      }
    }
  });

  return AppView;
});
<commit_msg>also hide remoteStorage widget on scrolling down, just like menu button<commit_after>define(function(require) {

  var $ = require('jquery');
  var _ = require('underscore');
  var Backbone = require('backbone');
  var EntriesView = require('views/entries');
  var EditorView = require('views/editor');
  var docs = require('collections/docs');
  var cache = require('utils/cache');
  var settings = require('models/settings');

  var noDocs = function() {
    return docs.length < 2 && cache.openDoc.isEmpty();
  };

  var AppView = Backbone.View.extend({

    el: 'body',

    initialize: function() {
      this.editor = new EditorView();
      this.entries = new EntriesView();

      this.$menuButton = $('#menu-button');

      if (cache.isMobile) {

        cache.loading.done(_.bind(function() {
          //only one doc and it is empty
          if ( noDocs() ) {
            this.$menuButton.fadeOut();
          } else {
            this.aside('visible');
          }
        }, this));

      } else {

        this.aside('visible');

        setTimeout(_.bind(function() {
          this.aside('hidden');
        }, this), 3000);

      }

      docs.on('change:title', function() {
          setTimeout(_.bind(function() {
            if (cache.isMobile) {
              this.$menuButton[noDocs() ? 'fadeOut' : 'fadeIn']();
            } else {
              this.aside('hidden');
            }
          }, this), 1000);
      }, this);

      var showAside = function() {
        if (!cache.isMobile) this.aside('visible');
      };
      docs.on('add', showAside, this);
      docs.on('fetch', showAside, this);

      this.editor.$el.scroll(_.bind(this.toggleHeader, this));
      this.entries.$el.scroll(_.bind(this.toggleHeader, this));

    },

    aside: (function() {
      var $aside = this.$('aside');
      return function(state) {
        if (!state) {
          $aside.toggleClass('visible');
        } else if (state === 'visible') {
          $aside.addClass('visible');
        } else if (state === 'hidden') {
          //hide sidebar when 3 or more docs and the open doc is not empty
          if (docs.length > 2 && !cache.openDoc.isEmpty()) {
            $aside.removeClass('visible');
          }
        }
      };
    })(),

    events: {
      'click #add': 'newDoc',
      'click #aside': 'toggleAside',
      'click #menu-button': 'toggleAside',
      'keydown': 'handleKey',
      'keyup': 'hideAside'
    },

    newDoc: function(e) {
      e.preventDefault();

      if (!cache.openDoc.isEmpty()) {
        docs.addNew();
      } else {
        this.editor.focus();
      }

    },

    toggleAside: function(e) {
      if (cache.isMobile) {
        e.stopImmediatePropagation();
        this.$menuButton.fadeIn();
        this.aside();
      }
    },

    toggleHeader: function(e) {
      if (cache.isMobile) {
        var top = $(e.currentTarget).scrollTop();
        this.$menuButton[top > 20 ? 'fadeOut' : 'fadeIn' ]();
        $('#remotestorage-connect')[top > 20 ? 'fadeOut' : 'fadeIn' ]();
      }
    },

    handleKey: function(e) {
      if (e.which === 9) { //tab
        e.preventDefault();
      } else if (e[cache.modKey.name]) {
        this.aside('visible');
        if (e.which === 78) { //n
          this.newDoc(e);
        } else if (e.which === 38) { //up
          this.openPreviousDoc();
          return false;
        } else if (e.which === 40) { //down
          this.openNextDoc();
          return false;
        }
      }
    },

    hideAside: function(e) {
      if (e.which === (cache.modKey.code)) this.aside('hidden');
    },

    openPreviousDoc: function() {
      var previousDoc = docs.at(docs.indexOf(cache.openDoc) - 1);
      if (previousDoc) {
        settings.save('openDocId', previousDoc.id);
      }
    },

    openNextDoc: function() {
      var nextDoc = docs.at(docs.indexOf(cache.openDoc) + 1);
      if (nextDoc) {
        settings.save('openDocId', nextDoc.id);
      }
    }
  });

  return AppView;
});
<|endoftext|>


<commit_before>
// ------------------------------------------
// Cssllax.js - v1.0.0
// CSS property parallax library
// Copyright (c) 2017 Luke Butler
// MIT license
//
// Thanks to Moe Amaya and his Rellax.js library for inspiration
// ------------------------------------------

(function (root, lib) {
  if (typeof define === 'function' && define.amd) {
    define([], lib); // Register as an anonymous module.
  } else if (typeof module === 'object' && module.exports) {
    // Only works with CommonJS-like environments that support module.exports, like Node
    module.exports = lib();
  } else {
    root.Cssllax = lib(); // Register as browser global
  }
}(this, function () {
  var Cssllax = function () {
    'use strict';
    var posY = 0;
    var csslaxxItems = [];

    // check what requestAnimationFrame to use, and if
    // it's not supported, use setTimeout
    var loop = window.requestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.msRequestAnimationFrame ||
      window.oRequestAnimationFrame ||
      function (callback) { setTimeout(callback, 1000 / 60); };

    // Returns the element's percentage down viewport
    var percentageDownViewport = function (el) {
      // Get bounding box
      var rect = el.getBoundingClientRect();

      if (rect.top >= window.innerHeight) {
        // the element is below the viewport
        return 100;
      } else if (rect.bottom <= 0) {
        // the element is above the viewport
        return 0;
      }

      var distanceFromBottom = window.innerHeight - rect.top;
      // Preceding with "100 -" to flip the percentage output
      return 100 - (distanceFromBottom / ((window.innerHeight + rect.height) / 100));
    }

    // Add a cssllax item
    var add = function (selector, options) {
      // Set defaults
      var min = 0;
      var mid = 50;
      var max = 100;
      var unit = '%';
      var afterHook = false;

      options = options === undefined ? {} : options;

      if (typeof options === 'string') {
        // If options is a string, treat it as a css property
        var property = options;
      } else if (typeof options === 'object') {
        // Set values
        min = options.min === undefined ? min : options.min;
        max = options.max === undefined ? max : options.max;
        unit = options.unit === undefined ? unit : options.unit;
        // Calculate middle if one is not provided
        mid = options.mid === undefined ? ((max - min) / 2) + min : options.mid;
        // If property isn't set, leave the value undefined
        var property = options.property === undefined ? undefined : options.property;
        // Set the after hook if one was provided
        if (options.after && typeof options.after === 'function') {
          afterHook = options.after;
        }
      } else if (typeof options === 'function') {
        var customAction = options;
      }

      // If no css property or custom action is set, there's nothing to do
      if (property === undefined && !customAction) return false;

      // Define ranges for the css property value (top and bottom half of page)
      var range = [mid - min, max - mid];

      var getTargetSize = function (position) {
        if (position < 50) {
          // Top half of page
          return (range[0] * (position / 50)) + min;
        } else {
          // Bottom half of page
          return (range[1] * ((position - 50) / 50)) + mid;
        }
      }

      // get all items that match the selector
      var elems = document.querySelectorAll(selector);

      var item = function (el) {
        // Main animate
        var animate = function () {
          // Element position in viewport
          var position = percentageDownViewport(el);
          // Get the target value
          var targetValue = getTargetSize(position);
          // Set the css property
          el.style[property] = targetValue + unit;
          // Execute after hook if one was provided
          if (afterHook !== false) afterHook(el, position, targetValue);
        }

        // Custom animate
        var customAnimate = function () {
          customAction(el, percentageDownViewport(el));
        }

        // Return animate function
        return {
          animate: (typeof customAction === 'function')
            ? customAnimate // If customAction is set, it will run instead of animate
            : animate
          }
      }

      // Add each document item's update function to the array
      for (var i = 0; i < elems.length; i++) {
        csslaxxItems.push(item(elems[i]));
      }

      // Trigger the first run manually
      animate();
    }

    // Set the scrollTop position (for detecting scroll updates)
    var setYPosition = function () {
      var oldY = posY;

      posY = window.pageYOffset !== undefined
        ? window.pageYOffset
        : (document.documentElement || document.body.parentNode || document.body).scrollTop;

      // Has scroll changed?
      return oldY != posY ? true : false;
    }

    // Animate all elements
    var animate = function () {
      if (csslaxxItems.length) {
        for (var i = 0; i < csslaxxItems.length; i++) {
          // Call the animate function on each item
          csslaxxItems[i].animate();
        }
      }
    }

    var update = function () {
      // Only trigger animate when there are scroll updates
      if (setYPosition()) animate();
      // Keep the update loop going
      loop(update);
    }

    var init = function () {
      // Start the loop
      update();

      // Return function that allows for adding elements
      return add;
    }

    // add an accessible update function
    add.update = animate;

    return init();
  }
  return Cssllax();
}));<commit_msg>Custom browser center position<commit_after>
// ------------------------------------------
// Cssllax.js - v1.0.0
// CSS property parallax library
// Copyright (c) 2017 Luke Butler
// MIT license
//
// Thanks to Moe Amaya and his Rellax.js library for inspiration
// ------------------------------------------

(function (root, lib) {
  if (typeof define === 'function' && define.amd) {
    define([], lib); // Register as an anonymous module.
  } else if (typeof module === 'object' && module.exports) {
    // Only works with CommonJS-like environments that support module.exports, like Node
    module.exports = lib();
  } else {
    root.Cssllax = lib(); // Register as browser global
  }
}(this, function () {
  var Cssllax = function () {
    'use strict';
    var posY = 0;
    var csslaxxItems = [];

    // check what requestAnimationFrame to use, and if
    // it's not supported, use setTimeout
    var loop = window.requestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.msRequestAnimationFrame ||
      window.oRequestAnimationFrame ||
      function (callback) { setTimeout(callback, 1000 / 60); };

    // Returns the element's percentage down viewport
    var percentageDownViewport = function (el) {
      // Get bounding box
      var rect = el.getBoundingClientRect();

      if (rect.top >= window.innerHeight) {
        // the element is below the viewport
        return 100;
      } else if (rect.bottom <= 0) {
        // the element is above the viewport
        return 0;
      }

      var distanceFromBottom = window.innerHeight - rect.top;
      // Preceding with "100 -" to flip the percentage output
      return 100 - (distanceFromBottom / ((window.innerHeight + rect.height) / 100));
    }

    // Add a cssllax item
    var add = function (selector, options) {
      // Set defaults
      var min = 0;
      var mid = 50;
      var max = 100;
      var center = 50;
      var unit = '%';
      var afterHook = false;

      options = options === undefined ? {} : options;

      if (typeof options === 'string') {
        // If options is a string, treat it as a css property
        var property = options;
      } else if (typeof options === 'object') {
        // Set values
        min = options.min === undefined ? min : options.min;
        max = options.max === undefined ? max : options.max;
        unit = options.unit === undefined ? unit : options.unit;
        // Calculate middle if one is not provided
        mid = options.mid === undefined ? ((max - min) / 2) + min : options.mid;
        // Define what cssllax considers the center of the viewport
        center = options.center === undefined ? center : options.center;
        // If property isn't set, leave the value undefined
        var property = options.property === undefined ? undefined : options.property;
        // Set the after hook if one was provided
        if (options.after && typeof options.after === 'function') {
          afterHook = options.after;
        }
      } else if (typeof options === 'function') {
        var customAction = options;
      }

      // If no css property or custom action is set, there's nothing to do
      if (property === undefined && !customAction) return false;

      // Define ranges for the css property value (top and bottom half of page)
      var range = [mid - min, max - mid];

      var getTargetSize = function (position) {
        if (position < center) {
          // Top half of page
          return (range[0] * (position / center)) + min;
        } else {
          // Bottom half of page
          return (range[1] * ((position - center) / (100 - center))) + mid;
        }
      }

      // get all items that match the selector
      var elems = document.querySelectorAll(selector);

      var item = function (el) {
        // Main animate
        var animate = function () {
          // Element position in viewport
          var position = percentageDownViewport(el);
          // Get the target value
          var targetValue = getTargetSize(position);
          // Set the css property
          el.style[property] = targetValue + unit;
          // Execute after hook if one was provided
          if (afterHook !== false) afterHook(el, position, targetValue);
        }

        // Custom animate
        var customAnimate = function () {
          customAction(el, percentageDownViewport(el));
        }

        // Return animate function
        return {
          animate: (typeof customAction === 'function')
            ? customAnimate // If customAction is set, it will run instead of animate
            : animate
          }
      }

      // Add each document item's update function to the array
      for (var i = 0; i < elems.length; i++) {
        csslaxxItems.push(item(elems[i]));
      }

      // Trigger the first run manually
      animate();
    }

    // Set the scrollTop position (for detecting scroll updates)
    var setYPosition = function () {
      var oldY = posY;

      posY = window.pageYOffset !== undefined
        ? window.pageYOffset
        : (document.documentElement || document.body.parentNode || document.body).scrollTop;

      // Has scroll changed?
      return oldY != posY ? true : false;
    }

    // Animate all elements
    var animate = function () {
      if (csslaxxItems.length) {
        for (var i = 0; i < csslaxxItems.length; i++) {
          // Call the animate function on each item
          csslaxxItems[i].animate();
        }
      }
    }

    var update = function () {
      // Only trigger animate when there are scroll updates
      if (setYPosition()) animate();
      // Keep the update loop going
      loop(update);
    }

    var init = function () {
      // Start the loop
      update();

      // Return function that allows for adding elements
      return add;
    }

    // add an accessible update function
    add.update = animate;

    return init();
  }
  return Cssllax();
}));<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// TODO(kgr): remove use of SimpleValue, just use data$ binding instead.
CLASS({
  package: 'foam.ui',
  name: 'DAOListView',

  requires: ['SimpleValue'],

  extendsModel: 'foam.ui.SimpleView',
  traits: ['foam.ui.DAODataViewTrait'],

  constants: {
    ROW_CLICK: ['row-click']
  },

  properties: [
    {
      model_: 'BooleanProperty',
      name: 'isHidden',
      defaultValue: false,
      postSet: function(_, isHidden) {
        if ( this.dao && ! isHidden ) this.onDAOUpdate();
      }
    },
    {
      model_: 'ViewFactoryProperty',
      name: 'rowView',
      defaultValue: 'foam.ui.DetailView'
    },
    {
      name: 'mode',
      defaultValue: 'read-write',
      view: { factory_: 'foam.ui.ChoiceView', choices: ['read-only', 'read-write', 'final'] }
    },
    {
      name: 'useSelection',
      help: 'Backward compatibility for selection mode. Create a X.selection$ value in your context instead.',
      postSet: function(old, nu) {
        if ( this.useSelection && !this.X.selection$ ) this.X.selection$ = this.SimpleValue.create();
        this.selection$ = this.X.selection$;
      }
    },
    {
      name: 'selection',
      help: 'Backward compatibility for selection mode. Create a X.selection$ value in your context instead.',
      factory: function() {
        return this.SimpleValue.create();
      }
    },
    {
      name: 'scrollContainer',
      help: 'Containing element that is responsible for scrolling.'
    },
    {
      name: 'chunkSize',
      defaultValue: 0,
      help: 'Number of entries to load in each infinite scroll chunk.'
    },
    {
      name: 'chunksLoaded',
      isHidden: true,
      defaultValue: 1,
      help: 'The number of chunks currently loaded.'
    },
    {
      model_: 'BooleanProperty',
      name: 'painting',
      defaultValue: false,
      transient: true
    },
    {
      model_: 'BooleanProperty',
      name: 'repaintRequired',
      defaultValue: false,
      transient: true
    }
  ],

  methods: {
    init: function() {
      this.SUPER();

      var self = this;
      this.subscribe(this.ON_HIDE, function() {
        self.isHidden = true;
      });

      this.subscribe(this.ON_SHOW, function() {
        self.isHidden = false;
      });

      // bind to selection, if present
      if (this.X.selection$) {
        this.selection$ = this.X.selection$;
      }
    },

    initHTML: function() {
      this.SUPER();

      // If we're doing infinite scrolling, we need to find the container.
      // Either an overflow: scroll element or the window.
      // We keep following the parentElement chain until we get null.
      if ( this.chunkSize > 0 ) {
        var e = this.$;
        while ( e ) {
          if ( window.getComputedStyle(e).overflow === 'scroll' ) break;
          e = e.parentElement;
        }
        this.scrollContainer = e || window;
        this.scrollContainer.addEventListener('scroll', this.onScroll, false);
      }

      if ( ! this.isHidden ) this.updateHTML();
    },

    construct: function() {
      if ( ! this.dao || ! this.$ ) return;
      if ( this.painting ) {
        this.repaintRequired = true;
        return;
      }
      this.painting = true;
      var out = [];
      this.children = [];
      this.initializers_ = [];

      var doneFirstItem = false;
      var d = this.dao;
      if ( this.chunkSize ) {
        d = d.limit(this.chunkSize * this.chunksLoaded);
      }
      d.select({put: function(o) {
        if ( this.mode === 'read-write' ) o = o.model_.create(o, this.Y); //.clone();
        var view = this.rowView({data: o, model: o.model_}, this.Y);
        // TODO: Something isn't working with the Context, fix
        view.DAO = this.dao;
        if ( this.mode === 'read-write' ) {
          o.addPropertyListener(null, function(o, topic) {
            var prop = o.model_.getProperty(topic[1]);
            // TODO(kgr): remove the deepClone when the DAO does this itself.
            if ( ! prop.transient ) {
              // TODO: if o.id changed, remove the old one?
              view.DAO.put(o.deepClone());
            }
          });
        }
        this.addChild(view);

        if ( ! doneFirstItem ) {
          doneFirstItem = true;
        } else {
          this.separatorToHTML(out); // optional separator
        }

        if ( this.X.selection$ ) {
          var itemId = this.on('click', (function() {
            this.selection = o;
            this.publish(this.ROW_CLICK);
          }).bind(this))
          this.setClass('dao-selected', function() { return equals(this.selection, o); }.bind(this), itemId);
          this.setClass(this.className + '-row', function() { return true; }, itemId);
          out.push('
');
        }
        out.push(view.toHTML());
        if ( this.X.selection$ ) {
          out.push('
');
        }
      }.bind(this)})(function() {
        if (this.repaintRequired) {
          this.repaintRequired = false;
          this.painting = false;
          this.realDAOUpdate();
          return;
        }

        var e = this.$;

        if ( ! e ) return;

        e.innerHTML = out.join('');
        this.initInnerHTML();
        this.painting = false;
      }.bind(this));
    },

    /** Allow rowView to be optional when defined using HTML. **/
    fromElement: function(e) {
      var children = e.children;
      if ( children.length == 1 && children[0].nodeName === 'rowView' ) {
        this.SUPER(e);
      } else {
        this.rowView = e.innerHTML;
      }
    },

    // Template method
    separatorToHTML: function(out) {
      /* Template method. Override to provide a separator if required. This
      method is called before each list item, except the first. Use
      out.push("<myhtml>...") for efficiency. */
    }
  },

  listeners: [
    {
      name: 'onDAOUpdate',
      code: function() {
        this.realDAOUpdate();
      }
    },
    {
      name: 'realDAOUpdate',
      isFramed: true,
      code: function() {
        if ( ! this.isHidden ) this.updateHTML();
      }
    },
    {
      name: 'onScroll',
      code: function() {
        var e = this.scrollContainer;
        if ( this.chunkSize > 0 && e.scrollTop + e.offsetHeight >= e.scrollHeight ) {
          this.chunksLoaded++;
          this.updateHTML();
        }
      }
    }
  ]
});
<commit_msg>DAOListView: Unhook old listeners on destroy(). Fix for #381<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// TODO(kgr): remove use of SimpleValue, just use data$ binding instead.
CLASS({
  package: 'foam.ui',
  name: 'DAOListView',

  requires: ['SimpleValue'],

  extendsModel: 'foam.ui.SimpleView',
  traits: ['foam.ui.DAODataViewTrait'],

  constants: {
    ROW_CLICK: ['row-click']
  },

  properties: [
    {
      model_: 'BooleanProperty',
      name: 'isHidden',
      defaultValue: false,
      postSet: function(_, isHidden) {
        if ( this.dao && ! isHidden ) this.onDAOUpdate();
      }
    },
    {
      model_: 'ViewFactoryProperty',
      name: 'rowView',
      defaultValue: 'foam.ui.DetailView'
    },
    {
      name: 'mode',
      defaultValue: 'read-write',
      view: { factory_: 'foam.ui.ChoiceView', choices: ['read-only', 'read-write', 'final'] }
    },
    {
      name: 'useSelection',
      help: 'Backward compatibility for selection mode. Create a X.selection$ value in your context instead.',
      postSet: function(old, nu) {
        if ( this.useSelection && !this.X.selection$ ) this.X.selection$ = this.SimpleValue.create();
        this.selection$ = this.X.selection$;
      }
    },
    {
      name: 'selection',
      help: 'Backward compatibility for selection mode. Create a X.selection$ value in your context instead.',
      factory: function() {
        return this.SimpleValue.create();
      }
    },
    {
      name: 'scrollContainer',
      help: 'Containing element that is responsible for scrolling.'
    },
    {
      name: 'chunkSize',
      defaultValue: 0,
      help: 'Number of entries to load in each infinite scroll chunk.'
    },
    {
      name: 'chunksLoaded',
      isHidden: true,
      defaultValue: 1,
      help: 'The number of chunks currently loaded.'
    },
    {
      model_: 'BooleanProperty',
      name: 'painting',
      defaultValue: false,
      transient: true
    },
    {
      model_: 'BooleanProperty',
      name: 'repaintRequired',
      defaultValue: false,
      transient: true
    },
    {
      model_: 'ArrayProperty',
      name: 'propertyListeners_',
      lazyFactory: function() { return []; }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();

      var self = this;
      this.subscribe(this.ON_HIDE, function() {
        self.isHidden = true;
      });

      this.subscribe(this.ON_SHOW, function() {
        self.isHidden = false;
      });

      // bind to selection, if present
      if (this.X.selection$) {
        this.selection$ = this.X.selection$;
      }
    },

    initHTML: function() {
      this.SUPER();

      // If we're doing infinite scrolling, we need to find the container.
      // Either an overflow: scroll element or the window.
      // We keep following the parentElement chain until we get null.
      if ( this.chunkSize > 0 ) {
        var e = this.$;
        while ( e ) {
          if ( window.getComputedStyle(e).overflow === 'scroll' ) break;
          e = e.parentElement;
        }
        this.scrollContainer = e || window;
        this.scrollContainer.addEventListener('scroll', this.onScroll, false);
      }

      if ( ! this.isHidden ) this.updateHTML();
    },

    construct: function() {
      if ( ! this.dao || ! this.$ ) return;
      if ( this.painting ) {
        this.repaintRequired = true;
        return;
      }
      this.painting = true;
      var out = [];
      this.children = [];
      this.initializers_ = [];

      var doneFirstItem = false;
      var d = this.dao;
      if ( this.chunkSize ) {
        d = d.limit(this.chunkSize * this.chunksLoaded);
      }
      d.select({put: function(o) {
        if ( this.mode === 'read-write' ) o = o.model_.create(o, this.Y); //.clone();
        var view = this.rowView({data: o, model: o.model_}, this.Y);
        // TODO: Something isn't working with the Context, fix
        view.DAO = this.dao;
        if ( this.mode === 'read-write' ) {
          this.addRowPropertyListener(o, view);
        }
        this.addChild(view);

        if ( ! doneFirstItem ) {
          doneFirstItem = true;
        } else {
          this.separatorToHTML(out); // optional separator
        }

        if ( this.X.selection$ ) {
          var itemId = this.on('click', (function() {
            this.selection = o;
            this.publish(this.ROW_CLICK);
          }).bind(this))
          this.setClass('dao-selected', function() { return equals(this.selection, o); }.bind(this), itemId);
          this.setClass(this.className + '-row', function() { return true; }, itemId);
          out.push('
');
        }
        out.push(view.toHTML());
        if ( this.X.selection$ ) {
          out.push('
');
        }
      }.bind(this)})(function() {
        if (this.repaintRequired) {
          this.repaintRequired = false;
          this.painting = false;
          this.realDAOUpdate();
          return;
        }

        var e = this.$;

        if ( ! e ) return;

        e.innerHTML = out.join('');
        this.initInnerHTML();
        this.painting = false;
      }.bind(this));
    },

    destroy: function(isParentDestroyed) {
      var listeners = this.propertyListeners_;
      for ( var i = 0; i < listeners.length; ++i ) {
        listeners[i].data.removePropertyListener(null, listeners[i].listener);
      }
      this.propertyListeners_ = [];
      return this.SUPER(isParentDestroyed);
    },

    /** Allow rowView to be optional when defined using HTML. **/
    fromElement: function(e) {
      var children = e.children;
      if ( children.length == 1 && children[0].nodeName === 'rowView' ) {
        this.SUPER(e);
      } else {
        this.rowView = e.innerHTML;
      }
    },

    // Template method
    separatorToHTML: function(out) {
      /* Template method. Override to provide a separator if required. This
      method is called before each list item, except the first. Use
      out.push("<myhtml>...") for efficiency. */
    },

    addRowPropertyListener: function(data, view) {
      var listener = function(o, topic) {
        var prop = o.model_.getProperty(topic[1]);
        // TODO(kgr): remove the deepClone when the DAO does this itself.
        if ( ! prop.transient ) {
          // TODO: if o.id changed, remove the old one?
          view.DAO.put(o.deepClone());
        }
      };
      data.addPropertyListener(null, listener);
      this.propertyListeners_.push({ data: data, listener: listener });
    }
  },

  listeners: [
    {
      name: 'onDAOUpdate',
      code: function() {
        this.realDAOUpdate();
      }
    },
    {
      name: 'realDAOUpdate',
      isFramed: true,
      code: function() {
        if ( ! this.isHidden ) this.updateHTML();
      }
    },
    {
      name: 'onScroll',
      code: function() {
        var e = this.scrollContainer;
        if ( this.chunkSize > 0 && e.scrollTop + e.offsetHeight >= e.scrollHeight ) {
          this.chunksLoaded++;
          this.updateHTML();
        }
      }
    }
  ]
});
<|endoftext|>