<commit_before>'use strict';
/* globals _, MetadataFilter, TestReporter */
/* exported Connector */

/**
 * Connector base object
 *
 * Handles all communication with the extension background script
 * and provides some convenient methods for parsing song data from the document
 *
 * @constructor
 */
var BaseConnector = window.BaseConnector || function () {

	/**
	 * Selector of an element containing artist name. The containing string will be filtered
	 * in the background script, if needed.
	 * Only applies when default implementation of {@link BaseConnector#getArtist} is used
	 *
	 * @type {string}
	 */
	this.artistSelector = null;

	/**
	 * Selector of an element containing track name. The containing string will be filtered
	 * in the background script, if needed.
	 * Only applies when default implementation of {@link BaseConnector#getTrack} is used
	 *
	 * @type {string}
	 */
	this.trackSelector = null;

	/**
	 * Selector of an element containing album name. The containing string will be filtered
	 * in the background script, if needed.
	 * Only applies when default implementation of {@link BaseConnector#getAlbum} is used
	 *
	 * @type {string}
	 */
	this.albumSelector = null;

	/**
	 * Selector of an element containing track current time in h:m:s format.
	 * Only applies when default implementation of {@link BaseConnector#getCurrentTime} is used
	 *
	 * @type {string}
	 */
	this.currentTimeSelector = null;

	/**
	 * Selector of an element containing track duration (total time) in h:m:s format.
	 * Only applies when default implementation of {@link BaseConnector#getDuration} is used
	 *
	 * @type {string}
	 */
	this.durationSelector = null;

	/**
	 * Selector of an element containing both artist and track name.
	 * {@link BaseConnector#artistSelector} and {@link BaseConnector#trackSelector} properties have priority over this,
	 * and {@link BaseConnector#artistTrackSelector} is used only if any of the previous returns empty result.
	 *
	 * The containing string will be filtered in the background script, if needed.
	 *
	 * Only applies when default implementation of {@link BaseConnector#getArtistTrack} is used
	 *
	 * @type {string}
	 */
	this.artistTrackSelector = null;

	/**
	 * Selector of an play button element. If the element is not visible, the playback is considered to be playing.
	 * Only applies when default implementation of {@link BaseConnector#isPlaying} is used
	 *
	 * @type {string}
	 */
	this.playButtonSelector = null;

	/**
	 * Selector of a container closest to the player. Changes on this element will be observed and
	 * dispatched to {@link BaseConnector#onStateChanged}
	 *
	 * Set this selector to use with default {@link MutationEvent} observing or set up some custom
	 * detection of player state changing
	 *
	 * @type {string}
	 */
	this.playerSelector = null;

	/**
	 * Selector of image used to represent the track being played. is used for
	 * the notification service.
	 *
	 * If not specified will fall back to last.fm API.
	 *
	 * @type {string}
	 */
	this.trackArtImageSelector = null;

	/**
	 * Default array of separators.
	 *
	 * Push new separators in the implementation if required.
	 *
	 * @type {array}
	 */
	this.separators = [' -- ', '--', ' - ', ' – ', ' — ', '-', '–', '—', ':', '|', '///'];

	/**
	 * Default implementation of artist name lookup by selector
	 *
	 * Override this method for more complex behaviour
	 *
	 * @returns {string|null}
	 */
	this.getArtist = function () {
		return $(this.artistSelector).text();
	};

	/**
	 * Default implementation of track name lookup by selector
	 *
	 * Override this method for more complex behaviour
	 *
	 * @returns {string|null}
	 */
	this.getTrack = function () {
		return $(this.trackSelector).text();
	};

	/**
	 * Default implementation of album name lookup by selector
	 *
	 * Override this method for more complex behaviour
	 *
	 * @returns {string|null}
	 */
	this.getAlbum = function () {
		return $(this.albumSelector).text();
	};

	/**
	 * Default implementation of track duration lookup. If this method returns an empty result,
	 * the track duration loaded from L.FM will be used
	 *
	 * While it's not generally needed, override this method for more complex behaviour
	 *
	 * @returns {number|null} track length in seconds
	 */
	this.getDuration = function () {
		var text = $(this.durationSelector).text();
		return this.stringToSeconds(text);
	};

	/**
	 * Default implementation of track current time lookup by selector with some basic parsing
	 *
	 * Override this method for more complex behaviour
	 *
	 * @returns {number} number of seconds passed from the beginning of the track
	 */
	this.getCurrentTime = function () {
		var text = $(this.currentTimeSelector).text();
		return this.stringToSeconds(text);
	};

	/**
	 * Default implementation of artist and track name lookup by selector.
	 * This method is called only when either {@link BaseConnector#getArtist} or {@link BaseConnector#getTrack}
	 * returns an empty result
	 *
	 * Override this method for more complex behaviour
	 *
	 * @returns {{artist, track}}
	 */
	this.getArtistTrack = function () {
		var text = $(this.artistTrackSelector).text();
		return this.splitArtistTrack(text);
	};

	/**
	 * Returns a unique identifier of current track. The identifier does not have to be in any specific format.
	 * The uniqueness is only needed within the scope of the connector (values are internally namespaced by connector
	 * names).
	 *
	 * The value is used for storing the track metadata and reusing them later. Connectors which will implement this
	 * method will allow its users to store custom metadata where otherwise the track would be unrecognized.
	 *
	 * It is strongly recommended for connector authors to implement this method when possible.
	 *
	 * @returns {String|null}
	 */
	this.getUniqueID = function () {
		return null;
	};

	/**
	 * Default implementation of check for active playback by play button selector.
	 * The state of playback allows the core to detect pauses.
	 *
	 * Returns TRUE as default when button selector is not specified. It's better to assume the playback
	 * is always playing than otherwise :)
	 *
	 * Override this method for custom behaviour
	 *
	 * @returns {Boolean}
	 */
	this.isPlaying = function () {
		return this.playButtonSelector === null || !$(this.playButtonSelector).is(':visible');
	};

	/**
	 * Default implementation used to get the track art from the trackArtImageSelector.
	 *
	 * Override this method for more complex behavor
	 * @return {String|null}
	 */
	this.getTrackArt = function () {
		return this.trackArtImageSelector === null ? null : $(this.trackArtImageSelector).attr('src');
	};

	/**
	 * Default implementation of a check to see if a state change is allowed.
	 *  MutationObserver will ignore mutations while this function returns false.
	 *
	 * Override this method to allow certain states to be ignored, for example if an advert is playing.
	 * @return {Boolean}
	 */
	this.isStateChangeAllowed = function () {
		return true;
	};

	/**
	 * Filter object used to filter song metadata.
	 *
	 * @see {link MetadataFilter}
	 * @type {MetadataFilter}
	 */
	this.filter = MetadataFilter.getTrimFilter();

	// --- state & api -------------------------------------------------------------------------------------------------

	/**
	 * Gathered info about the current track for internal use of base connector only.
	 */
	var currentState = {
		track: null,
		artist: null,
		album: null,
		uniqueID: null,
		duration: null,
		currentTime: 0,
		isPlaying: true,
		url: window.location.href
	};

	/**
	 * Callback set by the reactor to listen on state changes of this connector
	 *
	 * Custom connectors are NOT supposed to set this property
	 *
	 * @type {Function}
	 */
	this.reactorCallback = null;

	/**
	 * Function for all the hard work around detecting and updating state
	 */
	var stateChangedWorker = function () {
		var changedFields = [];

		var newURL = window.location.href;
		if (newURL !== currentState.url) {
			currentState.url = newURL;
			changedFields.push('url');
		}

		var newTrack = this.getTrack();
		var newArtist = this.getArtist();

		var artistTrack = this.getArtistTrack() || {artist: null, track: null};
		if (newArtist === null && artistTrack.artist) {
			newArtist = artistTrack.artist;
		}
		if (newTrack === null && artistTrack.track) {
			newTrack = artistTrack.track;
		}

		newTrack = this.filter.filterTrack(newTrack) || null;
		if (newTrack !== currentState.track) {
			currentState.track = newTrack;
			changedFields.push('track');
		}

		newArtist = this.filter.filterArtist(newArtist) || null;
		if (newArtist !== currentState.artist) {
			currentState.artist = newArtist;
			changedFields.push('artist');
		}

		var newAlbum = this.getAlbum;
		newAlbum = this.filter.filterAlbum(newAlbum) || null;
		if (newAlbum !== currentState.album) {
			currentState.album = newAlbum;
			changedFields.push('album');
		}

		var newUID = this.getUniqueID() || null;
		if (newUID !== currentState.uniqueID) {
			currentState.uniqueID = newUID;
			changedFields.push('uniqueID');
		}

		var newDuration = this.escapeBadTimeValues(this.getDuration());
		if (newDuration !== currentState.duration) {
			currentState.duration = newDuration;
			changedFields.push('duration');
		}

		var newCurrentTime = this.escapeBadTimeValues(this.getCurrentTime());
		if (newCurrentTime !== currentState.currentTime) {
			currentState.currentTime = newCurrentTime;
			changedFields.push('currentTime');
		}

		var newIsPlaying = this.isPlaying();
		if (newIsPlaying !== currentState.isPlaying) {
			currentState.isPlaying = newIsPlaying;
			changedFields.push('isPlaying');
		}

		var newTrackArt = this.getTrackArt() || null;
		if (newTrackArt !== currentState.trackArt) {
			currentState.trackArt = newTrackArt;
			changedFields.push('trackArt');
		}

		// take action if needed
		if (changedFields.length > 0) {
			if (this.reactorCallback !== null) {
				this.reactorCallback(currentState, changedFields);
			}

			// @ifdef DEBUG
			var isNewSongPlaying = (changedFields.length > 1) &&
				(currentState.artist && currentState.track);
			// Report for scrobble testing
			if (isNewSongPlaying) {
				TestReporter.reportSongRecognition(currentState);
			}
			// @endif
		}

	}.bind(this);

	/**
	 * Throttled call for state changed worker
	 */
	var stateChangedWorkerThrottled = _.throttle(stateChangedWorker, 500);

	/**
	 * Listener for the player state changes. Automatically detects the state, collects the track metadata
	 * and communicates with the background script if needed.
	 *
	 * Connectors are NOT supposed to override this method
	 */
	this.onStateChanged = function () {
		if (!this.isStateChangeAllowed()) {
			return;
		}

		/**
		 * Because gathering the state from DOM is quite expensive and mutation events can be emitted REALLY often,
		 * we use throttle to set a minimum delay between two calls of the state change listener.
		 *
		 * Only exception is change in pause/play state which we detect immediately so we don't miss
		 * a quick play/pause/play or pause/play/pause sequence
		 */
		var isPlaying = this.isPlaying();
		if (isPlaying !== currentState.isPlaying) {
			stateChangedWorker();
		} else {
			stateChangedWorkerThrottled();
		}
	}.bind(this);

	/**
	 * Helper method to convert given time-string into seconds
	 *
	 * @param str time-string in h:m:s format
	 * @returns {number}
	 */
	this.stringToSeconds = function (str) {
		var s = str.toString().trim();
		var val, seconds = 0;

		for (var i = 0; i < 3; i++) {
			var idx = s.lastIndexOf(':');
			if (idx > -1) {
				val = parseInt(s.substr(idx + 1));
				seconds += val * Math.pow(60, i);
				s = s.substr(0, idx);
			} else {
				val = parseInt(s);
				seconds += val * Math.pow(60, i);
				break;
			}
		}

		return seconds || 0;
	};

	/**
	 * Split string to artist and track.
	 * @param  {String} str [description]
	 * @return {Object} Object contains artist and track fields
	 */
	this.splitArtistTrack = function(str) {
		let artist = null;
		let track = null;

		if (str !== null) {
			let separator = this.findSeparator(str);

			if (separator !== null) {
				artist = str.substr(0, separator.index);
				track = str.substr(separator.index + separator.length);
			}
		}

		return {artist, track};
	};

	/**
	 * Find first occurence of possible separator in given string
	 * and return separator's position and size in chars or null.
	 *
	 * @returns {{index, length}|null}
	 */
	this.findSeparator = function (str) {

		if (str === null || str.length === 0) {
			return null;
		}

		for (var i in this.separators) {
			var sep = this.separators[i];
			var index = str.indexOf(sep);
			if (index > -1) {
				return { index: index, length: sep.length };
			}
		}

		return null;
	};

	this.escapeBadTimeValues = function(time) {
		if (typeof time !== 'number') {
			return null;
		}
		if (isNaN(time) || !isFinite(time)) {
			return null;
		}
		return Math.round(time);
	};
};

