<commit_before>/**
 * ownCloud - Music app
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author <NAME> <<EMAIL>>
 * @copyright 2019 <NAME>
 *
 */

/**
 * This directive observes whether the host element is within the view-port or not.
 * When the status changes to either direction, it notifies the registered listeners.
 */
angular.module('Music').directive('inViewObserver', ['$rootScope', '$timeout', 'inViewService',
function($rootScope, $timeout, inViewService) {

	var _instances = []; // in creation order i.e. top-most first
	var _firstIndexInView = 0;
	var _lastIndexInView = -1;

	// Drop all instances when view switching begins
	$rootScope.$on('deactivateView', function() {
		// cancel any pending notifications first
		_(_instances).each(function(inst) {
			if (inst.promise) {
				$timeout.cancel(inst.promise);
			}
		});

		_instances = [];
		_firstIndexInView = 0;
		_lastIndexInView = -1;
	});

	var throttledOnScroll = _.throttle(onScroll, 50, {leading: false});

	document.getElementById('app-content').addEventListener('scroll', throttledOnScroll);
	$rootScope.$on('resize', throttledOnScroll);
	$rootScope.$on('trackListCollapsed', throttledOnScroll);

	function onScroll() {
		// do not react while the layout building is still ongoing
		if (!$rootScope.loading) {
			if (!validInViewRange()) {
				initInViewRange();
			} else {
				updateInViewRange();
			}
		}
	}

	function validInViewRange() {
		return _firstIndexInView <= _lastIndexInView;
	}

	/**
	 * Initial setup of the in-view-port statuses of the available instances
	 */
	function initInViewRange() {
		var length = _instances.length;
		var i;

		// loop from the begining until we find the first instance in viewport
		for (i = 0; i < length; ++i) {
			if (updateInViewStatus(_instances[i])) {
				_firstIndexInView = i;
				_lastIndexInView = i;
				break;
			}
		}

		// if some instance was found, then continue looping until we have found
		// all the instances in viewport
		for (++i; i < length; ++i) {
			if (updateInViewStatus(_instances[i])) {
				_lastIndexInView = i;
			} else {
				break;
			}
		}
	}

	/**
	 * Update in-view-port status when we have a valid previous in-view-range
	 */
	function updateInViewRange() {
		var prevFirst = _firstIndexInView;
		var prevLast = _lastIndexInView;
		var i;
		var length = _instances.length;

		// Check if instances in the beginning of the range have slided off
		for (i = _firstIndexInView; i <= _lastIndexInView; ++i) {
			if (!updateInViewStatus(_instances[i])) {
				++_firstIndexInView;
			} else {
				break;
			}
		}

		// Check if instances in the end of the range have slided off
		for (i = _lastIndexInView; i > _firstIndexInView; --i) {
			if (!updateInViewStatus(_instances[i])) {
				--_lastIndexInView;
			} else {
				break;
			}
		}

		if (!validInViewRange()) {
			// None of the previous instances were anymore in the view-port.
			// We have lost the track of the scrolling direction and location
			// and have to start from scratch.
			initInViewRange();
		}
		else {
			// There may be more in-view instances above if the first index
			// did not move downwards
			if (prevFirst === _firstIndexInView) {
				for (i = _firstIndexInView - 1; i >= 0; --i) {
					if (updateInViewStatus(_instances[i])) {
						--_firstIndexInView;
					} else {
						break;
					}
				}
			}

			// There may be more in-view instances below if the last index
			// did not move upwards
			if (prevLast === _lastIndexInView) {
				for (i = _lastIndexInView + 1; i < length; ++i) {
					if (updateInViewStatus(_instances[i])) {
						++_lastIndexInView;
					} else {
						break;
					}
				}
			}
		}
	}

	/**
	 * Update in-view-port status of the given instance
	 */
	function updateInViewStatus(inst) {
		var elem = inst.element;
		var nowInViewPort = elemInViewPort(elem);
		var wasInViewPort = inst.inViewPort;

		if (inst.promise) {
			if (!nowInViewPort) {
				// element left the viewport before onEnterView was called, cancel the pending call
				$timeout.cancel(inst.promise);
				inst.promise = null;
			}
		}
		else if (!wasInViewPort && nowInViewPort) {
			// element entered the viewport, notify the listeners with small delay
			inst.promise = $timeout(onEnterView, 250, true, inst);
		}
		else if (wasInViewPort && !nowInViewPort) {
			// element left the viewport after it had been notified that it has entered the view
			onLeaveView(inst);
		}

		return nowInViewPort;
	}

	function elemInViewPort(elem) {
		return inViewService.isElementInViewPort(elem, 500, 500);
	}

	function onEnterView(inst) {
		inst.promise = null;
		inst.inViewPort = true;
		_(inst.listeners).each(function(listener) {
			listener.onEnterView();
		});
	}

	function onLeaveView(inst) {
		inst.inViewPort = false;
		_(inst.listeners).each(function(listener) {
			listener.onLeaveView();
		});
	}

	return {
		scope: {},
		controller: function($scope, $element) {
			this.inViewPort = false;
			this.element = $element[0];
			this.listeners = [];

			/**
			 * Listener must have two function-type properties: onEnterView and onLeaveView<commit_msg>Delay "leaveView" notifications until when scrolling stops<commit_after>/**
 * ownCloud - Music app
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author <NAME> <<EMAIL>>
 * @copyright 2019 <NAME>
 *
 */

/**
 * This directive observes whether the host element is within the view-port or not.
 * When the status changes to either direction, it notifies the registered listeners.
 */
angular.module('Music').directive('inViewObserver', ['$rootScope', '$timeout', 'inViewService',
function($rootScope, $timeout, inViewService) {

	var _instances = []; // in creation order i.e. top-most first
	var _firstIndexInView = 0;
	var _lastIndexInView = -1;

	// Drop all instances when view switching begins
	$rootScope.$on('deactivateView', function() {
		// cancel any pending notifications first
		_(_instances).each(function(inst) {
			if (inst.pendingEnterView) {
				$timeout.cancel(inst.pendingEnterView);
			}
		});

		_instances = [];
		_firstIndexInView = 0;
		_lastIndexInView = -1;
	});

	var throttledOnScroll = _.throttle(onScroll, 50, {leading: false});

	document.getElementById('app-content').addEventListener('scroll', throttledOnScroll);
	$rootScope.$on('resize', throttledOnScroll);
	$rootScope.$on('trackListCollapsed', throttledOnScroll);

	var debouncedNotifyLeave = _.debounce(function() {
		_(_instances).each(function(inst) {
			if (inst.leaveViewPending) {
				onLeaveView(inst);
			}
		});
	}, 1000);

	function onScroll() {
		// do not react while the layout building is still ongoing
		if (!$rootScope.loading) {
			if (!validInViewRange()) {
				initInViewRange();
			} else {
				updateInViewRange();
			}

			// leave notifications are post-poned until when the scrolling stops
			debouncedNotifyLeave();
		}
	}

	function validInViewRange() {
		return _firstIndexInView <= _lastIndexInView;
	}

	/**
	 * Initial setup of the in-view-port statuses of the available instances
	 */
	function initInViewRange() {
		var length = _instances.length;
		var i;

		// loop from the begining until we find the first instance in viewport
		for (i = 0; i < length; ++i) {
			if (updateInViewStatus(_instances[i])) {
				_firstIndexInView = i;
				_lastIndexInView = i;
				break;
			}
		}

		// if some instance was found, then continue looping until we have found
		// all the instances in viewport
		for (++i; i < length; ++i) {
			if (updateInViewStatus(_instances[i])) {
				_lastIndexInView = i;
			} else {
				break;
			}
		}
	}

	/**
	 * Update in-view-port status when we have a valid previous in-view-range
	 */
	function updateInViewRange() {
		var prevFirst = _firstIndexInView;
		var prevLast = _lastIndexInView;
		var i;
		var length = _instances.length;

		// Check if instances in the beginning of the range have slided off
		for (i = _firstIndexInView; i <= _lastIndexInView; ++i) {
			if (!updateInViewStatus(_instances[i])) {
				++_firstIndexInView;
			} else {
				break;
			}
		}

		// Check if instances in the end of the range have slided off
		for (i = _lastIndexInView; i > _firstIndexInView; --i) {
			if (!updateInViewStatus(_instances[i])) {
				--_lastIndexInView;
			} else {
				break;
			}
		}

		if (!validInViewRange()) {
			// None of the previous instances were anymore in the view-port.
			// We have lost the track of the scrolling direction and location
			// and have to start from scratch.
			initInViewRange();
		}
		else {
			// There may be more in-view instances above if the first index
			// did not move downwards
			if (prevFirst === _firstIndexInView) {
				for (i = _firstIndexInView - 1; i >= 0; --i) {
					if (updateInViewStatus(_instances[i])) {
						--_firstIndexInView;
					} else {
						break;
					}
				}
			}

			// There may be more in-view instances below if the last index
			// did not move upwards
			if (prevLast === _lastIndexInView) {
				for (i = _lastIndexInView + 1; i < length; ++i) {
					if (updateInViewStatus(_instances[i])) {
						++_lastIndexInView;
					} else {
						break;
					}
				}
			}
		}
	}

	/**
	 * Update in-view-port status of the given instance
	 */
	function updateInViewStatus(inst) {
		var elem = inst.element;
		var wasInViewPort = inst.inViewPort;
		inst.inViewPort = elemInViewPort(elem);

		if (!wasInViewPort && inst.inViewPort) {
			if (!inst.leaveViewPending) {
				// element entered the viewport, notify the listeners with small delay
				inst.pendingEnterView = $timeout(onEnterView, 250, true, inst);
			}
			inst.leaveViewPending = false;
		}
		else if (wasInViewPort && !inst.inViewPort) {
			if (inst.pendingEnterView) {
				// element left the viewport before onEnterView was called, cancel the pending call
				$timeout.cancel(inst.pendingEnterView);
				inst.pendingEnterView = null;
			}
			else {
				// element left the viewport after it had been notified that it has entered the view
				inst.leaveViewPending = true;
			}
		}

		return inst.inViewPort;
	}

	function elemInViewPort(elem) {
		return inViewService.isElementInViewPort(elem, 500, 500);
	}

	function onEnterView(inst) {
		inst.pendingEnterView = null;
		_(inst.listeners).each(function(listener) {
			listener.onEnterView();
		});
	}

	function onLeaveView(inst) {
		inst.leaveViewPending = false;
		_(inst.listeners).each(function(listener) {
			listener.onLeaveView();
		});
	}

	return {
		scope: {},
		controller: function($scope, $element) {
			this.inViewPort = false;
			this.leaveViewPending = false;
			this.pendingEnterView = null;
			this.element = $element[0];
			this.listeners = [];

			/**
			 * Listener must have two function-type properties: onEnterView and onLeaveView
{'fraction_non_alphanumeric': 0.11797856614929786, 'fraction_numerical': 0.004988913525498891, 'mean_word_length': 3.2905271502179945, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7539323', 'n_tokens_mistral': 4046, 'n_tokens_neox': 3417, 'n_words': 987}