window.BaseConnector = BaseConnector;

/**
 * Create object to be overridden in specific connector implementation
 * @type {BaseConnector}
 */
let Connector;
if (window.Connector) {
	Connector = window.Connector;
} else {
	Connector = new BaseConnector();
	window.Connector = Connector;
}
<commit_msg>Revert accidental changes<commit_after>'use strict';
/* globals _, MetadataFilter, TestReporter */
/* exported Connector */

/**
 * Connector base object
 *
 * Handles all communication with the extension background script
 * and provides some convenient methods for parsing song data from the document
 *
 * @constructor
 */
var BaseConnector = window.BaseConnector || function () {

	/**
	 * Selector of an element containing artist name. The containing string will be filtered
	 * in the background script, if needed.
	 * Only applies when default implementation of {@link BaseConnector#getArtist} is used
	 *
	 * @type {string}
	 */
	this.artistSelector = null;

	/**
	 * Selector of an element containing track name. The containing string will be filtered
	 * in the background script, if needed.
	 * Only applies when default implementation of {@link BaseConnector#getTrack} is used
	 *
	 * @type {string}
	 */
	this.trackSelector = null;

	/**
	 * Selector of an element containing album name. The containing string will be filtered
	 * in the background script, if needed.
	 * Only applies when default implementation of {@link BaseConnector#getAlbum} is used
	 *
	 * @type {string}
	 */
	this.albumSelector = null;

	/**
	 * Selector of an element containing track current time in h:m:s format.
	 * Only applies when default implementation of {@link BaseConnector#getCurrentTime} is used
	 *
	 * @type {string}
	 */
	this.currentTimeSelector = null;

	/**
	 * Selector of an element containing track duration (total time) in h:m:s format.
	 * Only applies when default implementation of {@link BaseConnector#getDuration} is used
	 *
	 * @type {string}
	 */
	this.durationSelector = null;

	/**
	 * Selector of an element containing both artist and track name.
	 * {@link BaseConnector#artistSelector} and {@link BaseConnector#trackSelector} properties have priority over this,
	 * and {@link BaseConnector#artistTrackSelector} is used only if any of the previous returns empty result.
	 *
	 * The containing string will be filtered in the background script, if needed.
	 *
	 * Only applies when default implementation of {@link BaseConnector#getArtistTrack} is used
	 *
	 * @type {string}
	 */
	this.artistTrackSelector = null;

	/**
	 * Selector of an play button element. If the element is not visible, the playback is considered to be playing.
	 * Only applies when default implementation of {@link BaseConnector#isPlaying} is used
	 *
	 * @type {string}
	 */
	this.playButtonSelector = null;

	/**
	 * Selector of a container closest to the player. Changes on this element will be observed and
	 * dispatched to {@link BaseConnector#onStateChanged}
	 *
	 * Set this selector to use with default {@link MutationEvent} observing or set up some custom
	 * detection of player state changing
	 *
	 * @type {string}
	 */
	this.playerSelector = null;

	/**
	 * Selector of image used to represent the track being played. is used for
	 * the notification service.
	 *
	 * If not specified will fall back to last.fm API.
	 *
	 * @type {string}
	 */
	this.trackArtImageSelector = null;

	/**
	 * Default array of separators.
	 *
	 * Push new separators in the implementation if required.
	 *
	 * @type {array}
	 */
	this.separators = [' -- ', '--', ' - ', ' – ', ' — ', '-', '–', '—', ':', '|', '///'];

	/**
	 * Default implementation of artist name lookup by selector
	 *
	 * Override this method for more complex behaviour
	 *
	 * @returns {string|null}
	 */
	this.getArtist = function () {
		return $(this.artistSelector).text();
	};

	/**
	 * Default implementation of track name lookup by selector
	 *
	 * Override this method for more complex behaviour
	 *
	 * @returns {string|null}
	 */
	this.getTrack = function () {
		return $(this.trackSelector).text();
	};

	/**
	 * Default implementation of album name lookup by selector
	 *
	 * Override this method for more complex behaviour
	 *
	 * @returns {string|null}
	 */
	this.getAlbum = function () {
		return $(this.albumSelector).text();
	};

	/**
	 * Default implementation of track duration lookup. If this method returns an empty result,
	 * the track duration loaded from L.FM will be used
	 *
	 * While it's not generally needed, override this method for more complex behaviour
	 *
	 * @returns {number|null} track length in seconds
	 */
	this.getDuration = function () {
		var text = $(this.durationSelector).text();
		return this.stringToSeconds(text);
	};

	/**
	 * Default implementation of track current time lookup by selector with some basic parsing
	 *
	 * Override this method for more complex behaviour
	 *
	 * @returns {number} number of seconds passed from the beginning of the track
	 */
	this.getCurrentTime = function () {
		var text = $(this.currentTimeSelector).text();
		return this.stringToSeconds(text);
	};

	/**
	 * Default implementation of artist and track name lookup by selector.
	 * This method is called only when either {@link BaseConnector#getArtist} or {@link BaseConnector#getTrack}
	 * returns an empty result
	 *
	 * Override this method for more complex behaviour
	 *
	 * @returns {{artist, track}}
	 */
	this.getArtistTrack = function () {
		var text = $(this.artistTrackSelector).text();
		return this.splitArtistTrack(text);
	};

	/**
	 * Returns a unique identifier of current track. The identifier does not have to be in any specific format.
	 * The uniqueness is only needed within the scope of the connector (values are internally namespaced by connector
	 * names).
	 *
	 * The value is used for storing the track metadata and reusing them later. Connectors which will implement this
	 * method will allow its users to store custom metadata where otherwise the track would be unrecognized.
	 *
	 * It is strongly recommended for connector authors to implement this method when possible.
	 *
	 * @returns {String|null}
	 */
	this.getUniqueID = function () {
		return null;
	};

	/**
	 * Default implementation of check for active playback by play button selector.
	 * The state of playback allows the core to detect pauses.
	 *
	 * Returns TRUE as default when button selector is not specified. It's better to assume the playback
	 * is always playing than otherwise :)
	 *
	 * Override this method for custom behaviour
	 *
	 * @returns {Boolean}
	 */
	this.isPlaying = function () {
		return this.playButtonSelector === null || !$(this.playButtonSelector).is(':visible');
	};

	/**
	 * Default implementation used to get the track art from the trackArtImageSelector.
	 *
	 * Override this method for more complex behavor
	 * @return {String|null}
	 */
	this.getTrackArt = function () {
		return this.trackArtImageSelector === null ? null : $(this.trackArtImageSelector).attr('src');
	};

	/**
	 * Default implementation of a check to see if a state change is allowed.
	 *  MutationObserver will ignore mutations while this function returns false.
	 *
	 * Override this method to allow certain states to be ignored, for example if an advert is playing.
	 * @return {Boolean}
	 */
	this.isStateChangeAllowed = function () {
		return true;
	};

	/**
	 * Filter object used to filter song metadata.
	 *
	 * @see {link MetadataFilter}
	 * @type {MetadataFilter}
	 */
	this.filter = MetadataFilter.getTrimFilter();

	// --- state & api -------------------------------------------------------------------------------------------------

	/**
	 * Gathered info about the current track for internal use of base connector only.
	 */
	var currentState = {
		track: null,
		artist: null,
		album: null,
		uniqueID: null,
		duration: null,
		currentTime: 0,
		isPlaying: true,
		url: window.location.href
	};

	/**
	 * Callback set by the reactor to listen on state changes of this connector
	 *
	 * Custom connectors are NOT supposed to set this property
	 *
	 * @type {Function}
	 */
	this.reactorCallback = null;

	/**
	 * Function for all the hard work around detecting and updating state
	 */
	var stateChangedWorker = function () {
		var changedFields = [];

		var newURL = window.location.href;
		if (newURL !== currentState.url) {
			currentState.url = newURL;
			changedFields.push('url');
		}

		var newTrack = this.getTrack();
		var newArtist = this.getArtist();

		var artistTrack = this.getArtistTrack() || {artist: null, track: null};
		if (newArtist === null && artistTrack.artist) {
			newArtist = artistTrack.artist;
		}
		if (newTrack === null && artistTrack.track) {
			newTrack = artistTrack.track;
		}

		newTrack = this.filter.filterTrack(newTrack) || null;
		if (newTrack !== currentState.track) {
			currentState.track = newTrack;
			changedFields.push('track');
		}

		newArtist = this.filter.filterArtist(newArtist) || null;
		if (newArtist !== currentState.artist) {
			currentState.artist = newArtist;
			changedFields.push('artist');
		}

		var newAlbum = this.getAlbum();
		newAlbum = this.filter.filterAlbum(newAlbum) || null;
		if (newAlbum !== currentState.album) {
			currentState.album = newAlbum;
			changedFields.push('album');
		}

		var newUID = this.getUniqueID() || null;
		if (newUID !== currentState.uniqueID) {
			currentState.uniqueID = newUID;
			changedFields.push('uniqueID');
		}

		var newDuration = this.escapeBadTimeValues(this.getDuration());
		if (newDuration !== currentState.duration) {
			currentState.duration = newDuration;
			changedFields.push('duration');
		}

		var newCurrentTime = this.escapeBadTimeValues(this.getCurrentTime());
		if (newCurrentTime !== currentState.currentTime) {
			currentState.currentTime = newCurrentTime;
			changedFields.push('currentTime');
		}

		var newIsPlaying = this.isPlaying();
		if (newIsPlaying !== currentState.isPlaying) {
			currentState.isPlaying = newIsPlaying;
			changedFields.push('isPlaying');
		}

		var newTrackArt = this.getTrackArt() || null;
		if (newTrackArt !== currentState.trackArt) {
			currentState.trackArt = newTrackArt;
			changedFields.push('trackArt');
		}

		// take action if needed
		if (changedFields.length > 0) {
			if (this.reactorCallback !== null) {
				this.reactorCallback(currentState, changedFields);
			}

			// @ifdef DEBUG
			var isNewSongPlaying = (changedFields.length > 1) &&
				(currentState.artist && currentState.track);
			// Report for scrobble testing
			if (isNewSongPlaying) {
				TestReporter.reportSongRecognition(currentState);
			}
			// @endif
		}

	}.bind(this);

	/**
	 * Throttled call for state changed worker
	 */
	var stateChangedWorkerThrottled = _.throttle(stateChangedWorker, 500);

	/**
	 * Listener for the player state changes. Automatically detects the state, collects the track metadata
	 * and communicates with the background script if needed.
	 *
	 * Connectors are NOT supposed to override this method
	 */
	this.onStateChanged = function () {
		if (!this.isStateChangeAllowed()) {
			return;
		}

		/**
		 * Because gathering the state from DOM is quite expensive and mutation events can be emitted REALLY often,
		 * we use throttle to set a minimum delay between two calls of the state change listener.
		 *
		 * Only exception is change in pause/play state which we detect immediately so we don't miss
		 * a quick play/pause/play or pause/play/pause sequence
		 */
		var isPlaying = this.isPlaying();
		if (isPlaying !== currentState.isPlaying) {
			stateChangedWorker();
		} else {
			stateChangedWorkerThrottled();
		}
	}.bind(this);

	/**
	 * Helper method to convert given time-string into seconds
	 *
	 * @param str time-string in h:m:s format
	 * @returns {number}
	 */
	this.stringToSeconds = function (str) {
		var s = str.toString().trim();
		var val, seconds = 0;

		for (var i = 0; i < 3; i++) {
			var idx = s.lastIndexOf(':');
			if (idx > -1) {
				val = parseInt(s.substr(idx + 1));
				seconds += val * Math.pow(60, i);
				s = s.substr(0, idx);
			} else {
				val = parseInt(s);
				seconds += val * Math.pow(60, i);
				break;
			}
		}

		return seconds || 0;
	};

	/**
	 * Split string to artist and track.
	 * @param  {String} str [description]
	 * @return {Object} Object contains artist and track fields
	 */
	this.splitArtistTrack = function(str) {
		let artist = null;
		let track = null;

		if (str !== null) {
			let separator = this.findSeparator(str);

			if (separator !== null) {
				artist = str.substr(0, separator.index);
				track = str.substr(separator.index + separator.length);
			}
		}

		return {artist, track};
	};

	/**
	 * Find first occurence of possible separator in given string
	 * and return separator's position and size in chars or null.
	 *
	 * @returns {{index, length}|null}
	 */
	this.findSeparator = function (str) {

		if (str === null || str.length === 0) {
			return null;
		}

		for (var i in this.separators) {
			var sep = this.separators[i];
			var index = str.indexOf(sep);
			if (index > -1) {
				return { index: index, length: sep.length };
			}
		}

		return null;
	};

	this.escapeBadTimeValues = function(time) {
		if (typeof time !== 'number') {
			return null;
		}
		if (isNaN(time) || !isFinite(time)) {
			return null;
		}
		return Math.round(time);
	};
};

window.BaseConnector = BaseConnector;

/**
 * Create object to be overridden in specific connector implementation
 * @type {BaseConnector}
 */
let Connector;
if (window.Connector) {
	Connector = window.Connector;
} else {
	Connector = new BaseConnector();
	window.Connector = Connector;
}
<|endoftext|>


<commit_before>define(["requirejs-dplugins/has"], function (has) {
	/* global Platform */
	has.add("console-api", typeof console !== "undefined");
	has.add("host-browser", typeof window !== "undefined");
	has.add("object-observe-api", typeof Object.observe === "function" && typeof Array.observe === "function");
	has.add("object-is-api", Object.is);
	has.add("setimmediate-api", typeof setImmediate === "function");
	has.add("mutation-observer-api",
		typeof MutationObserver !== "undefined"
			&& (/\[\s*native\s+code\s*\]/i.test(MutationObserver) // Avoid polyfill version of MutationObserver
				|| !/^\s*function/.test(MutationObserver)));
	has.add("polymer-platform", typeof Platform !== "undefined");
	return has;
});
<commit_msg>Fix native Object.is() detection, fixes #54.<commit_after>define(["requirejs-dplugins/has"], function (has) {
	/* global Platform */
	has.add("console-api", typeof console !== "undefined");
	has.add("host-browser", typeof window !== "undefined");
	has.add("object-observe-api", typeof Object.observe === "function" && typeof Array.observe === "function");
	has.add("object-is-api", !!Object.is);
	has.add("setimmediate-api", typeof setImmediate === "function");
	has.add("mutation-observer-api",
		typeof MutationObserver !== "undefined"
			&& (/\[\s*native\s+code\s*\]/i.test(MutationObserver) // Avoid polyfill version of MutationObserver
				|| !/^\s*function/.test(MutationObserver)));
	has.add("polymer-platform", typeof Platform !== "undefined");
	return has;
});
<|endoftext|>


<commit_before>(function($) {

var fengshui = {};

function removePrefix(selector) {
  if (selector[0] === '.' || selector[0] === '#') {
    selector = selector.slice(1);
  }
  return selector;
}

function selectorToVar(selector, separator) {
  return selector.replace(
    new RegExp(separator+'([a-z])', 'g'), function(g) {
      return g[1].toUpperCase();
    });
}

function processName(component, separator, useComponentVars) {
  var selectorVar = removePrefix(component);
  if (useComponentVars) {
    selectorVar = selectorToVar(selectorVar, separator);
  }
  return selectorVar;
}

function System(blueprint) {
  // settle blueprint.options' default values
  var options = blueprint.options === undefined
    ? {} : blueprint.options;
  var useComponentVars = options.componentVars === undefined
    ? true
    : options.componentVars;
  var separator = options.separator === undefined
    ? '-'
    : options.separator;
  var aliases = blueprint.aliases === undefined
    ? {} : blueprint.aliases;

  // having System inherit some props from blueprint
  // TODO: add aliases
  blueprint.root = '.'+blueprint.root; // add class prefix
  this.fsRootStr = blueprint.root;
  this.fsRoot = $(this.fsRootStr);
  this.fsComponents = [];
  this.fsSeparator = separator;
  this.fsUseVars = useComponentVars;
  this.aliases = aliases;
  var system = this;

  var $root = $(blueprint.root);
  // just sample one
  // somehow jQuery will skip the first one in array
  // when using jQuery collection as context,
  // so use element instead
  var $topRoot = $root[0];

  // get everything with class attr
  var $hasClass = $('*[class]', $topRoot);
  $hasClass.each(function() {
    var classes = $.trim($(this).attr('class')).split(/\s+/g);
    // ['btn', 'btn-lg', 'btn-default']
    $.each(classes, function(index) {
      var component = classes[index];
      var componentVar = processName(component, separator,
        useComponentVars);
      // add only if it's not there
      if (system[componentVar] === undefined) {
        system.fsComponents.push(component);
        system[componentVar] = $('.'+component, $root);
      }

      // create an alias
      if (aliases[component] !== undefined) {
        system[aliases[component]] = system[componentVar];
      }
    });
  });
}

function setCtx(context) {
  var $context = $(context);
  var $root = $context.closest(this.fsRootStr);
  var aliases = this.aliases;
  for (var i = 0; i < this.fsComponents.length; i++) {
    var component = this.fsComponents[i];
    var componentVar = processName(component,
      this.fsSeparator, this.fsUseVars);
    this[componentVar] = $root.find('.'+component);

    // create an alias
    if (aliases[component] !== undefined) {
      this[aliases[component]] = this[componentVar];
    }
  }
  return this;
}

System.prototype = {
  setCtx: setCtx
};

// function attachments
fengshui.System = System;
window.fengshui = fengshui;

})(jQuery); // fengshui

// extra jQuery plugins
(function($) {
$.fn.incr = function increment(diff) {
  diff === undefined ? diff = 1 : diff;
  var number = parseInt(this.text(), 10);
  number += diff;
  this.text(number.toString());
};
})(jQuery);
<commit_msg>Added type feature<commit_after>(function($) {

var fengshui = {};

function removePrefix(selector) {
  if (selector[0] === '.' || selector[0] === '#') {
    selector = selector.slice(1);
  }
  return selector;
}

function selectorToVar(selector, separator) {
  return selector.replace(
    new RegExp(separator+'([a-z])', 'g'), function(g) {
      return g[1].toUpperCase();
    });
}

function processName(component, separator, useComponentVars) {
  var selectorVar = removePrefix(component);
  if (useComponentVars) {
    selectorVar = selectorToVar(selectorVar, separator);
  }
  return selectorVar;
}

function System(blueprint) {
  // settle blueprint.options' default values
  var options = blueprint.options === undefined
    ? {} : blueprint.options;
  var useComponentVars = options.componentVars === undefined
    ? true
    : options.componentVars;
  var separator = options.separator === undefined
    ? '-'
    : options.separator;
  var aliases = blueprint.aliases === undefined
    ? {} : blueprint.aliases;
  var types = blueprint.types === undefined
    ? {} : blueprint.types;

  // having System inherit some props from blueprint
  // TODO: add aliases
  blueprint.root = '.'+blueprint.root; // add class prefix
  this.fsRootStr = blueprint.root;
  this.fsRoot = $(this.fsRootStr);
  this.fsComponents = [];
  this.fsSeparator = separator;
  this.fsUseVars = useComponentVars;
  this.aliases = aliases;
  this.types = types;
  var system = this;

  var $root = $(blueprint.root);
  // just sample one
  // somehow jQuery will skip the first one in array
  // when using jQuery collection as context,
  // so use element instead
  var $topRoot = $root[0];

  // get everything with class attr
  var $hasClass = $('*[class]', $topRoot);
  $hasClass.each(function() {
    var classes = $.trim($(this).attr('class')).split(/\s+/g);
    // ['btn', 'btn-lg', 'btn-default']
    $.each(classes, function(index) {
      var component = classes[index];
      var componentVar = processName(component, separator,
        useComponentVars);
      // add only if it's not there
      if (system[componentVar] === undefined) {
        system.fsComponents.push(component);
        system[componentVar] = $('.'+component, $root);
      }

      // create an alias
      if (aliases[component] !== undefined) {
        system[aliases[component]] = system[componentVar];
      }
    });
  });
}

function setCtx(context) {
  var $context = $(context);
  var $root = $context.closest(this.fsRootStr);
  var aliases = this.aliases;
  var types = this.types;
  var system = this;

  for (var i = 0; i < this.fsComponents.length; i++) {
    var component = this.fsComponents[i];
    var componentVar = processName(component,
      this.fsSeparator, this.fsUseVars);
    this[componentVar] = $root.find('.'+component);

    // create an alias
    if (aliases[component] !== undefined) {
      this[aliases[component]] = this[componentVar];
    }
  }

  $.each(types, function(type, componentVars) {
    switch (type) {
      case "text":
        componentVars.map(function(componentVar) {
          system[componentVar] = system[componentVar].text();
        });
        break;
      case "number":
        componentVars.map(function(componentVar) {
          system[componentVar] = parseInt(
            system[componentVar].text(), 10);
        });
        break;
      default:
        throw new TypeError('Only number and text type available');
    }
  });

  function handleText(jq) {
    return jq.text();
  }

  return this;
}

System.prototype = {
  setCtx: setCtx
};

// function attachments
fengshui.System = System;
window.fengshui = fengshui;

})(jQuery); // fengshui

// extra jQuery plugins
(function($) {
$.fn.incr = function increment(diff) {
  diff === undefined ? diff = 1 : diff;
  var number = parseInt(this.text(), 10);
  number += diff;
  this.text(number.toString());
};
})(jQuery);
<|endoftext|>


<commit_before>(function() {
  chrome.storage.sync.get("playedTutorial", function(playedTutorial) {
    if(!playedTutorial.playedTutorial) {
      console.log(playedTutorial.playedTutorial)
      document.getElementById("loading").style.display = "none";
      document.getElementById("oninstall").style.display = "block";
      utils.onInstall.setTutorialPlayed();
    }else {

    }
  });
})();

/*TODO: rename file to something more descriptive*/
<commit_msg>Remove unnecessary js<commit_after>(function() {
  chrome.storage.sync.get("playedTutorial", function(playedTutorial) {
    if(!playedTutorial.playedTutorial) {
      document.getElementById("oninstall").style.display = "block";
      utils.onInstall.setTutorialPlayed();
    } else {
      document.getElementById("onupdate").style.display = "block";
    }
  });
})();

/*TODO: rename file to something more descriptive*/
<|endoftext|>


<commit_before>/**
 *
 * Publications
 *
 */

import React, { PropTypes } from 'react';
// import styled from 'styled-components';
import { LinearProgress } from 'material-ui/Progress';
import { MdAddCircleOutline, MdRemoveCircleOutline } from 'react-icons/lib/md';
import ReactGA from 'react-ga';

import { withStyles } from 'material-ui/styles';

import axios from 'axios';
import { graphQLRoot } from 'utils/constants';

import PublicationSystems from './publicationSystems';

const styles = (theme) => ({
  publicationEntry: {
    textAlign: 'left',
    [theme.breakpoints.down('sm')]: {
      fontSize: '14px',
    },
  },
  publicationYear: {
    textAlign: 'left',
    [theme.breakpoints.down('sm')]: {
      textAlign: 'center',
    },
  },
});

const prettyPrintReference = (reference) => {
  if (reference.indexOf('{') > -1) {
    const ref = JSON.parse(reference);
    // TODO Integrate with crossref.org api
    // if (false && typeof ref.doi === 'undefined' || ref.doi === '') {
    return (
      {ref.title !== '' ? <emph>{`"${ref.title}"`}. </emph> : null }
      {typeof ref.authors !== 'undefined' ? {ref.authors.join('; ').replace('\\o', 'ø')}.  : null }
      {ref.journal !== '' ? {ref.journal},  : null }
      {ref.volume !== '' ? {ref.volume}  : null}
      {ref.year !== '' ? ({ref.year}):  : null}
      {ref.pages !== '' ? {ref.pages},  : null}
    );
  }
  return null;
};

class Publications extends React.Component { // eslint-disable-line react/prefer-stateless-function
  constructor(props) {
    super(props);
    this.state = {
      years: [],
      references: {},
      dois: {},
      loading: false,
      openedPublication: null,
      systems: [],
    };
    this.clickPublication = this.clickPublication.bind(this);
  }
  componentDidMount() {
    const yearQuery = '{numberKeys(key:"publication_year") { edges { node { value } } }}';
    axios.post(graphQLRoot, {
      query: yearQuery,
    })
      .then((response) => {
        let years = response.data.data.numberKeys.edges.map((n) => (n.node.value));
        years = [...new Set(years)];
        this.setState({
          years,
        });
        years.map((year) => {
          const query = `{catapp(year: ${year}) { edges { node { year publication doi dftCode dftFunctional } } }}`;
          return axios.post(graphQLRoot, {
            query,
          })
            .then((yearResponse) => {
              let references = yearResponse.data.data.catapp.edges.map((n) => (n.node.publication));
              references = [...new Set(references)];
              let dois = yearResponse.data.data.catapp.edges.map((n) => (n.node.doi));
              dois = [...new Set(dois)];

              const allReferences = this.state.references;
              const allDois = this.state.dois;

              allReferences[year] = references;
              allDois[year] = dois;

              this.setState({
                references: allReferences,
                dois: allDois,
              });
            })
            .catch(() => {
            })
          ;
        });
      });
  }
  clickPublication(event, target, key) {
    const splitKey = key.split('_');
    const year = parseInt(splitKey[1], 10);
    const count = parseInt(splitKey[2], 10);
    const doi = this.state.dois[year][count];
    /* reference = reference.split('"').join('\\"'); */
    if (this.state.openedPublication === key) {
      this.setState({
        openedPublication: null,
      });
    } else {
      this.setState({
        openedPublication: key,
      });
    }
    const query = `query{systems(keyValuePairs: "~doi\\": \\"${doi}") { edges { node { natoms Formula Facet uniqueId energy DftCode DftFunctional PublicationTitle PublicationAuthors PublicationYear PublicationDoi } } }}`;
    axios.post(graphQLRoot, { query })
      .then((response) => {
        this.setState({
          systems: response.data.data.systems.edges,
        });
      })
      .catch(() => {
      });
  }

  render() {
    return (

        {this.state.references === {} ? <LinearProgress color="primary" /> : null }
        {this.state.years.map((year, i) => (
          <div key={`div_year_${i}`}>
            <h2 key={`pyear_${year}`} className={this.props.classes.publicationYear}>{year}

            {(this.state.references[year] || [])
                .filter((references, j) => (
                  this.state.dois[year][j] !== ''
                ))
                .map((reference, j) => (
                  <div key={`pli_${i}_${j}`} className={this.props.classes.publicationEntry}>
                    <button role="button" onClick={(target, event) => this.clickPublication(event, target, `elem_${year}_${j}`)} className={this.props.classes.publicationEntry}>
                      { this.state.openedPublication !== `elem_${year}_${j}` ?
                        <MdAddCircleOutline className={this.props.classes.publicationEntry} />
                      :
                        <MdRemoveCircleOutline className={this.props.classes.publicationEntry} />
                      }

                        {prettyPrintReference(reference)}
                        <ReactGA.OutboundLink
                          eventLabel={`http://dx.doi.org/${this.state.dois[year][j]}`}
                          to={`http://dx.doi.org/${this.state.dois[year][j]}`}
                          target="_blank"
                        >
                          DOI: {this.state.dois[year][j]}
                        </ReactGA.OutboundLink>

                    </button>

                      { this.state.openedPublication !== `elem_${year}_${j}` ? null :
                      <PublicationSystems {...this.state} />
                      }

                ))}

        ))
        }

    );
  }
}

Publications.propTypes = {
  classes: PropTypes.object,

};

export default withStyles(styles, { withTheme: true })(Publications);
<commit_msg>Touched up on publication list<commit_after>/**
 *
 * Publications
 *
 */

import React, { PropTypes } from 'react';
// import styled from 'styled-components';
import { LinearProgress } from 'material-ui/Progress';
import { MdAddCircleOutline, MdRemoveCircleOutline } from 'react-icons/lib/md';
import ReactGA from 'react-ga';

import { withStyles } from 'material-ui/styles';

import axios from 'axios';
import { graphQLRoot } from 'utils/constants';

import PublicationSystems from './publicationSystems';

const styles = (theme) => ({
  publicationEntry: {
    textAlign: 'left',
    [theme.breakpoints.down('sm')]: {
      fontSize: '14px',
    },
  },
  publicationYear: {
    textAlign: 'left',
    [theme.breakpoints.down('sm')]: {
      textAlign: 'center',
    },
  },
});

const prettyPrintReference = (reference) => {
  if (reference.indexOf('{') > -1) {
    const ref = JSON.parse(reference);
    // TODO Integrate with crossref.org api
    // if (false && typeof ref.doi === 'undefined' || ref.doi === '') {
    return (
      {ref.title !== '' ? <emph>{`"${ref.title}"`}. </emph> : null }
      {typeof ref.authors !== 'undefined' ? {ref.authors.join('; ').replace('\\o', 'ø')}.  : null }
      {ref.journal !== '' ? {ref.journal},  : null }
      {ref.volume !== '' ? {ref.volume}  : null}
      {ref.year !== '' ? ({ref.year}):  : null}
      {ref.pages !== '' ? {ref.pages}.  : null}
    );
  }
  return null;
};

class Publications extends React.Component { // eslint-disable-line react/prefer-stateless-function
  constructor(props) {
    super(props);
    this.state = {
      years: [],
      references: {},
      dois: {},
      loading: false,
      openedPublication: null,
      systems: [],
    };
    this.clickPublication = this.clickPublication.bind(this);
  }
  componentDidMount() {
    const yearQuery = '{numberKeys(key:"publication_year") { edges { node { value } } }}';
    axios.post(graphQLRoot, {
      query: yearQuery,
    })
      .then((response) => {
        let years = response.data.data.numberKeys.edges.map((n) => (n.node.value));
        years = [...new Set(years)];
        this.setState({
          years,
        });
        years.map((year) => {
          const query = `{catapp(year: ${year}) { edges { node { year publication doi dftCode dftFunctional } } }}`;
          return axios.post(graphQLRoot, {
            query,
          })
            .then((yearResponse) => {
              let references = yearResponse.data.data.catapp.edges.map((n) => (n.node.publication));
              references = [...new Set(references)];
              let dois = yearResponse.data.data.catapp.edges.map((n) => (n.node.doi));
              dois = [...new Set(dois)];

              const allReferences = this.state.references;
              const allDois = this.state.dois;

              allReferences[year] = references;
              allDois[year] = dois;

              this.setState({
                references: allReferences,
                dois: allDois,
              });
            })
            .catch(() => {
            })
          ;
        });
      });
  }
  clickPublication(event, target, key) {
    const splitKey = key.split('_');
    const year = parseInt(splitKey[1], 10);
    const count = parseInt(splitKey[2], 10);
    const doi = this.state.dois[year][count];
    /* reference = reference.split('"').join('\\"'); */
    if (this.state.openedPublication === key) {
      this.setState({
        openedPublication: null,
      });
    } else {
      this.setState({
        openedPublication: key,
      });
    }
    const query = `query{systems(keyValuePairs: "~doi\\": \\"${doi}") { edges { node { natoms Formula Facet uniqueId energy DftCode DftFunctional PublicationTitle PublicationAuthors PublicationYear PublicationDoi } } }}`;
    axios.post(graphQLRoot, { query })
      .then((response) => {
        this.setState({
          systems: response.data.data.systems.edges,
        });
      })
      .catch(() => {
      });
  }

  render() {
    return (

        {this.state.references === {} ? <LinearProgress color="primary" /> : null }
        {this.state.years.map((year, i) => (
          <div key={`div_year_${i}`}>
            <h2 key={`pyear_${year}`} className={this.props.classes.publicationYear}>{year}

            {(this.state.references[year] || [])
                .filter((references, j) => (
                  this.state.dois[year][j] !== ''
                ))
                .map((reference, j) => (
                  <div key={`pli_${i}_${j}`} className={this.props.classes.publicationEntry}>
                    <button role="button" onClick={(target, event) => this.clickPublication(event, target, `elem_${year}_${j}`)} className={this.props.classes.publicationEntry}>
                      { this.state.openedPublication !== `elem_${year}_${j}` ?
                        <MdAddCircleOutline className={this.props.classes.publicationEntry} />
                      :
                        <MdRemoveCircleOutline className={this.props.classes.publicationEntry} />
                      }

                        {prettyPrintReference(reference)}
                        <ReactGA.OutboundLink
                          eventLabel={`http://dx.doi.org/${this.state.dois[year][j]}`}
                          to={`http://dx.doi.org/${this.state.dois[year][j]}`}
                          target="_blank"
                        >
                          DOI: {this.state.dois[year][j]}.
                        </ReactGA.OutboundLink>

                    </button>

                      { this.state.openedPublication !== `elem_${year}_${j}` ? null :
                      <PublicationSystems {...this.state} />
                      }

                ))}

        ))
        }

    );
  }
}

Publications.propTypes = {
  classes: PropTypes.object,

};

export default withStyles(styles, { withTheme: true })(Publications);
<|endoftext|>


<commit_before>$(function() {
  // Handler for .ready() called.

  updateCategories();
});

function listServices(category) {

  var filteredServices = services.filter(function(service) {
    return service.Category == category;
  });
  var htmlTable = '
';
  filteredServices.forEach(function (service) {
    htmlTable += '
'
    htmlTable += '
'
    htmlTable += '
';
    htmlTable += '';
    htmlTable +=
      '
' +
        service.Name +
      '
';
    htmlTable += '';
    htmlTable += '
' + service["Short Description"] + '
' +'
';
    var service_name = service.Name;
    htmlTable += '
' +
    '
Description
' +
      '
Pricing
' +
      '
Recommendation
' +
    '
'+

    '
' +
      '
' +
        '
'+ service.Description +'
';
      //
      // // Case for putting tags inside descritption element
      //   htmlTable += '
';
      //   if (service.tags) {
      //     service.tags.forEach(function (tag) {
      //       htmlTable += '' + tag + '';
      //     });
      //   }
//
      htmlTable += '
' +
      '
' +
        '
'+ service.Pricing +'
' +
      '
' +
      '
' +
        '
'+ service.Recommendation+'
' +
      '
' +
    '
';

    htmlTable += '
';
    if (service.Tags) {
      service.Tags.forEach(function (tag) {
        htmlTable += '' + tag + '';
      });
    }
    htmlTable += '
';
    htmlTable += '
';
    htmlTable += '
';
    htmlTable += '';
    htmlTable += '
';
    htmlTable += '
';
  });
  htmlTable += "
"
  //$('#resources').html(htmlTable);
  return htmlTable;
}

function updateCategories() {
  var categoriesHTML = '';
  categories.forEach(function (category) {
    var prettified = category.replace(/[^a-zA-Z]/g, "")
    categoriesHTML += '
';
      categoriesHTML += '';
        categoriesHTML += '
';
          categoriesHTML += '
';
            categoriesHTML += category;
          categoriesHTML += '
';
        categoriesHTML += '
';
      categoriesHTML += '';
      categoriesHTML += '
';
        categoriesHTML += listServices(category);
      categoriesHTML += '
';
    categoriesHTML += '
';
  });
  $('#accordion').html(categoriesHTML);
}
<commit_msg>Solved: Hyperlinks broken by two worded names<commit_after>$(function() {
  // Handler for .ready() called.

  updateCategories();
});

function listServices(category) {

  var filteredServices = services.filter(function(service) {
    return service.Category == category;
  });
  var htmlTable = '
';
  filteredServices.forEach(function (service) {
    htmlTable += '
'
    htmlTable += '
'
    htmlTable += '
';
    htmlTable += '';
    htmlTable +=
      '
' +
        service.Name +
      '
';
    htmlTable += '';
    htmlTable += '
' + service["Short Description"] + '
' +'
';
    var service_name = service.Name;
    var prettified = service_name.replace(/[^a-zA-Z]/g, "")
    htmlTable += '
' +
    '
Description
' +
      '
Pricing
' +
      '
Recommendation
' +
    '
'+

    '
' +
      '
' +
        '
'+ service.Description +'
';
      //
      // // Case for putting tags inside descritption element
      //   htmlTable += '
';
      //   if (service.tags) {
      //     service.tags.forEach(function (tag) {
      //       htmlTable += '' + tag + '';
      //     });
      //   }
//
      htmlTable += '
' +
      '
' +
        '
'+ service.Pricing +'
' +
      '
' +
      '
' +
        '
'+ service.Recommendation+'
' +
      '
' +
    '
';

    htmlTable += '
';
    if (service.Tags) {
      service.Tags.forEach(function (tag) {
        htmlTable += '' + tag + '';
      });
    }
    htmlTable += '
';
    htmlTable += '
';
    htmlTable += '
';
    htmlTable += '';
    htmlTable += '
';
    htmlTable += '
';
  });
  htmlTable += "
"
  //$('#resources').html(htmlTable);
  return htmlTable;
}

function updateCategories() {
  var categoriesHTML = '';
  categories.forEach(function (category) {
    var prettified = category.replace(/[^a-zA-Z]/g, "")
    categoriesHTML += '
';
      categoriesHTML += '';
        categoriesHTML += '
';
          categoriesHTML += '
';
            categoriesHTML += category;
          categoriesHTML += '
';
        categoriesHTML += '
';
      categoriesHTML += '';
      categoriesHTML += '
';
        categoriesHTML += listServices(category);
      categoriesHTML += '
';
    categoriesHTML += '
';
  });
  $('#accordion').html(categoriesHTML);
}
<|endoftext|>


<commit_before>settings = {
	"host":		"localhost",
	"port":		10089
}

/* dont fuck with me */
module.exports = settings;<commit_msg>Delete settings.js<commit_after><|endoftext|>


<commit_before>// user settings:
var _settings;

var saveSettings = function(){
	localStorage.setItem("user_settings",JSON.stringify(_settings));
}

var initializeSettings = function(){
	_settings = JSON.parse(localStorage.getItem("user_settings"));
	if(_settings === null || _settings === {}){
		_settings = [
			{item:'fullscreenCaptions',value:true},
			{item:'notificationColors',value:true}
		]
		saveSettings();
	}
}

$(document).ready(function(){
	initializeSettings();
});

// Retrieve a setting object by 'item' property (the name) from the _settings arrays for use
var getSetting = function(setting){
	// retrieve the first item from an array of settings that match the given name
	return $.grep(_settings,function(e){return e.item === setting})[0];
};

var settingChanged = function(element){
	var $element = $(element);
	if($element.is(":checkbox")){
		console.log('its a checkbox');
		var setting = getSetting($element.prop('value'));
		if($element.is(":checked")){
			// find the setting for the given checkbox and match the setting to whether or not its checked:
			setting.value = true;

		}
		else{
			setting.value = false;
		}
		console.log(setting.value);
	}

	saveSettings();
};

var settingsDialogue = function(){
	var dialog = $('#d_Settings');
	var settingsDiv = $('#d_Settings #settingsContainer');

	settingsDiv.html("");
	$(_settings).each(function(i,item){

		var settingItem = null;
		var evalItem = item.value;

		switch(typeof evalItem){
			case 'boolean':
				settingItem = $("<input type='checkbox' value='"+item.item+"'" +
				""+
				(item.value === true ? 'checked' : '') + ">"+item.item+"</input>");

				break;
			default:
				console.log('could not evaluate setting ' + item.item);
				break;
		}

		console.log(settingItem);
		settingsDiv.append(settingItem);
		settingsDiv.append('
');
	});

	$(':checkbox').change(function(){
		settingChanged(this);
	});

};<commit_msg>add new 'label' property to settings for easy display<commit_after>// user settings:
var _settings;

var saveSettings = function(){
	localStorage.setItem("user_settings",JSON.stringify(_settings));
}

var initializeSettings = function(){
	_settings = JSON.parse(localStorage.getItem("user_settings"));
	if(_settings === null || _settings === {}){
		_settings = [
			{item:'fullscreenCaptions',value:true,label:'Fullscreen Notifications'},
			{item:'notificationColors',value:true,label:'Notification Colors'}
		]
		saveSettings();
	}
}

$(document).ready(function(){
	initializeSettings();
});

// Retrieve a setting object by 'item' property (the name) from the _settings arrays for use
var getSetting = function(setting){
	// retrieve the first item from an array of settings that match the given name
	return $.grep(_settings,function(e){return e.item === setting})[0];
};

var settingChanged = function(element){
	var $element = $(element);
	if($element.is(":checkbox")){
		console.log('its a checkbox');
		var setting = getSetting($element.prop('value'));
		if($element.is(":checked")){
			// find the setting for the given checkbox and match the setting to whether or not its checked:
			setting.value = true;

		}
		else{
			setting.value = false;
		}
		console.log(setting.value);
	}

	saveSettings();
};

var settingsDialogue = function(){
	var dialog = $('#d_Settings');
	var settingsDiv = $('#d_Settings #settingsContainer');

	settingsDiv.html("");
	$(_settings).each(function(i,item){

		var settingItem = null;
		var evalItem = item.value;

		switch(typeof evalItem){
			case 'boolean':
				settingItem = $("<input type='checkbox' value='"+item.item+"'" +
				""+
				(item.value === true ? 'checked' : '') + ">"+item.label+"</input>");

				break;
			default:
				console.log('could not evaluate setting ' + item.item);
				break;
		}

		console.log(settingItem);
		settingsDiv.append(settingItem);
		settingsDiv.append('
');
	});

	$(':checkbox').change(function(){
		settingChanged(this);
	});

};<|endoftext|>


<commit_before>"use strict";

var graphlib = require('graphlib');
var Graph = graphlib.Graph;
var path = require('path');
var fs = require("fs-extra");
var Pattern = require('./object_factory').Pattern;
var CompileState = require('./object_factory').CompileState;
var PatternGraphDot = require('./pattern_graph_dot');
var PatternRegistry = require('./pattern_registry');

/**
 * Wrapper around a graph library to build a dependency graph of patterns and
 *
 * @param graph {Graph} The graphlib graph object
 * @param timestamp {int} The unix timestamp
 * @param registry {PatternRegistry} Central registry for patterns.
 * @returns {{PatternGraph: PatternGraph}}
 * @constructor Constructs a new PatternGraph from a JSON-style JavaScript object or an empty graph
 * if no argument is given.
 * @see PatternGraph#fromJson
 * @see #540
 */
var PatternGraph = function (graph, timestamp) {

  this.graph = graph || new Graph({
    directed: true
  });
  this.graph.setDefaultEdgeLabel({});

  // Allows faster lookups for patterns by name for each element in the graph
  // The idea here is to make a pattern known to the graph as soon as it exists
  this.patterns = new PatternRegistry();
  this.timestamp = timestamp || new Date().getTime();
};

// shorthand. Use relPath as it is always unique, even with subPatternType
var nodeName =
  p => p instanceof Pattern ? p.relPath : p;

PatternGraph.prototype = {
  /**
   * Creates an independent copy of the graph where nodes and edges can be modified without
   * affecting the source.
   * @param g {PatternGraph}
   */
  clone: function () {
    var json = graphlib.json.write(this.graph);
    var graph = graphlib.json.read(json);
    return new PatternGraph(graph, this.timestamp);
  },

  /**
   *
   * @param pattern {Pattern}
   */
  add: function (pattern) {
    var n = nodeName(pattern);
    if (!this.patterns.has(n)) {
      this.graph.setNode(n, {
        compileState: pattern.compileState
      });

      // TODO events in pattern registry should call add(), instead of having this glue code.
      this.patterns.put(pattern);
    }
  },

  remove: function (pattern) {
    var n = nodeName(pattern);
    this.graph.removeNode(n);
    this.patterns.remove(n);
  },

  /**
   * Removes nodes for which the given predicate function returns false.
   * @param fn {function} which takes a node name as argument
   */
  filter: function (fn) {
    this.graph.nodes().forEach(n => {
      if (!fn(n)) {
        this.remove(n);
      }
    });
  },

  link: function (patternFrom, patternTo) {
    this.add(patternFrom);
    this.add(patternTo);

    let nameFrom = nodeName(patternFrom);
    let nameTo = nodeName(patternTo);
    this.graph.setEdge(nameFrom, nameTo);
  },

  hasLink: function (patternFrom, patternTo) {
    let nameFrom = nodeName(patternFrom);
    let nameTo = nodeName(patternTo);
    return this.graph.hasEdge(nameFrom, nameTo);
  },

  compileOrder: function () {
    var compileStateFilter = function (patterns, n) {
      var node = patterns.get(n);
      return node.compileState !== CompileState.CLEAN;
    };
    // A graph with reversed edges for topological ordering
    let compileGraph = new Graph({
      directed: true
    });

    let changedNodes = this.graph.nodes().filter(n => compileStateFilter(this.patterns, n));
    this.nodes2patterns(changedNodes).forEach(pattern => {
      let patternNode = nodeName(pattern);
      if (!compileGraph.hasNode(patternNode)) {
        compileGraph.setNode(patternNode);
      }
      this.applyReverse(pattern, (from, to) => {
        // FIXME to.compileState == null for pseudopatterns!
        from.compileState = CompileState.NEEDS_REBUILD;
        let fromName = nodeName(from);
        let toName = nodeName(to);
        for (let nodeName of [fromName, toName]) {
          if (!compileGraph.hasNode(nodeName)) {
            compileGraph.setNode(nodeName);
          }
        }
        if (!compileGraph.hasNode(toName)) {
          compileGraph.setNode(toName);
        }
        // reverse!
        compileGraph.setEdge({v:toName, w:fromName});
      });
    });
    // Apply topological sorting, Start at the leafs of the graphs (e.g. atoms) and go further
    // up in the hierarchy
    var o = graphlib.alg.topsort(compileGraph);
    return this.nodes2patterns(o);
  },

  /**
   * Given a node and its predecessor, allows exchanging states between nodes.
   * @param pattern
   * @param fn A function that takes the currently viewed pattern and node data. Allows synching data
   * between patterns and node metadata.
   */
  applyReverse: function (pattern, fn) {
    for (let p of this.lineageR(pattern)) {
      fn(p, pattern);
      this.applyReverse(p, fn);
    }
  },

  node: function (pattern) {
    return this.graph.node(nodeName(pattern));
  },

  /**
   *
   * @param nodes {Array}
   * @return {Array} An Array of Patterns
   */
  nodes2patterns: function (nodes) {
    return nodes.map(n => this.patterns.get(n));
  },

  // TODO cache result in a Map[String, Array]?
  // We trade the pattern.lineage array - O(pattern.lineage.length << |V|) - vs. O(|V|) of the graph.
  // As long as no edges are added or removed, we can cache the result in a Map and just return it.
  lineage: function (pattern) {
    var nodes = this.graph.successors(nodeName(pattern));
    return this.nodes2patterns(nodes);
  },

  /**
   * Returns all patterns that include the given pattern
   * @param pattern {Pattern}
   * @return {*|Array}
   */
  lineageR: function (pattern) {
    var nodes = this.graph.predecessors(nodeName(pattern));
    return this.nodes2patterns(nodes);
  },

  /**
   * Given a {Pattern}, return all {Pattern} objects included in this the given pattern
   * @param pattern
   */
  lineageIndex: function (pattern) {
    var lineage = this.lineage(pattern);
    return lineage.map(p => p.patternPartial);
  },

  /**
   * Given a {Pattern}, return all {Pattern} objects which include the given pattern
   * @param pattern
   */
  lineageRIndex: function (pattern) {
    var lineageR = this.lineageR(pattern);
    return lineageR.map(p => p.patternPartial);
  },

  /**
   *
   * @returns {{timestamp: number, graph}}
   */
  toJson: function () {
    return {
      timestamp: this.timestamp,
      graph: graphlib.json.write(this.graph)
    };
  },

  nodes: function () {
    return this.graph.nodes();
  }
};

/**
 * @return {PatternGraph}
 */
PatternGraph.empty = function () {
  return new PatternGraph(null, 0);
};

/**
 * Parse the graph from a JSON object.
 * @param o The JSON object to read from
 */
PatternGraph.fromJson = function (o) {
  var graph = graphlib.json.read(o.graph);
  return new PatternGraph(graph, o.timestamp);
};

PatternGraph.resolveJsonGraphFile = function (patternlab, file) {
  return path.resolve(patternlab.config.paths.public.root, file || 'dependencyGraph.json');
};

/**
 * Loads a graph from the file. Does not add any patterns from the patternlab object,
 * i.e. graph.patterns will be still empty until all patterns have been processed.
 *
 * @param patternlab
 * @param [file] Optional
 */
PatternGraph.loadFromFile = function (patternlab, file) {
  var jsonGraphFile = this.resolveJsonGraphFile(patternlab, file);

  // File is fresh, so simply constuct an empty graph in memory
  if (!fs.existsSync(jsonGraphFile)) {
    return PatternGraph.empty();
  }

  var obj = fs.readJSONSync(jsonGraphFile);
  return this.fromJson(obj);
};

// Second argument is for unit testing only
PatternGraph.storeToFile = function (patternlab, file) {
  var jsonGraphFile = this.resolveJsonGraphFile(patternlab, file);
  patternlab.graph.timestamp = new Date().getTime();
  fs.writeJSONSync(jsonGraphFile, patternlab.graph.toJson());
};

PatternGraph.exportToDot = function (patternlab, file) {
  var dotFile = this.resolveJsonGraphFile(patternlab, file);
  var g = PatternGraphDot.write(patternlab.graph);
  fs.outputFileSync(dotFile, g);
};

module.exports = {
  PatternGraph: PatternGraph
};
<commit_msg>#540 Only allow linking nodes which have been added before explicitly<commit_after>"use strict";

var graphlib = require('graphlib');
var Graph = graphlib.Graph;
var path = require('path');
var fs = require("fs-extra");
var Pattern = require('./object_factory').Pattern;
var CompileState = require('./object_factory').CompileState;
var PatternGraphDot = require('./pattern_graph_dot');
var PatternRegistry = require('./pattern_registry');

/**
 * Wrapper around a graph library to build a dependency graph of patterns and
 *
 * @param graph {Graph} The graphlib graph object
 * @param timestamp {int} The unix timestamp
 * @param registry {PatternRegistry} Central registry for patterns.
 * @returns {{PatternGraph: PatternGraph}}
 * @constructor Constructs a new PatternGraph from a JSON-style JavaScript object or an empty graph
 * if no argument is given.
 * @see PatternGraph#fromJson
 * @see #540
 */
var PatternGraph = function (graph, timestamp) {

  this.graph = graph || new Graph({
    directed: true
  });
  this.graph.setDefaultEdgeLabel({});

  // Allows faster lookups for patterns by name for each element in the graph
  // The idea here is to make a pattern known to the graph as soon as it exists
  this.patterns = new PatternRegistry();
  this.timestamp = timestamp || new Date().getTime();
};

// shorthand. Use relPath as it is always unique, even with subPatternType
var nodeName =
  p => p instanceof Pattern ? p.relPath : p;

PatternGraph.prototype = {
  /**
   * Creates an independent copy of the graph where nodes and edges can be modified without
   * affecting the source.
   * @param g {PatternGraph}
   */
  clone: function () {
    var json = graphlib.json.write(this.graph);
    var graph = graphlib.json.read(json);
    return new PatternGraph(graph, this.timestamp);
  },

  /**
   *
   * @param pattern {Pattern}
   */
  add: function (pattern) {
    var n = nodeName(pattern);
    if (!this.patterns.has(n)) {
      this.graph.setNode(n, {
        compileState: pattern.compileState
      });

      // TODO events in pattern registry should call add(), instead of having this glue code.
      this.patterns.put(pattern);
    }
  },

  remove: function (pattern) {
    var n = nodeName(pattern);
    this.graph.removeNode(n);
    this.patterns.remove(n);
  },

  /**
   * Removes nodes for which the given predicate function returns false.
   * @param fn {function} which takes a node name as argument
   */
  filter: function (fn) {
    this.graph.nodes().forEach(n => {
      if (!fn(n)) {
        this.remove(n);
      }
    });
  },

  link: function (patternFrom, patternTo) {
    let nameFrom = nodeName(patternFrom);
    let nameTo = nodeName(patternTo);
    for (let name of [nameFrom, nameTo])
    if (!this.patterns.has(name)) {
      throw new Error("Pattern not known: " + name);
    }
    this.graph.setEdge(nameFrom, nameTo);
  },

  hasLink: function (patternFrom, patternTo) {
    let nameFrom = nodeName(patternFrom);
    let nameTo = nodeName(patternTo);
    return this.graph.hasEdge(nameFrom, nameTo);
  },

  compileOrder: function () {
    var compileStateFilter = function (patterns, n) {
      var node = patterns.get(n);
      return node.compileState !== CompileState.CLEAN;
    };
    // A graph with reversed edges for topological ordering
    let compileGraph = new Graph({
      directed: true
    });

    let nodes = this.graph.nodes();
    let changedNodes = nodes.filter(n => compileStateFilter(this.patterns, n));
    this.nodes2patterns(changedNodes).forEach(pattern => {
      let patternNode = nodeName(pattern);
      if (!compileGraph.hasNode(patternNode)) {
        compileGraph.setNode(patternNode);
      }
      this.applyReverse(pattern, (from, to) => {
        // FIXME to.compileState == null for pseudopatterns!
        from.compileState = CompileState.NEEDS_REBUILD;
        let fromName = nodeName(from);
        let toName = nodeName(to);
        for (let nodeName of [fromName, toName]) {
          if (!compileGraph.hasNode(nodeName)) {
            compileGraph.setNode(nodeName);
          }
        }
        if (!compileGraph.hasNode(toName)) {
          compileGraph.setNode(toName);
        }
        // reverse!
        compileGraph.setEdge({v:toName, w:fromName});
      });
    });
    // Apply topological sorting, Start at the leafs of the graphs (e.g. atoms) and go further
    // up in the hierarchy
    var o = graphlib.alg.topsort(compileGraph);
    return this.nodes2patterns(o);
  },

  /**
   * Given a node and its predecessor, allows exchanging states between nodes.
   * @param pattern
   * @param fn A function that takes the currently viewed pattern and node data. Allows synching data
   * between patterns and node metadata.
   */
  applyReverse: function (pattern, fn) {
    for (let p of this.lineageR(pattern)) {
      fn(p, pattern);
      this.applyReverse(p, fn);
    }
  },

  node: function (pattern) {
    return this.graph.node(nodeName(pattern));
  },

  /**
   *
   * @param nodes {Array}
   * @return {Array} An Array of Patterns
   */
  nodes2patterns: function (nodes) {
    return nodes.map(n => this.patterns.get(n));
  },

  // TODO cache result in a Map[String, Array]?
  // We trade the pattern.lineage array - O(pattern.lineage.length << |V|) - vs. O(|V|) of the graph.
  // As long as no edges are added or removed, we can cache the result in a Map and just return it.
  lineage: function (pattern) {
    var nodes = this.graph.successors(nodeName(pattern));
    return this.nodes2patterns(nodes);
  },

  /**
   * Returns all patterns that include the given pattern
   * @param pattern {Pattern}
   * @return {*|Array}
   */
  lineageR: function (pattern) {
    var nodes = this.graph.predecessors(nodeName(pattern));
    return this.nodes2patterns(nodes);
  },

  /**
   * Given a {Pattern}, return all {Pattern} objects included in this the given pattern
   * @param pattern
   */
  lineageIndex: function (pattern) {
    var lineage = this.lineage(pattern);
    return lineage.map(p => p.patternPartial);
  },

  /**
   * Given a {Pattern}, return all {Pattern} objects which include the given pattern
   * @param pattern
   */
  lineageRIndex: function (pattern) {
    var lineageR = this.lineageR(pattern);
    return lineageR.map(p => p.patternPartial);
  },

  /**
   *
   * @returns {{timestamp: number, graph}}
   */
  toJson: function () {
    return {
      timestamp: this.timestamp,
      graph: graphlib.json.write(this.graph)
    };
  },

  nodes: function () {
    return this.graph.nodes();
  }
};

/**
 * @return {PatternGraph}
 */
PatternGraph.empty = function () {
  return new PatternGraph(null, 0);
};

/**
 * Parse the graph from a JSON object.
 * @param o The JSON object to read from
 */
PatternGraph.fromJson = function (o) {
  var graph = graphlib.json.read(o.graph);
  return new PatternGraph(graph, o.timestamp);
};

PatternGraph.resolveJsonGraphFile = function (patternlab, file) {
  return path.resolve(patternlab.config.paths.public.root, file || 'dependencyGraph.json');
};

/**
 * Loads a graph from the file. Does not add any patterns from the patternlab object,
 * i.e. graph.patterns will be still empty until all patterns have been processed.
 *
 * @param patternlab
 * @param [file] Optional
 */
PatternGraph.loadFromFile = function (patternlab, file) {
  var jsonGraphFile = this.resolveJsonGraphFile(patternlab, file);

  // File is fresh, so simply constuct an empty graph in memory
  if (!fs.existsSync(jsonGraphFile)) {
    return PatternGraph.empty();
  }

  var obj = fs.readJSONSync(jsonGraphFile);
  return this.fromJson(obj);
};

// Second argument is for unit testing only
PatternGraph.storeToFile = function (patternlab, file) {
  var jsonGraphFile = this.resolveJsonGraphFile(patternlab, file);
  patternlab.graph.timestamp = new Date().getTime();
  fs.writeJSONSync(jsonGraphFile, patternlab.graph.toJson());
};

PatternGraph.exportToDot = function (patternlab, file) {
  var dotFile = this.resolveJsonGraphFile(patternlab, file);
  var g = PatternGraphDot.write(patternlab.graph);
  fs.outputFileSync(dotFile, g);
};

module.exports = {
  PatternGraph: PatternGraph
};
<|endoftext|>


<commit_before>var Layout = {}

Layout.init = function (msgbus) {

};

layout = Object.create(Layout);

Meteor.startup(function () {
    layout.init(msgbus);
});
<commit_msg>Create first autorun for updating layout<commit_after>var Layout = {}

var autoruns = {
    'centerFocussedItem': function () {
        var focussedItem = uiCore.getFocussedItem();
        if (focussedItem != null) {
            layout.render(focussedItem);
        }
    },
};

Layout.init = function (msgbus) {
    for (func in autoruns) {
        Tracker.autorun(autoruns[func]);
    }
};

Layout.render = function (item) {
    assert_type(item, Item);

    console.log('render item #' + item.id);
};

layout = Object.create(Layout);

Meteor.startup(function () {
    layout.init(msgbus);
});
<|endoftext|>


<commit_before>
"use strict";

const wm = new WeakMap();

/**
 * Extend default $resource service so it returns more intelligent resource
 * objects, e.g. with dirty checking.
 * In Monad, we want to be able to easily append a new item to an array of
 * queried resources. This exposes a `save` method on the array.
 */
export default ['$resource', $resource => {
    return (url, paramDefaults = {}, actions = {}, options = {}) => {
        let res = $resource(url, paramDefaults, actions, options);

        /**
         * Helper method to quickly (un)set bitflags on a model.
         *
         * @param string source The name of the bitflag field. This should of
         *  course contain an integer.
         * @param object mapping A hash mapping names to flags, e.g. {on: 1}.
         *  From then on you can say `if (obj.on) { ... }` and `obj.on = false`.
         */
        res.prototype.setBitflags = function (source, mapping = {}) {
            for (let name in mapping) {
                Object.defineProperty(this, name, {
                    get: () => !!(this[source] & mapping[name]),
                    set: value => {
                        if (!!value) {
                            this[source] |= mapping[name];
                        } else {
                            this[source] &= ~mapping[name];
                        }
                    }
                });
            }
        };

        Object.defineProperty(res.prototype, '$dirty', {
            /**
             * Virtual property to check if res model is "dirty".
             *
             * @return boolean True if dirty, false if pristine.
             */
            get: function () {
                if (wm.get(this).deleted) {
                    return true;
                }
                let initial = wm.get(this).initial || {};
                for (let prop in this) {
                    if (prop.substring(0, 1) == '$') {
                        continue;
                    }
                    if (differs(res, this[prop], initial[prop])) {
                        return true;
                    }
                }
                return false;
            }
        });

        Object.defineProperty(res.prototype, '$title', {
            /**
             * Guesstimate the "title" of a particular model instance.
             *
             * @return string A guesstimated title.
             */
            get: function () {
                // First, the usual suspects:
                if ('title' in this) {
                    return this.title;
                }
                if ('name' in this) {
                    return this.name;
                }
                // If any field is an actual string _and_ its length is shorter
                // than 255 chars (reasonable maximum...) use that:
                for (let prop in this) {
                    if (prop.substring(0, 1) == '$') {
                        continue;
                    }
                    if (typeof this[prop] == 'string' && this[prop].length && res[prop].length <= 255) {
                        return this[prop];
                    }
                }
                return '[Untitled]';
            }
        });

        Object.defineProperty(res.prototype, '$deleted', {
            /**
             * Get private deleted state.
             *
             * @return bool
             */
            get: function () {
                return wm.get(this).deleted;
            },
            /**
             * Set the deleted state. Note that res does _not_ call the "$delete"
             * resource method.
             *
             * @param mixed value Truthy for "scheduled for deletion".
             */
            set: function (value) {
                wm.get(this).deleted = !!value;
            }
        });

        let query = res.query;
        let get = res.get;
        res.query = (parameters, success, error) => {
            let found = query.call(res, parameters, success, error);
            found.$promise.then(() => {
                found.map(item => {
                    wm.set(item, {initial: copy(item), deleted: false});
                });
                return found;
            });
            found.prototype.push = function (obj) {
                [].call(this, 'push', new res(obj));
            };
            found.prototype.$save = function () {
                for (let i = 0; i < this.length; i++) {
                    if (angular.isArray(this[i]) && '$save' in this[i] && this[i].$dirty) {
                        this[i].$save();
                        continue;
                    }
                    if (this[i].$deleted) {
                        this[i].$delete();
                    } else if (this[i].$dirty) {
                        this[i].$save();
                    }
                }
            };
            Object.defineProperty(found.prototype, '$dirty', {
                get: function () {
                    for (let i = 0; i < this.length; i++) {
                        if (this[i].$dirty) {
                            return true;
                        }
                    }
                    return false;
                }
            });
            return found;
        };
        res.get = (parameters, success, error) => {
            let resource = get.call(res, parameters, success, error);
            wm.set(resource, {initial: undefined, deleted: false});
            resource.$promise.then(() => {
                wm.get(resource).initial = copy(resource);
            });
            return resource;
        };
        return res;
    };
}];

/**
 * Private helper to copy a resource, values only.
 *
 * @param Resource resource An Angular resource instance to copy (or anything
 *  else, really - it just copies objects excluding properties starting with $
 *  (special in Angular) and methods.
 */
function copy(resource) {
    let copied = angular.copy(resource);
    let store = {};
    for (let i in copied) {
        if (i.substring(0, 1) != '$' && typeof copied[i] != 'function') {
            store[i] = copied[i];
        }
    }
    return store;
}

/**
 * Private helper to check if a field is actually set.
 * Unset is defined as undefined or null, string with no length OR
 * a string consisting only of

 (for WYSIWTYG fields).
 *
 * @param mixed val The value to check.
 * @return bool True if the value is considered "empty", else false.
 */
function isset(val) {
    return !!(val !== undefined && val !== null && ('' + val).trim().length && !('' + val).trim().match(/^
(\s|\n| )*<\/p>$/));
}

/**
 * Private helper to determine if two values differ. Values can be of any type;
 * the helper guesstimates and recurses as needed.
 *
 * @param Resource comp Resource to check type against.
 * @param mixed a Value to check.
 * @param mixed b Value to compare to.
 * @return bool
 */
function differs(comp, a, b) {
    if ((!isset(a) && isset(b)) || (isset(a) && !isset(b))) {
        return true;
    }
    if (!a && !b) {
        return false;
    }
    // If `a` is "dirty" we don't even have to do any other checks.
    if (a instanceof comp) {
        return a.$dirty || a.$deleted;
    }
    if (angular.isArray(a) && angular.isArray(b)) {
        if (a.length != b.length) {
            return true;
        }
        for (let i = 0; i < a.length; i++) {
            if (differs(comp, a[i], b[i])) {
                return true;
            }
        }
    }
    if ((a instanceof Date) && (b instanceof Date)) {
        return a  b;
    }
    if (typeof a == 'object' && typeof b == 'object') {
        for (let i in a) {
            if (i.substring(0, 1) == '$') {
                continue;
            }
            if (differs(comp, a[i], b[i])) {
                return true;
            }
        }
        for (let i in b) {
            if (i.substring(0, 1) == '$') {
                continue;
            }
            if (!(i in a)) {
                return true;
            }
        }
        return false;
    }
    return ('' + a).trim() != ('' + b).trim();
}

<commit_msg>check if function<commit_after>
"use strict";

const wm = new WeakMap();

/**
 * Extend default $resource service so it returns more intelligent resource
 * objects, e.g. with dirty checking.
 * In Monad, we want to be able to easily append a new item to an array of
 * queried resources. This exposes a `save` method on the array.
 */
export default ['$resource', $resource => {
    return (url, paramDefaults = {}, actions = {}, options = {}) => {
        let res = $resource(url, paramDefaults, actions, options);

        /**
         * Helper method to quickly (un)set bitflags on a model.
         *
         * @param string source The name of the bitflag field. This should of
         *  course contain an integer.
         * @param object mapping A hash mapping names to flags, e.g. {on: 1}.
         *  From then on you can say `if (obj.on) { ... }` and `obj.on = false`.
         */
        res.prototype.setBitflags = function (source, mapping = {}) {
            for (let name in mapping) {
                Object.defineProperty(this, name, {
                    get: () => !!(this[source] & mapping[name]),
                    set: value => {
                        if (!!value) {
                            this[source] |= mapping[name];
                        } else {
                            this[source] &= ~mapping[name];
                        }
                    }
                });
            }
        };

        Object.defineProperty(res.prototype, '$dirty', {
            /**
             * Virtual property to check if res model is "dirty".
             *
             * @return boolean True if dirty, false if pristine.
             */
            get: function () {
                if (wm.get(this).deleted) {
                    return true;
                }
                let initial = wm.get(this).initial || {};
                for (let prop in this) {
                    if (prop.substring(0, 1) == '$' || typeof this[prop] == 'function') {
                        continue;
                    }
                    if (differs(res, this[prop], initial[prop])) {
                        return true;
                    }
                }
                return false;
            }
        });

        Object.defineProperty(res.prototype, '$title', {
            /**
             * Guesstimate the "title" of a particular model instance.
             *
             * @return string A guesstimated title.
             */
            get: function () {
                // First, the usual suspects:
                if ('title' in this) {
                    return this.title;
                }
                if ('name' in this) {
                    return this.name;
                }
                // If any field is an actual string _and_ its length is shorter
                // than 255 chars (reasonable maximum...) use that:
                for (let prop in this) {
                    if (prop.substring(0, 1) == '$') {
                        continue;
                    }
                    if (typeof this[prop] == 'string' && this[prop].length && res[prop].length <= 255) {
                        return this[prop];
                    }
                }
                return '[Untitled]';
            }
        });

        Object.defineProperty(res.prototype, '$deleted', {
            /**
             * Get private deleted state.
             *
             * @return bool
             */
            get: function () {
                return wm.get(this).deleted;
            },
            /**
             * Set the deleted state. Note that res does _not_ call the "$delete"
             * resource method.
             *
             * @param mixed value Truthy for "scheduled for deletion".
             */
            set: function (value) {
                wm.get(this).deleted = !!value;
            }
        });

        let query = res.query;
        let get = res.get;
        res.query = (parameters, success, error) => {
            let found = query.call(res, parameters, success, error);
            found.$promise.then(() => {
                found.map(item => {
                    wm.set(item, {initial: copy(item), deleted: false});
                });
                return found;
            });
            found.push = function (obj) {
                [].call(this, 'push', new res(obj));
            };
            found.$save = function () {
                for (let i = 0; i < this.length; i++) {
                    if (angular.isArray(this[i]) && '$save' in this[i] && this[i].$dirty) {
                        this[i].$save();
                        continue;
                    }
                    if (this[i].$deleted) {
                        this[i].$delete();
                    } else if (this[i].$dirty) {
                        this[i].$save();
                    }
                }
            };
            Object.defineProperty(found, '$dirty', {
                get: function () {
                    for (let i = 0; i < this.length; i++) {
                        if (this[i].$dirty) {
                            return true;
                        }
                    }
                    return false;
                }
            });
            return found;
        };
        res.get = (parameters, success, error) => {
            let resource = get.call(res, parameters, success, error);
            wm.set(resource, {initial: undefined, deleted: false});
            resource.$promise.then(() => {
                wm.get(resource).initial = copy(resource);
            });
            return resource;
        };
        return res;
    };
}];

/**
 * Private helper to copy a resource, values only.
 *
 * @param Resource resource An Angular resource instance to copy (or anything
 *  else, really - it just copies objects excluding properties starting with $
 *  (special in Angular) and methods.
 */
function copy(resource) {
    let copied = angular.copy(resource);
    let store = {};
    for (let i in copied) {
        if (i.substring(0, 1) != '$' && typeof copied[i] != 'function') {
            store[i] = copied[i];
        }
    }
    return store;
}

/**
 * Private helper to check if a field is actually set.
 * Unset is defined as undefined or null, string with no length OR
 * a string consisting only of

 (for WYSIWTYG fields).
 *
 * @param mixed val The value to check.
 * @return bool True if the value is considered "empty", else false.
 */
function isset(val) {
    return !!(val !== undefined && val !== null && ('' + val).trim().length && !('' + val).trim().match(/^
(\s|\n| )*<\/p>$/));
}

/**
 * Private helper to determine if two values differ. Values can be of any type;
 * the helper guesstimates and recurses as needed.
 *
 * @param Resource comp Resource to check type against.
 * @param mixed a Value to check.
 * @param mixed b Value to compare to.
 * @return bool
 */
function differs(comp, a, b) {
    if ((!isset(a) && isset(b)) || (isset(a) && !isset(b))) {
        return true;
    }
    if (!a && !b) {
        return false;
    }
    // If `a` is "dirty" we don't even have to do any other checks.
    if (a instanceof comp) {
        return a.$dirty || a.$deleted;
    }
    if (angular.isArray(a) && angular.isArray(b)) {
        if (a.length != b.length) {
            return true;
        }
        for (let i = 0; i < a.length; i++) {
            if (differs(comp, a[i], b[i])) {
                return true;
            }
        }
    }
    if ((a instanceof Date) && (b instanceof Date)) {
        return a  b;
    }
    if (typeof a == 'object' && typeof b == 'object') {
        for (let i in a) {
            if (i.substring(0, 1) == '$') {
                continue;
            }
            if (differs(comp, a[i], b[i])) {
                return true;
            }
        }
        for (let i in b) {
            if (i.substring(0, 1) == '$') {
                continue;
            }
            if (!(i in a)) {
                return true;
            }
        }
        return false;
    }
    return ('' + a).trim() != ('' + b).trim();
}

<|endoftext|>