<commit_before>'use strict';
/* globals _ */
/* 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 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 implementation of artist name lookup by selector
		 *
		 * Override this method for more complex behaviour
		 *
		 * @returns {string|null}
		 */
		this.getArtist = function () {
			var text = $(this.artistSelector).text();
			return text || null;
		};

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

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

		/**
		 * 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 () {
			return null;
		};

		/**
		 * 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();
			var separator = this.findSeparator(text);

			var artist = null;
			var track = null;

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

			return {artist: artist, track: track};
		};

		/**
		 * 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;
		};

		// --- 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
		};

		/**
		 * 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 newTrack = this.getTrack();
			var newArtist = this.getArtist();

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

			if (newTrack !== currentState.track) {
				currentState.track = newTrack;
				changedFields.push('track');
			}

			if (newArtist !== currentState.artist) {
				currentState.artist = newArtist;
				changedFields.push('artist');
			}

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

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

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

			var newCurrentTime = 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();
			if (newTrackArt !== currentState.trackArt) {
				currentState.trackArt = newTrackArt;
				changedFields.push('trackArt');
			}

			// take action if needed
			if (changedFields.length > 0 && this.reactorCallback !== null) {
				this.reactorCallback(currentState, changedFields);
			}
		}.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 () {
			/**
			 * 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;
		};

		/**
		 * 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;
			}

			// care - minus vs hyphen
			var separators = [' - ', ' – ', '-', '–', ':'];

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

			return null;
		};

	};

/**
 * Create object to be overridden in specific connector implementation
 * @type {BaseConnector}
 */
var Connector = new BaseConnector();
<commit_msg>em dash as artist/track separator<commit_after>'use strict';
/* globals _ */
/* 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 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 implementation of artist name lookup by selector
		 *
		 * Override this method for more complex behaviour
		 *
		 * @returns {string|null}
		 */
		this.getArtist = function () {
			var text = $(this.artistSelector).text();
			return text || null;
		};

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

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

		/**
		 * 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 () {
			return null;
		};

		/**
		 * 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();
			var separator = this.findSeparator(text);

			var artist = null;
			var track = null;

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

			return {artist: artist, track: track};
		};

		/**
		 * 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;
		};

		// --- 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
		};

		/**
		 * 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 newTrack = this.getTrack();
			var newArtist = this.getArtist();

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

			if (newTrack !== currentState.track) {
				currentState.track = newTrack;
				changedFields.push('track');
			}

			if (newArtist !== currentState.artist) {
				currentState.artist = newArtist;
				changedFields.push('artist');
			}

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

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

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

			var newCurrentTime = 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();
			if (newTrackArt !== currentState.trackArt) {
				currentState.trackArt = newTrackArt;
				changedFields.push('trackArt');
			}

			// take action if needed
			if (changedFields.length > 0 && this.reactorCallback !== null) {
				this.reactorCallback(currentState, changedFields);
			}
		}.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 () {
			/**
			 * 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;
		};

		/**
		 * 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;
			}

			// care - minus vs hyphen
			var separators = [' - ', ' – ', ' — ', '-', '–', '—', ':'];

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

			return null;
		};

	};

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


<commit_before>/**
 * Paginator class module
 * @module classes/Paginator
 */

'use strict';

// var _ = require('lodash');
var Display = require('./Display');
var Page = require('./Page');
var parser = require('./paginator/parser');

var errors = require('./paginator/errors'),
  InvalidPageRankError = errors.InvalidPageRankError;

/**
 * Paginator is the page manager
 * @constructor
 * @param {string} pageFormatLabel The label of the paper format for all pages. For example, 'A4'
 * @param {string} pageOrientation The label of the orientation for all pages. May be 'portait' or 'landscape'
 * @param {DOMDocument} doc The document given by editor.getDoc() of the tinymce API
 *
 * @example
 paginator = new Paginator('A4','portait', editor.getDoc());
 *
 * @see utils/page-formats
 */
function Paginator(pageFormatLabel, pageOrientation, ed){

  editor = ed;
  /**
   * The DOMDocument given in the constructor
   * @property {DOMDocument}
   * @private
   */
  this._document = ed.getDoc();
  /**
   * The Display to manage screen and dimensions
   * @property {Display}
   * @private
   */
  this._display = new Display(this._document);
  /**
   * The default abstract page from all real pages inherits
   * @property {Page}
   * @private
   */
  this._defaultPage = new Page(pageFormatLabel, pageOrientation);
  /**
   * The body element of the full document
   * @property {Element}
   * @private
   */
  this._body = this._document.getElementsByTagName('body');

}

/**
 * The current page
 * @property {Page}
 * @private
 */
var currentPage;

/**
 * The list of pages
 * @property {Array}
 * @private
 */
var pages = [];

/**
 * Current editor
 * @property {Editor}
 * @private
 */
var editor;

/**
 * Get the current page
 * @method
 * @return {Page} the current page loaded in editor
 */
Paginator.prototype.getCurrentPage = function(){
  return currentPage;
};

/**
 * Get the page with the given rank
 * @method
 * @param {Number} rank The requested page rank
 * @return {Page} The requested page
 */
Paginator.prototype.getPage = function(rank){
  if (!pages.length)
    throw new Error('Paginator pages length in null. Can\'t iterate on it.');

  var isLower = rank-1 < 0;
  var isGreater = rank-1 > pages.length;

  if (isLower || isGreater) throw new InvalidPageRankError(rank);
  else return pages[rank-1];
};

/**
 * Get all pages in paginator
 * @method
 * @return {Array<Page>} all paginator pages
 */
Paginator.prototype.getPages = function(){
  return pages;
};

/**
 * Return the previous page
 * @method
 * @return {Page} The previous page
 */
Paginator.prototype.getPrevious = function(){
  try {
    console.log('this.getCurrentPage()',currentPage);
    return this.getPage(this.getCurrentPage().rank-1);
  } catch(err) {
    console.error(err.stck);
    return null;
  }
};

/**
 * Get the next page
 * @method
 * @return {Page} The next page
 */
Paginator.prototype.getNext = function(){
  try {
    return this.getPage(this.getCurrentPage().rank+1);
  } catch(err) {
    return null;
  }
};

/**
 * Navigate to the given page
 * @method
 * @param {Page} the page to navigate to
 * @return void
 */
Paginator.prototype.gotoPage = function(toPage){

  // Show the destination page
  $(toPage._content).css({ 'display': 'block' });

  // Hide all other pages
  $.each(pages,function(i, loopPage){
    if (toPage.rank !== loopPage.rank) {
      $(loopPage._content).css({ 'display': 'none' });
    }
  });

  currentPage = toPage;

};

/**
 * Get the currently focused page div
 * @method
 * @return {Element} The parent div element having an attribute data-paginator
 */
Paginator.prototype.getFocusedPageDiv = function(){
  var selectedElement = editor.selection.getRng().startContainer;
  var parents = editor.dom.getParents(selectedElement,'div',editor.getDoc().body);
  var ret;
  $.each(parents,function(i,parent){
    if ($(parent).attr('data-paginator')) {
      ret = parent;
    }
  });
  if (!ret) throw new Error('No parent page found ! You are out of a page.');
  else return ret;
};

/**
 * Go to the page having the focus
 * @method
 * @return void
 */
Paginator.prototype.gotoFocusedPage = function(){
  var focusedDiv = this.getFocusedPageDiv();
  var pageRank = $(focusedDiv).attr('data-paginator-page-rank');
  var focusedPage = this.getPage(pageRank);
  currentPage = focusedPage;
  this.gotoPage(focusedPage);
};

/**
 * Navigate to the previous page
 * @method
 * @return {Page} The previous page after navigation is done
 */
Paginator.prototype.gotoPrevious = function(){
  console.info('goto previous page');
  return this.gotoPage(this.getPrevious());
};

/**
 * Navigate to the next page
 * @method
 * @return {Page} The next page after navigation is done
 */
Paginator.prototype.gotoNext = function(){
  console.info('goto next page');
  return this.gotoPage(this.getNext());
};

/**
 * Watch the current page, to check if content overflows the page's max-height.
 * @method
 * @return void
 * @todo If it overflows, put the content that overflows in the next page, else, check if
 * the text on the next page can fill the current one without overflowing.
 */
Paginator.prototype.watchPage = function(){

  // console.log('body clientHeight', this._body.clientHeight,'body scrollHeight', this._body.scrollHeight);

  // console.log('padding',padding);

  // console.log('default height:', this._defaultPage.height);
  // console.log('page (Display) height (px): ', this._display.height('px') + ' px');
  // console.log('page (Display) height (mm): ', this._display.height('mm') + ' mm');
  // console.log('page content', page.content());
};

/**
 * Get the current computed padding
 * @method
 * @return {object}
 */
Paginator.prototype._getDocPadding = function(){
  var that = this;
  return {
    top: $(that._body).css('padding-top'),
    right: $(that._body).css('padding-right'),
    bottom: $(that._body).css('padding-bottom'),
    left: $(that._body).css('padding-left')
  };
}

/**
 * Compute the page inner height in pixels
 * @method
 * @return {Number} The resulted height in pixels
 */
Paginator.prototype._getPageInnerHeight = function(){

  var outerHeight = Number(this._display.mm2px(this._defaultPage.height)*10); // @TODO (*10) is a bug fix
  var docPadding = this._getDocPadding();
  var paddingTop = Number(docPadding.top.split('px').join(''));
  var paddingBottom = Number(docPadding.bottom.split('px').join(''));

  var innerHeight = outerHeight - paddingTop - paddingBottom;

  return innerHeight-1 // -1 because of a bug in border-bottom pdf rendering
};

/**
 * Initialize the paginator
 * @method
 * @return void
 */
Paginator.prototype.init = function(){
  function findPageWrappers(){
    return $('div[data-paginator-page-rank]',that._body);
  }
  var that = this;

  // search the paginator page wrappers
  var wrappedPages = findPageWrappers();
  var wrapper = $('<div>');
  wrapper.attr({
    'data-paginator': true,
    'data-paginator-page-rank': 1
  }).css({
    'page-break-after': 'always',
    'height': that._getPageInnerHeight(),
    // 'border': 'solid red 1px',
    // 'background': 'yellow',
    'overflow': 'hidden'
  });

  // wrap unwrapped content
  if (!wrappedPages.length){
    $(this._body).wrapInner(wrapper);
    wrappedPages = findPageWrappers();
  }

  pages = [];
  $.each(wrappedPages,function(i,el){
    pages.push(new Page(that._defaultPage.format().label, that._defaultPage.orientation, i+1, el));
  });

  console.log(pages);

};

/**
 * Paginator class
 */
module.exports = Paginator;
<commit_msg>fix doc comments<commit_after>/**
 * Paginator class module
 * @module classes/Paginator
 */

'use strict';

// var _ = require('lodash');
var Display = require('./Display');
var Page = require('./Page');
var parser = require('./paginator/parser');

var errors = require('./paginator/errors'),
  InvalidPageRankError = errors.InvalidPageRankError;

/**
 * Paginator is the page manager
 * @constructor
 * @param {string} pageFormatLabel The label of the paper format for all pages. For example, 'A4'
 * @param {string} pageOrientation The label of the orientation for all pages. May be 'portait' or 'landscape'
 * @param {DOMDocument} doc The document given by editor.getDoc() of the tinymce API
 *
 * @example
 paginator = new Paginator('A4','portait', editor.getDoc());
 *
 * @see utils/page-formats
 */
function Paginator(pageFormatLabel, pageOrientation, ed){

  editor = ed;
  /**
   * The DOMDocument given in the constructor
   * @property {DOMDocument}
   * @private
   */
  this._document = ed.getDoc();
  /**
   * The Display to manage screen and dimensions
   * @property {Display}
   * @private
   */
  this._display = new Display(this._document);
  /**
   * The default abstract page from all real pages inherits
   * @property {Page}
   * @private
   */
  this._defaultPage = new Page(pageFormatLabel, pageOrientation);
  /**
   * The body element of the full document
   * @property {Element}
   * @private
   */
  this._body = this._document.getElementsByTagName('body');

}

/**
 * The current page
 * @property {Page}
 * @private
 */
var currentPage;

/**
 * The list of pages
 * @property {Array}
 * @private
 */
var pages = [];

/**
 * Current editor
 * @property {Editor}
 * @private
 */
var editor;

/**
 * Get the current page
 * @method
 * @return {Page} the current page loaded in editor
 */
Paginator.prototype.getCurrentPage = function(){
  return currentPage;
};

/**
 * Get the page with the given rank
 * @method
 * @param {Number} rank The requested page rank
 * @return {Page} The requested page
 */
Paginator.prototype.getPage = function(rank){
  if (!pages.length)
    throw new Error('Paginator pages length in null. Can\'t iterate on it.');

  var isLower = rank-1 < 0;
  var isGreater = rank-1 > pages.length;

  if (isLower || isGreater) throw new InvalidPageRankError(rank);
  else return pages[rank-1];
};

/**
 * Get all pages in paginator
 * @method
 * @return {Array<Page>} all paginator pages
 */
Paginator.prototype.getPages = function(){
  return pages;
};

/**
 * Return the previous page
 * @method
 * @return {Page} The previous page
 */
Paginator.prototype.getPrevious = function(){
  try {
    console.log('this.getCurrentPage()',currentPage);
    return this.getPage(this.getCurrentPage().rank-1);
  } catch(err) {
    console.error(err.stck);
    return null;
  }
};

/**
 * Get the next page
 * @method
 * @return {Page} The next page
 */
Paginator.prototype.getNext = function(){
  try {
    return this.getPage(this.getCurrentPage().rank+1);
  } catch(err) {
    return null;
  }
};

/**
 * Navigate to the given page
 * @method
 * @param {Page} the page to navigate to
 * @return void
 */
Paginator.prototype.gotoPage = function(toPage){

  // Show the destination page
  $(toPage._content).css({ 'display': 'block' });

  // Hide all other pages
  $.each(pages,function(i, loopPage){
    if (toPage.rank !== loopPage.rank) {
      $(loopPage._content).css({ 'display': 'none' });
    }
  });

  currentPage = toPage;

};

/**
 * Get the currently focused page div
 * @method
 * @return {Element} The parent div element having an attribute data-paginator
 */
Paginator.prototype.getFocusedPageDiv = function(){
  var selectedElement = editor.selection.getRng().startContainer;
  var parents = editor.dom.getParents(selectedElement,'div',editor.getDoc().body);
  var ret;
  $.each(parents,function(i,parent){
    if ($(parent).attr('data-paginator')) {
      ret = parent;
    }
  });
  if (!ret) throw new Error('No parent page found ! You are out of a page.');
  else return ret;
};

/**
 * Go to the page having the focus
 * @method
 * @return void
 */
Paginator.prototype.gotoFocusedPage = function(){
  var focusedDiv = this.getFocusedPageDiv();
  var pageRank = $(focusedDiv).attr('data-paginator-page-rank');
  var focusedPage = this.getPage(pageRank);
  currentPage = focusedPage;
  this.gotoPage(focusedPage);
};

/**
 * Navigate to the previous page
 * @method
 * @return {Page} The previous page after navigation is done
 */
Paginator.prototype.gotoPrevious = function(){
  console.info('goto previous page');
  return this.gotoPage(this.getPrevious());
};

/**
 * Navigate to the next page
 * @method
 * @return {Page} The next page after navigation is done
 */
Paginator.prototype.gotoNext = function(){
  console.info('goto next page');
  return this.gotoPage(this.getNext());
};

/**
 * Watch the current page, to check if content overflows the page's max-height.
 * @method
 * @return void
 */
Paginator.prototype.watchPage = function(){

  // console.log('body clientHeight', this._body.clientHeight,'body scrollHeight', this._body.scrollHeight);

  // console.log('padding',padding);

  // console.log('default height:', this._defaultPage.height);
  // console.log('page (Display) height (px): ', this._display.height('px') + ' px');
  // console.log('page (Display) height (mm): ', this._display.height('mm') + ' mm');
  // console.log('page content', page.content());
};

/**
 * Get the current computed padding
 * @method
 * @return {object}
 */
Paginator.prototype._getDocPadding = function(){
  var that = this;
  return {
    top: $(that._body).css('padding-top'),
    right: $(that._body).css('padding-right'),
    bottom: $(that._body).css('padding-bottom'),
    left: $(that._body).css('padding-left')
  };
}

/**
 * Compute the page inner height in pixels
 * @method
 * @return {Number} The resulted height in pixels
 */
Paginator.prototype._getPageInnerHeight = function(){

  var outerHeight = Number(this._display.mm2px(this._defaultPage.height)*10); // @TODO (*10) is a bug fix
  var docPadding = this._getDocPadding();
  var paddingTop = Number(docPadding.top.split('px').join(''));
  var paddingBottom = Number(docPadding.bottom.split('px').join(''));

  var innerHeight = outerHeight - paddingTop - paddingBottom;

  return innerHeight-1 // -1 because of a bug in border-bottom pdf rendering
};

/**
 * Initialize the paginator
 * @method
 * @return void
 */
Paginator.prototype.init = function(){
  function findPageWrappers(){
    return $('div[data-paginator-page-rank]',that._body);
  }
  var that = this;

  // search the paginator page wrappers
  var wrappedPages = findPageWrappers();
  var wrapper = $('<div>');
  wrapper.attr({
    'data-paginator': true,
    'data-paginator-page-rank': 1
  }).css({
    'page-break-after': 'always',
    'height': that._getPageInnerHeight(),
    // 'border': 'solid red 1px',
    // 'background': 'yellow',
    'overflow': 'hidden'
  });

  // wrap unwrapped content
  if (!wrappedPages.length){
    $(this._body).wrapInner(wrapper);
    wrappedPages = findPageWrappers();
  }

  pages = [];
  $.each(wrappedPages,function(i,el){
    pages.push(new Page(that._defaultPage.format().label, that._defaultPage.orientation, i+1, el));
  });

  console.log(pages);

};

/**
 * Paginator class
 */
module.exports = Paginator;
<|endoftext|>


<commit_before>import {
  UUID,
  formatNumber,
  getPercentageChange
} from '../helpers'

import moment from 'moment-shortformat'
import { Base64 } from 'js-base64'
import Item from '../classes/item'

const HOUR = 1000 * 60 * 60

function encode (obj) {
  return Base64.encode(JSON.stringify(obj))
}

function decode (str) {
  return JSON.parse(Base64.decode(str))
}

class Portfolio {
  constructor ({
    id,
    name,
    league,
    history,
    tabs,
    tabItems,
    createdAt
  }) {
    this.id = id || UUID()
    this.name = name
    this.tabs = tabs || []
    this.league = league
    this.history = history || []
    this.tabItems = tabItems || {}
    this.line = {}
    this.listeners = []
    this.createdAt = createdAt || Date.now()
    this.isUpdating = false
    this.isOld = false

    this.migrate()
  }

  migrate () {
    this.history = this.history.map(record => {
      let firstItem = record.items[0]

      if (typeof firstItem === 'object' && !Array.isArray(firstItem)) {
        record.items = record.items.map((v, index) => {
          v.item = Item.toItem(v.item)
          return this.convertReportItemObjectToArray(v)
        })
      }

      if (!record.hash) {
        record.hash = encode({
          total: record.total,
          items: record.items.map(v => [ v[1], v[5] ])
        })
      }

      return record
    })
  }

  // remove old tab item hashes
  cleanup () {
    let { tabs, tabItems } = this
    Object.keys(tabItems).forEach(id => {
      let tab = tabs.find(tab => tab.value === id)
      if (!tab) delete tabItems[id]
    })
  }

  shouldUpdateTabItems (tabId, hash) {
    let itemHash = this.tabItems[tabId]
    if (itemHash == null) {
      return true
    }

    if (itemHash === hash) {
      let lastUpdated = this.lastUpdatedAt()
      if (lastUpdated == null) {
        return true
      }

      if (lastUpdated && (Date.now() - lastUpdated) < HOUR) {
        return false
      }
    }

    return true
  }

  // ran only when a tab is updated
  update (tab, tabItems) {
    let tabItemsHash = encode(tabItems)

    this.lastChecked = Date.now()
    this.isUpdating = this.shouldUpdateTabItems(tab.value, tabItemsHash)
    if (this.isUpdating) {
      this.tabItems[tab.value] = tabItemsHash
      return this.onUpdate()
    }

    return false
  }

  onUpdate () {
    // Cleanup tab item hashes before updating
    this.cleanup()

    // Update portfolio items
    let {tabItems, history, league} = this
    let previous = this.latestReport()
    let prices = CC.Prices[league]
    let cluster = []

    Object.keys(tabItems).forEach(tabId => {
      let items = decode(tabItems[tabId])

      items.forEach(item => {
        item = Item.toItem(item)

        let itemPrice = prices.find(pi => this.isItemPriceObject(item, pi))
        if (!itemPrice) {
          return
        }

        let clusterItemIndex = cluster.findIndex(
          v => {
            let isMatch = v[1] === item.fullName &&
              v[2] === item.variant &&
              v[6] === item.links

            if ('gems' in item.source.category) {
              isMatch = isMatch && v[7] === itemPrice.gemQuality &&
                v[8] === itemPrice.gemLevel
            }

            return isMatch
          }
        )

        if (clusterItemIndex < 0) {
          return cluster.push(this.buildReportItem(item, itemPrice))
        }

        let clusterItem = cluster[clusterItemIndex]
        clusterItem[3] += item.stackSize
        clusterItem[5] += item.stackSize * itemPrice.chaosValue
        clusterItem[clusterItem.length - 1].push([
          [item.stackSize, [item.source.inventoryId, item.source.w, item.source.x, item.source.y]]
        ])
      })
    })

    let report = {
      createdAt: Date.now(),
      total: cluster.reduce((p, c) => p + c[5], 0),
      items: cluster.sort((a, b) =>  b[5] - a[5])
    }

    report.hash = encode({
      total: report.total,
      items: report.items.map(v => [ v[1], v[5] ])
    })

    let pushReport = true

    if (previous && previous.hash === report.hash) {
      pushReport = false
    }

    if (pushReport) {
      this.history.push(report)
    }

    if (previous) {
      while (this.history.length > 24) {
        this.history.shift()
      }
    }

    this.isUpdating = false

    return pushReport
  }

  buildReportItem (item, itemPrice) {
    return [
      itemPrice.icon || item.icon,
      item.fullName,
      item.variant,
      item.stackSize,
      itemPrice.chaosValue,
      item.stackSize * itemPrice.chaosValue,
      item.links,
      itemPrice.gemQuality,
      itemPrice.gemLevel,
      [ this.buildReportItemChild(item) ]
    ]
  }

  buildReportItemChild (item) {
    return [item.stackSize, [item.source.inventoryId, item.source.w, item.source.x, item.source.y]]
  }

  convertReportItemObjectToArray (v) {
    let reportItem = this.buildReportItem(v.item, v.price)

    reportItem[3] = v.stackSize
    reportItem[5] = v.chaosValue

    let children = reportItem[reportItem.length - 1]
    v.children.forEach(cv => children.push(this.buildReportItemChild(cv)))

    return reportItem
  }

  latestReport () {
    return this.history.length ? this.history[this.history.length - 1] : null
  }

  lastUpdatedAt () {
    let report = this.latestReport()
    return report ? report.createdAt : null
  }

  getLastUpdateTime () {
    if (this.isUpdating) {
      return 'updating...'
    }

    let lastUpdatedAt = this.lastUpdatedAt()

    return lastUpdatedAt
      ? moment(lastUpdatedAt).short()
      : 'waiting for data...'
  }

  getCurrencyRate (currency) {
    currency = currency || 'Chaos Orb'

    let prices = CC.Prices[this.league]
    if (prices) {
      let price = prices.find(v => v.fullName === currency)
      return price ? price.chaosValue : 1
    }

    return 1
  }

  getCurrencyShorthand (currency) {
    return Portfolio.Shorthands[currency || 'Chaos Orb'] || 'C'
  }

  isItemPriceObject (item, priceItem) {
    if (priceItem.fullName !== item.fullName) {
      return false
    }

    // Low Confidence Filter
    if ('count' in priceItem && priceItem.count < 1) {
      return false
    }

    // higher order relic check
    if (Item.isRelic(priceItem) && !item.isRelic) {
      return false
    }

    if (priceItem.gemQuality || priceItem.gemLevel) {
      if (item.corrupted && !priceItem.corrupted) {
        return false
      }

      let levelTolerance = Math.max(0, Math.ceil(5 - Math.max(item.level, priceItem.gemLevel) * 0.25))
      let qualityTolerance = 0

      // 17 % and lower can be considered quality 0%
      if (priceItem.gemQuality === 0) {
        qualityTolerance = 17
      }

      // Two % below can be considered quality 20%
      if (priceItem.gemQuality === 20) {
        qualityTolerance = 2
      }

      // Handle Special Gems
      let isSpecialSupport = item.fullName.indexOf('Enhance') > -1
        || item.fullName.indexOf('Empower') > -1
        || item.fullName.indexOf('Enlighten') > -1

      if (isSpecialSupport) {
        levelTolerance = 0
      }

      let levelsClose = Math.abs(priceItem.gemLevel - item.level) <= levelTolerance
      let qualityClose = Math.abs(priceItem.gemQuality - item.quality) <= qualityTolerance

      return levelsClose && qualityClose
    }

    if (priceItem.quality && priceItem.level) {
      return priceItem.quality === item.quality
          && priceItem.level === item.level
    }

    if (priceItem.quality) {
      return priceItem.quality === item.quality
    }

    if (priceItem.level) {
      return priceItem.level === item.level
    }

    if (priceItem.variant && item.variant) {
      // to simplify the logic, we sometimes set a variant event if poe.ninja doesn't
      return priceItem.variant === item.variant
    }

    // Price check fallthroughs
    if (priceItem.links != null) {
      return priceItem.links === item.links
    }

    return true
  }

  getHoldings (currency) {
    let report = this.latestReport()
    let rate = this.getCurrencyRate(currency)
    let shorthand = this.getCurrencyShorthand(currency)
    let total = (report ? report.total : 0) / rate

    return {
      value: total,
      valueFormatted: formatNumber(total),
      currency: shorthand
    }
  }

  getChange (currency) {
    let { history } = this
    let previousHoldings = history.length > 1 ? this.history[this.history.length - 2].total : 0
    let currentHoldings = history.length > 0 ? this.history[this.history.length - 1].total : 0
    let percentageChange = getPercentageChange(previousHoldings, currentHoldings)
    let rate = this.getCurrencyRate(currency)
    let shorthand = this.getCurrencyShorthand(currency)

    return {
      direction: percentageChange.direction,
      directionIndicator: !percentageChange.direction ? '' : percentageChange.direction === 'up' ? '+' : '-',
      directionClassName: percentageChange.direction ? percentageChange.direction : '',
      value: Math.abs(previousHoldings - currentHoldings),
      valueFormatted: formatNumber(Math.abs(previousHoldings - currentHoldings)),
      percentage: percentageChange,
      currency: shorthand
    }
  }

  toObject () {
    return {
      id: this.id,
      name: this.name,
      tabs: this.tabs,
      league: this.league,
      history: this.history,
      createdAt: this.createdAt
    }
  }
}

Portfolio.Shorthands = {
  'Chaos Orb': 'C',
  'Exalted Orb': 'Ex',
  'Jeweller\'s Orb': 'Jew',
  'Gemcutter\'s Prism': 'GCP',
  'Eternal Orb': 'Et',
  'Alchemy Orb': 'Alch',
  'Orb of Alteration': 'Alt',
  'Orb of Fusing': 'Fus',
  'Chromatic Orb': 'Chrome',
  'Augmentation Orb': 'Aug',
  'Vaal Orb': 'Vaal',
  'Cartographer\'s Chisel': 'Chis',
  'Orb of Scouring': 'Scour',
  'Transmuation Orb': 'Trans'
}

export default Portfolio<commit_msg>fix(portfolio): match items on isRelic property as well (#103)<commit_after>import {
  UUID,
  formatNumber,
  getPercentageChange
} from '../helpers'

import moment from 'moment-shortformat'
import { Base64 } from 'js-base64'
import Item from '../classes/item'

const HOUR = 1000 * 60 * 60

function encode (obj) {
  return Base64.encode(JSON.stringify(obj))
}

function decode (str) {
  return JSON.parse(Base64.decode(str))
}

class Portfolio {
  constructor ({
    id,
    name,
    league,
    history,
    tabs,
    tabItems,
    createdAt
  }) {
    this.id = id || UUID()
    this.name = name
    this.tabs = tabs || []
    this.league = league
    this.history = history || []
    this.tabItems = tabItems || {}
    this.line = {}
    this.listeners = []
    this.createdAt = createdAt || Date.now()
    this.isUpdating = false
    this.isOld = false

    this.migrate()
  }

  migrate () {
    this.history = this.history.map(record => {
      let firstItem = record.items[0]

      if (typeof firstItem === 'object' && !Array.isArray(firstItem)) {
        record.items = record.items.map((v, index) => {
          v.item = Item.toItem(v.item)
          return this.convertReportItemObjectToArray(v)
        })
      }

      if (!record.hash) {
        record.hash = encode({
          total: record.total,
          items: record.items.map(v => [ v[1], v[5] ])
        })
      }

      return record
    })
  }

  // remove old tab item hashes
  cleanup () {
    let { tabs, tabItems } = this
    Object.keys(tabItems).forEach(id => {
      let tab = tabs.find(tab => tab.value === id)
      if (!tab) delete tabItems[id]
    })
  }

  shouldUpdateTabItems (tabId, hash) {
    let itemHash = this.tabItems[tabId]
    if (itemHash == null) {
      return true
    }

    if (itemHash === hash) {
      let lastUpdated = this.lastUpdatedAt()
      if (lastUpdated == null) {
        return true
      }

      if (lastUpdated && (Date.now() - lastUpdated) < HOUR) {
        return false
      }
    }

    return true
  }

  // ran only when a tab is updated
  update (tab, tabItems) {
    let tabItemsHash = encode(tabItems)

    this.lastChecked = Date.now()
    this.isUpdating = this.shouldUpdateTabItems(tab.value, tabItemsHash)
    if (this.isUpdating) {
      this.tabItems[tab.value] = tabItemsHash
      return this.onUpdate()
    }

    return false
  }

  onUpdate () {
    // Cleanup tab item hashes before updating
    this.cleanup()

    // Update portfolio items
    let {tabItems, history, league} = this
    let previous = this.latestReport()
    let prices = CC.Prices[league]
    let cluster = []

    Object.keys(tabItems).forEach(tabId => {
      let items = decode(tabItems[tabId])

      items.forEach(item => {
        item = Item.toItem(item)

        let itemPrice = prices.find(pi => this.isItemPriceObject(item, pi))
        if (!itemPrice) {
          return
        }

        let clusterItemIndex = cluster.findIndex(
          v => {
            let isMatch = v[1] === item.fullName &&
              v[2] === item.variant &&
              v[6] === item.links &&
              v[9] === item.isRelic

            if ('gems' in item.source.category) {
              isMatch = isMatch && v[7] === itemPrice.gemQuality &&
                v[8] === itemPrice.gemLevel
            }

            return isMatch
          }
        )

        if (clusterItemIndex < 0) {
          return cluster.push(this.buildReportItem(item, itemPrice))
        }

        let clusterItem = cluster[clusterItemIndex]
        clusterItem[3] += item.stackSize
        clusterItem[5] += item.stackSize * itemPrice.chaosValue
        clusterItem[clusterItem.length - 1].push([
          [item.stackSize, [item.source.inventoryId, item.source.w, item.source.x, item.source.y]]
        ])
      })
    })

    let report = {
      createdAt: Date.now(),
      total: cluster.reduce((p, c) => p + c[5], 0),
      items: cluster.sort((a, b) =>  b[5] - a[5])
    }

    report.hash = encode({
      total: report.total,
      items: report.items.map(v => [ v[1], v[5] ])
    })

    let pushReport = true

    if (previous && previous.hash === report.hash) {
      pushReport = false
    }

    if (pushReport) {
      this.history.push(report)
    }

    if (previous) {
      while (this.history.length > 24) {
        this.history.shift()
      }
    }

    this.isUpdating = false

    return pushReport
  }

  buildReportItem (item, itemPrice) {
    return [
      itemPrice.icon || item.icon,
      item.fullName,
      item.variant,
      item.stackSize,
      itemPrice.chaosValue,
      item.stackSize * itemPrice.chaosValue,
      item.links,
      itemPrice.gemQuality,
      itemPrice.gemLevel,
      item.isRelic || false,
      [ this.buildReportItemChild(item) ]
    ]
  }

  buildReportItemChild (item) {
    return [item.stackSize, [item.source.inventoryId, item.source.w, item.source.x, item.source.y]]
  }

  convertReportItemObjectToArray (v) {
    let reportItem = this.buildReportItem(v.item, v.price)

    reportItem[3] = v.stackSize
    reportItem[5] = v.chaosValue

    let children = reportItem[reportItem.length - 1]
    v.children.forEach(cv => children.push(this.buildReportItemChild(cv)))

    return reportItem
  }

  latestReport () {
    return this.history.length ? this.history[this.history.length - 1] : null
  }

  lastUpdatedAt () {
    let report = this.latestReport()
    return report ? report.createdAt : null
  }

  getLastUpdateTime () {
    if (this.isUpdating) {
      return 'updating...'
    }

    let lastUpdatedAt = this.lastUpdatedAt()

    return lastUpdatedAt
      ? moment(lastUpdatedAt).short()
      : 'waiting for data...'
  }

  getCurrencyRate (currency) {
    currency = currency || 'Chaos Orb'

    let prices = CC.Prices[this.league]
    if (prices) {
      let price = prices.find(v => v.fullName === currency)
      return price ? price.chaosValue : 1
    }

    return 1
  }

  getCurrencyShorthand (currency) {
    return Portfolio.Shorthands[currency || 'Chaos Orb'] || 'C'
  }

  isItemPriceObject (item, priceItem) {
    if (priceItem.fullName !== item.fullName) {
      return false
    }

    // Low Confidence Filter
    if ('count' in priceItem && priceItem.count < 1) {
      return false
    }

    // higher order relic check
    if (Item.isRelic(priceItem) && !item.isRelic) {
      return false
    }

    if (priceItem.gemQuality || priceItem.gemLevel) {
      if (item.corrupted && !priceItem.corrupted) {
        return false
      }

      let levelTolerance = Math.max(0, Math.ceil(5 - Math.max(item.level, priceItem.gemLevel) * 0.25))
      let qualityTolerance = 0

      // 17 % and lower can be considered quality 0%
      if (priceItem.gemQuality === 0) {
        qualityTolerance = 17
      }

      // Two % below can be considered quality 20%
      if (priceItem.gemQuality === 20) {
        qualityTolerance = 2
      }

      // Handle Special Gems
      let isSpecialSupport = item.fullName.indexOf('Enhance') > -1
        || item.fullName.indexOf('Empower') > -1
        || item.fullName.indexOf('Enlighten') > -1

      if (isSpecialSupport) {
        levelTolerance = 0
      }

      let levelsClose = Math.abs(priceItem.gemLevel - item.level) <= levelTolerance
      let qualityClose = Math.abs(priceItem.gemQuality - item.quality) <= qualityTolerance

      return levelsClose && qualityClose
    }

    if (priceItem.quality && priceItem.level) {
      return priceItem.quality === item.quality
          && priceItem.level === item.level
    }

    if (priceItem.quality) {
      return priceItem.quality === item.quality
    }

    if (priceItem.level) {
      return priceItem.level === item.level
    }

    if (priceItem.variant && item.variant) {
      // to simplify the logic, we sometimes set a variant event if poe.ninja doesn't
      return priceItem.variant === item.variant
    }

    // Price check fallthroughs
    if (priceItem.links != null) {
      return priceItem.links === item.links
    }

    return true
  }

  getHoldings (currency) {
    let report = this.latestReport()
    let rate = this.getCurrencyRate(currency)
    let shorthand = this.getCurrencyShorthand(currency)
    let total = (report ? report.total : 0) / rate

    return {
      value: total,
      valueFormatted: formatNumber(total),
      currency: shorthand
    }
  }

  getChange (currency) {
    let { history } = this
    let previousHoldings = history.length > 1 ? this.history[this.history.length - 2].total : 0
    let currentHoldings = history.length > 0 ? this.history[this.history.length - 1].total : 0
    let percentageChange = getPercentageChange(previousHoldings, currentHoldings)
    let rate = this.getCurrencyRate(currency)
    let shorthand = this.getCurrencyShorthand(currency)

    return {
      direction: percentageChange.direction,
      directionIndicator: !percentageChange.direction ? '' : percentageChange.direction === 'up' ? '+' : '-',
      directionClassName: percentageChange.direction ? percentageChange.direction : '',
      value: Math.abs(previousHoldings - currentHoldings),
      valueFormatted: formatNumber(Math.abs(previousHoldings - currentHoldings)),
      percentage: percentageChange,
      currency: shorthand
    }
  }

  toObject () {
    return {
      id: this.id,
      name: this.name,
      tabs: this.tabs,
      league: this.league,
      history: this.history,
      createdAt: this.createdAt
    }
  }
}

Portfolio.Shorthands = {
  'Chaos Orb': 'C',
  'Exalted Orb': 'Ex',
  'Jeweller\'s Orb': 'Jew',
  'Gemcutter\'s Prism': 'GCP',
  'Eternal Orb': 'Et',
  'Alchemy Orb': 'Alch',
  'Orb of Alteration': 'Alt',
  'Orb of Fusing': 'Fus',
  'Chromatic Orb': 'Chrome',
  'Augmentation Orb': 'Aug',
  'Vaal Orb': 'Vaal',
  'Cartographer\'s Chisel': 'Chis',
  'Orb of Scouring': 'Scour',
  'Transmuation Orb': 'Trans'
}

export default Portfolio<|endoftext|>


<commit_before>/* @flow */
/**
 * create.js
 * Written by: Connor Taylor
 */
import log from '../../utils/log';

import generateTemplate from '../../template/generateTemplate';
import writeTemplate from '../../template/writeTemplate';
import loadGlobalConfig from '../../configs/loadGlobalConfig';

/**
 * `cyto create` creates a new template inside of the user's GTL. It does this
 * through generating the `cyto/template` template
 */
export default function create(program) {
  program
    .command('create <id>')
    .description('Create a new cyto template')
    .action(async (id) => {
      const { libraryPath: outputRoot, author } = loadGlobalConfig();

      try {
        const generatedTemplate = await generateTemplate({
          templateId: 'cyto/template',
          args: { id, author },
          outputRoot: '',
        });

        console.log(generatedTemplate);
        writeTemplate(generatedTemplate, outputRoot);
      } catch (e) {
        log.info(e);
      }
    });
}
<commit_msg>remove console log<commit_after>/* @flow */
/**
 * create.js
 * Written by: Connor Taylor
 */
import log from '../../utils/log';

import generateTemplate from '../../template/generateTemplate';
import writeTemplate from '../../template/writeTemplate';
import loadGlobalConfig from '../../configs/loadGlobalConfig';

/**
 * `cyto create` creates a new template inside of the user's GTL. It does this
 * through generating the `cyto/template` template
 */
export default function create(program) {
  program
    .command('create <id>')
    .description('Create a new cyto template')
    .action(async (id) => {
      const { libraryPath: outputRoot, author } = loadGlobalConfig();

      try {
        const generatedTemplate = await generateTemplate({
          templateId: 'cyto/template',
          args: { id, author },
          outputRoot: '',
        });

        writeTemplate(generatedTemplate, outputRoot);
      } catch (e) {
        log.info(e);
      }
    });
}
<|endoftext|>


<commit_before>const SpaaaceClientEngine = require("../client/SpaaaceClientEngine");
const SpaaaceRenderer = require('../client/SpaaaceRenderer');
const SpaaaceGameEngine = require('../common/SpaaaceGameEngine');
const InterpolateStrategy = require('incheon').syncStrategies.InterpolateStrategy;
const ClientPredictionStrategy = require('incheon').syncStrategies.ClientPredictionStrategy;

const renderer = new SpaaaceRenderer();
const gameEngine = new SpaaaceGameEngine({ renderer });
const spaaaceClientEngine = new SpaaaceClientEngine(gameEngine);
new InterpolateStrategy(spaaaceClientEngine, {});
new ClientPredictionStrategy(spaaaceClientEngine, {});

var game = window.game = new Phaser.Game(800, 600, Phaser.AUTO, 'spaaace', { preload: preload, create: create, update: update });

function preload() {
    game.load.image('ship', 'assets/ship1.png');
    game.load.image('missile', 'assets/shot.png');

    game.load.audio('missileHit', 'assets/audio/193429__unfa__projectile-hit.mp3');
    game.load.audio('fireMissile', 'assets/audio/248293__chocobaggy__weird-laser-gun.mp3');
}

function create() {

    game.physics.startSystem(Phaser.Physics.ARCADE);
    game.stage.disableVisibilityChange = true;
    game.stage.backgroundColor = 'black';

    spaaaceClientEngine.start();
}

function update() {

    spaaaceClientEngine.step();
}
<commit_msg>use Synchronizer<commit_after>const SpaaaceClientEngine = require("../client/SpaaaceClientEngine");
const SpaaaceRenderer = require('../client/SpaaaceRenderer');
const SpaaaceGameEngine = require('../common/SpaaaceGameEngine');
const Synchronizer = require('incheon').Synchronizer;

// create a client engine, a game engine, a synchronizer, and a renderer
const renderer = new SpaaaceRenderer();
const gameEngine = new SpaaaceGameEngine({ renderer });
const spaaaceClientEngine = new SpaaaceClientEngine(gameEngine);
const synchronizer = new Synchronizer(spaaaceClientEngine);

// object synchronization:
// use client prediction for objects controlled by the client
// interpolate all the rest
synchronizer.interpolateObjectSelector = (obj) => { return !obj.isPlayerControlled; };
synchronizer.clientPredictionSelector = (obj) => { return obj.isPlayerControlled; };

var game = window.game = new Phaser.Game(800, 600, Phaser.AUTO, 'spaaace', { preload: preload, create: create, update: update });

function preload() {
    game.load.image('ship', 'assets/ship1.png');
    game.load.image('missile', 'assets/shot.png');

    game.load.audio('missileHit', 'assets/audio/193429__unfa__projectile-hit.mp3');
    game.load.audio('fireMissile', 'assets/audio/248293__chocobaggy__weird-laser-gun.mp3');
}

function create() {

    game.physics.startSystem(Phaser.Physics.ARCADE);
    game.stage.disableVisibilityChange = true;
    game.stage.backgroundColor = 'black';

    spaaaceClientEngine.start();
}

function update() {

    spaaaceClientEngine.step();
}
<|endoftext|>


<commit_before>import {Map} from 'immutable';
import {logged} from '../auth/actions';
import {register} from '../dispatcher';
import {userCursor} from '../state';

const getIn = (path) => userCursor().getIn(path);

export const dispatchToken = register(({action, data}) => {

  switch (action) {
    case logged:
      userCursor(user => {
        return user.setIn(['authData'], Map(data));
      });
      break;
  }

});

export function isLoggedIn() {
  // TODO: Use sessionStorage and real redirect to fix Chrome.
  return !!getIn(['authData']);
}
<commit_msg>small change<commit_after>import {Map} from 'immutable';
import {logged} from '../auth/actions';
import {register} from '../dispatcher';
import {userCursor} from '../state';

export const dispatchToken = register(({action, data}) => {

  switch (action) {
    case logged:
      userCursor(user => {
        return user.setIn(['authData'], Map(data));
      });
      break;
  }

});

const getIn = (path) => userCursor().getIn(path);

export function isLoggedIn() {
  // TODO: Use sessionStorage and real redirect to fix Chrome.
  return !!getIn(['authData']);
}
<|endoftext|>


<commit_before>(function (lib, img, cjs, ss) {

var p; // shortcut to reference prototypes

// library properties:
lib.properties = {
	width: 550,
	height: 400,
	fps: 24,
	color: "#999999",
	manifest: []
};

// symbols:

(lib.triangle = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
    this.command = this.shape.graphics.f("#FFFFFF").command;
	this.shape.graphics.s().p("AhZArICAiDIAzCyg");
	this.shape.setTransform(-1.1,-1);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10.2,-10,18.2,18);

(lib.star_10 = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.command = this.shape.graphics.f("#FFFFFF").command;
    this.shape.graphics.s().p("AgMA7IgzAkIASg+Ig5AAIAngfIglgdIA0gBIgVg4IA1AgIAQgyIAOAuIAsggIgQA+IA4ABIgiAZIAoAbIg/ADIASBCIgrggIgMAng");
	this.shape.setTransform(0.6,-1);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-9.8,-11.3,20.8,20.7);

(lib.star = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
    this.command = this.shape.graphics.f("#FFFFFF").command;
	this.shape.graphics.s().p("AAAA2Ig+AsIAXhMIg8gtIBMgBIAYhLIAXBLIBMACIg8AtIAUBNg");
	this.shape.setTransform(0.4,-1.3);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-9.6,-11.3,20,20);

(lib.square = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.command = this.shape.graphics.f("#FFFFFF").command;
    this.shape.graphics.s().dr(-10,-10,20,20);
	this.shape.setTransform(10,10);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(0,0,20,20);

(lib.reverse_blur_circle = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.shape.graphics.rf(["#FFFFFF","rgba(255,255,255,0)","rgba(255,255,255,0)","rgba(255,255,255,0)","#FFFFFF","#FFFFFF","rgba(255,255,255,0)"],[0,0,0.008,0.886,1,1,1],0,0,0,0,0,11).s().de(-10.8,-10.8,21.7,21.7);
	this.shape.setTransform(0,0,0.922,0.922);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10,-10,20,20);

(lib.kirakira2 = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
    this.command = this.shape.graphics.f("#FFFFFF").command;
	this.shape.graphics.s().p("AgKAcQgMgcgRAAQARAAAMgcQAKgcAAgnQAAAnALAcQANAcAQAAQgQAAgNAcQgLAdAAAnQAAgngKgdg");

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-4,-9.6,8.1,19.3);

(lib.kirakira = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
    this.command = this.shape.graphics.f("#FFFFFF").command;
	this.shape.graphics.s().p("AgcAcQgdgcgoAAQAoAAAdgcQAcgdAAgoQAAAoAdAdQAdAcAoAAQgoAAgdAcQgdAeAAAoQAAgogcgeg");

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-9.8,-9.8,19.8,19.8);

(lib.heart = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.shape.this.command = this.shape.graphics.f("#FFFFFF");
    this.shape.graphics.s().p("Ag+AfIgOgUQgHgLgEgIIgIgUQgEgLAAgJQABgMAFgJQAEgJAHgGQAJgGAJgDQAJgCAJAAQALgBAJAEQAHADAHAFQAEAFAEAGIAEAJIAFgJQAEgGAFgFQAGgFAHgDQAJgEALABQAKAAAJACQAJAEAIAFQAHAGAFAJQAFAJAAALQgBAJgDALQgCAKgGAKQgEAJgHAKIgOAUIghAlIgeAdQgpgogVgZg");
	this.shape.setTransform(0,0.9);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10,-8.8,20,19.4);

(lib.flower = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.command = this.shape.graphics.f("#FFFFFF");
    this.shape.graphics.s().p("AAMBXQgLgLAAgQIAAgMIgBAAIgBAMQAAARgLALQgKALgPAAQgQAAgMgLQgNgMAAgPQAAgMAIgLQAIgKAMgEIALgEIAAgCIgBgBIgLAEIgMABQgRAAgLgKQgKgKAAgOQAAgRAKgLQALgMAPAAQAJAAAJAEQAJAFAGAHIAHAKIABgBIACgBIgHgKQgEgFgCgGQgCgHAAgGQAAgQALgKQALgKAPAAQAQAAALAKQAKAKAAAQQAAAHgBAGQgCAGgEAFIgHAKIACABIABABIAHgKQAGgHAIgFQAJgEAJAAQAQAAAKAMQALALAAARQAAAOgLAKQgKAKgRAAIgMgBIgLgEIgBACIAAABIALAEQAMAFAIAKQAHAKAAAMQAAAPgMAMQgMALgQAAQgPAAgLgMgAgXgRQgKAKAAAMQAAAPAKAKQAKAKANAAQAOAAAKgKQAKgKAAgPQAAgMgKgKQgKgKgOAAQgNAAgKAKg");
	this.shape.setTransform(0.1,-0.6);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10.2,-10.5,20.5,20);

(lib.circle = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.command = this.shape.graphics.f("#FFFFFF");
    this.shape.graphics.s().de(-10.8,-10.8,21.7,21.7);
	this.shape.setTransform(0,0,0.922,0.922);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10,-10,20,20);

(lib.blur_circle = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.shape.graphics.rf(["#FFFFFF","rgba(255,255,255,0)"],[0,1],0,0,0,0,0,11).s().de(-10.8,-10.8,21.7,21.7);
	this.shape.setTransform(0,0,0.922,0.922);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10,-10,20,20);

// stage content:
(lib.assetshapes = function() {
	this.initialize();

	// レイヤー 1
	this.instance = new lib.triangle();
	this.instance.setTransform(226.3,21.4,1,1,0,0,0,10,10);

	this.instance_1 = new lib.square();
	this.instance_1.setTransform(192.9,11.3,1,1,0,0,0,10,10);

	this.instance_2 = new lib.kirakira2();
	this.instance_2.setTransform(171.4,11.9);

	this.instance_3 = new lib.kirakira();
	this.instance_3.setTransform(151.6,11.9);

	this.instance_4 = new lib.flower();
	this.instance_4.setTransform(131.1,11.9);

	this.instance_5 = new lib.star_10();
	this.instance_5.setTransform(110.3,11.9);

	this.instance_6 = new lib.star();
	this.instance_6.setTransform(90.3,11.9);

	this.instance_7 = new lib.circle();
	this.instance_7.setTransform(81.5,21.4,1,1,0,0,0,10.8,10.8);

	this.instance_8 = new lib.reverse_blur_circle();
	this.instance_8.setTransform(61.5,21.4,1,1,0,0,0,10.8,10.8);

	this.instance_9 = new lib.blur_circle();
	this.instance_9.setTransform(41.5,20.8,1,1,0,0,0,10.8,10.8);

	this.instance_10 = new lib.heart();
	this.instance_10.setTransform(21.2,21.8,1,1,0,0,0,11.1,12.3);

	this.addChild(this.instance_10,this.instance_9,this.instance_8,this.instance_7,this.instance_6,this.instance_5,this.instance_4,this.instance_3,this.instance_2,this.instance_1,this.instance);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(275,200,224.3,21.8);

})(lib = lib||{}, images = images||{}, createjs = createjs||{}, ss = ss||{});
var lib, images, createjs, ss;<commit_msg>パーティクル：シェイプ選択 - ハートを選択するとエラー停止する件を修正（設定ミス）<commit_after>(function (lib, img, cjs, ss) {

var p; // shortcut to reference prototypes

// library properties:
lib.properties = {
	width: 550,
	height: 400,
	fps: 24,
	color: "#999999",
	manifest: []
};

// symbols:

(lib.triangle = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
    this.command = this.shape.graphics.f("#FFFFFF").command;
	this.shape.graphics.s().p("AhZArICAiDIAzCyg");
	this.shape.setTransform(-1.1,-1);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10.2,-10,18.2,18);

(lib.star_10 = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.command = this.shape.graphics.f("#FFFFFF").command;
    this.shape.graphics.s().p("AgMA7IgzAkIASg+Ig5AAIAngfIglgdIA0gBIgVg4IA1AgIAQgyIAOAuIAsggIgQA+IA4ABIgiAZIAoAbIg/ADIASBCIgrggIgMAng");
	this.shape.setTransform(0.6,-1);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-9.8,-11.3,20.8,20.7);

(lib.star = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
    this.command = this.shape.graphics.f("#FFFFFF").command;
	this.shape.graphics.s().p("AAAA2Ig+AsIAXhMIg8gtIBMgBIAYhLIAXBLIBMACIg8AtIAUBNg");
	this.shape.setTransform(0.4,-1.3);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-9.6,-11.3,20,20);

(lib.square = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.command = this.shape.graphics.f("#FFFFFF").command;
    this.shape.graphics.s().dr(-10,-10,20,20);
	this.shape.setTransform(10,10);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(0,0,20,20);

(lib.reverse_blur_circle = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.shape.graphics.rf(["#FFFFFF","rgba(255,255,255,0)","rgba(255,255,255,0)","rgba(255,255,255,0)","#FFFFFF","#FFFFFF","rgba(255,255,255,0)"],[0,0,0.008,0.886,1,1,1],0,0,0,0,0,11).s().de(-10.8,-10.8,21.7,21.7);
	this.shape.setTransform(0,0,0.922,0.922);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10,-10,20,20);

(lib.kirakira2 = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
    this.command = this.shape.graphics.f("#FFFFFF").command;
	this.shape.graphics.s().p("AgKAcQgMgcgRAAQARAAAMgcQAKgcAAgnQAAAnALAcQANAcAQAAQgQAAgNAcQgLAdAAAnQAAgngKgdg");

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-4,-9.6,8.1,19.3);

(lib.kirakira = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
    this.command = this.shape.graphics.f("#FFFFFF").command;
	this.shape.graphics.s().p("AgcAcQgdgcgoAAQAoAAAdgcQAcgdAAgoQAAAoAdAdQAdAcAoAAQgoAAgdAcQgdAeAAAoQAAgogcgeg");

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-9.8,-9.8,19.8,19.8);

(lib.heart = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.command = this.shape.graphics.f("red").command;
    this.shape.graphics.s().p("Ag+AfIgOgUQgHgLgEgIIgIgUQgEgLAAgJQABgMAFgJQAEgJAHgGQAJgGAJgDQAJgCAJAAQALgBAJAEQAHADAHAFQAEAFAEAGIAEAJIAFgJQAEgGAFgFQAGgFAHgDQAJgEALABQAKAAAJACQAJAEAIAFQAHAGAFAJQAFAJAAALQgBAJgDALQgCAKgGAKQgEAJgHAKIgOAUIghAlIgeAdQgpgogVgZg");
	this.shape.setTransform(0,0.9);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10,-8.8,20,19.4);

(lib.flower = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.command = this.shape.graphics.f("#FFFFFF").command;
    this.shape.graphics.s().p("AAMBXQgLgLAAgQIAAgMIgBAAIgBAMQAAARgLALQgKALgPAAQgQAAgMgLQgNgMAAgPQAAgMAIgLQAIgKAMgEIALgEIAAgCIgBgBIgLAEIgMABQgRAAgLgKQgKgKAAgOQAAgRAKgLQALgMAPAAQAJAAAJAEQAJAFAGAHIAHAKIABgBIACgBIgHgKQgEgFgCgGQgCgHAAgGQAAgQALgKQALgKAPAAQAQAAALAKQAKAKAAAQQAAAHgBAGQgCAGgEAFIgHAKIACABIABABIAHgKQAGgHAIgFQAJgEAJAAQAQAAAKAMQALALAAARQAAAOgLAKQgKAKgRAAIgMgBIgLgEIgBACIAAABIALAEQAMAFAIAKQAHAKAAAMQAAAPgMAMQgMALgQAAQgPAAgLgMgAgXgRQgKAKAAAMQAAAPAKAKQAKAKANAAQAOAAAKgKQAKgKAAgPQAAgMgKgKQgKgKgOAAQgNAAgKAKg");
	this.shape.setTransform(0.1,-0.6);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10.2,-10.5,20.5,20);

(lib.circle = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.command = this.shape.graphics.f("#FFFFFF").command;
    this.shape.graphics.s().de(-10.8,-10.8,21.7,21.7);
	this.shape.setTransform(0,0,0.922,0.922);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10,-10,20,20);

(lib.blur_circle = function() {
	this.initialize();

	// レイヤー 1
	this.shape = new cjs.Shape();
	this.shape.graphics.rf(["#FFFFFF","rgba(255,255,255,0)"],[0,1],0,0,0,0,0,11).s().de(-10.8,-10.8,21.7,21.7);
	this.shape.setTransform(0,0,0.922,0.922);

	this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-10,-10,20,20);

// stage content:
(lib.assetshapes = function() {
	this.initialize();

	// レイヤー 1
	this.instance = new lib.triangle();
	this.instance.setTransform(226.3,21.4,1,1,0,0,0,10,10);

	this.instance_1 = new lib.square();
	this.instance_1.setTransform(192.9,11.3,1,1,0,0,0,10,10);

	this.instance_2 = new lib.kirakira2();
	this.instance_2.setTransform(171.4,11.9);

	this.instance_3 = new lib.kirakira();
	this.instance_3.setTransform(151.6,11.9);

	this.instance_4 = new lib.flower();
	this.instance_4.setTransform(131.1,11.9);

	this.instance_5 = new lib.star_10();
	this.instance_5.setTransform(110.3,11.9);

	this.instance_6 = new lib.star();
	this.instance_6.setTransform(90.3,11.9);

	this.instance_7 = new lib.circle();
	this.instance_7.setTransform(81.5,21.4,1,1,0,0,0,10.8,10.8);

	this.instance_8 = new lib.reverse_blur_circle();
	this.instance_8.setTransform(61.5,21.4,1,1,0,0,0,10.8,10.8);

	this.instance_9 = new lib.blur_circle();
	this.instance_9.setTransform(41.5,20.8,1,1,0,0,0,10.8,10.8);

	this.instance_10 = new lib.heart();
	this.instance_10.setTransform(21.2,21.8,1,1,0,0,0,11.1,12.3);

	this.addChild(this.instance_10,this.instance_9,this.instance_8,this.instance_7,this.instance_6,this.instance_5,this.instance_4,this.instance_3,this.instance_2,this.instance_1,this.instance);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(275,200,224.3,21.8);

})(lib = lib||{}, images = images||{}, createjs = createjs||{}, ss = ss||{});
var lib, images, createjs, ss;<|endoftext|>


<commit_before>function syncDefaultOptions() {
  chrome.storage.sync.getBytesInUse(null, (bytes) => {
    if (bytes === 0) {
      chrome.storage.sync.set({
        lowerRegexLimit: 13,
        upperRegexLimit: 255,
        autoLoad: false,
        autoPlay: false,
      });
    }
  });
}

const activeTabs = {};
const loadedTabs = {};

function load(tabId) {
  chrome.tabs.executeScript(tabId, { file: 'src/bg/bracketless.js' }, (response) => {
    [loadedTabs[tabId]] = response;
    chrome.browserAction.setIcon({ tabId, path: 'icons/play.png' });
    chrome.browserAction.setTitle({ tabId, title: 'Collapse brackets' });
    chrome.tabs.insertCSS(tabId, { file: 'css/toggle.css' });
    chrome.tabs.executeScript(tabId, { file: 'src/bg/toggle_collapse.js' });
  });
}

function doAction(tabId, action) {
  const state = action === 'play' ?
    { message: 'Pause collapsing', collapse: true, icon: 'pause' } :
    { message: 'Collapse brackets', collapse: false, icon: 'play' };
  activeTabs[tabId] = state.collapse;
  chrome.browserAction.setIcon({ tabId, path: `icons/${state.icon}.png` });
  chrome.browserAction.setTitle({ tabId, title: state.message });
  chrome.tabs.sendMessage(tabId, { collapse: state.collapse });
}

function setUpContextMenus() {
  chrome.contextMenus.create({
    id: 'bracketless',
    title: 'Bracketless action',
  });
}
// activate play pause update?
// chrome.contextMenus.update();

function listenerAction(tabId) {
  if (!loadedTabs[tabId]) {
    load(tabId);
  } else if (!activeTabs[tabId]) {
    doAction(tabId, 'play');
  } else if (activeTabs[tabId]) {
    doAction(tabId, 'pause');
  }
}

function optionalPermsCheck() {
  return new Promise(resolve => chrome.permissions.contains({
    permissions: ['tabs'],
    origins: ['http://*/', 'https://*/'],
  }, granted => resolve(granted)));
}

function autoAction(tabPerm) {
  if (!tabPerm) return; // if permission false, return

  chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
    if (changeInfo.status === 'complete' && tab.active && !/(chrome)(?:[/:-])/.test(tab.url)) {
      chrome.storage.sync.get(null, (options) => {
        // race condition, needs to get fixed
        if (options.autoLoad) listenerAction(tabId);
        if (options.autoPlay) listenerAction(tabId);
      });
    }
  });
}

chrome.runtime.onInstalled.addListener(() => {
  syncDefaultOptions();
  setUpContextMenus();
  chrome.browserAction.onClicked.addListener(tab => listenerAction(tab.id));
  chrome.contextMenus.onClicked.addListener((_, tab) => listenerAction(tab.id));
  optionalPermsCheck()
    .then((value) => {
      autoAction(value);
    });
});

<commit_msg>Fix load and play sequencing issue<commit_after>function syncDefaultOptions() {
  chrome.storage.sync.getBytesInUse(null, (bytes) => {
    if (bytes === 0) {
      chrome.storage.sync.set({
        lowerRegexLimit: 13,
        upperRegexLimit: 255,
        autoLoad: false,
        autoPlay: false,
      });
    }
  });
}

const activeTabs = {};
const loadedTabs = {};

function load(tabId) {
  return new Promise((resolve) => {
    chrome.tabs.executeScript(tabId, { file: 'src/bg/bracketless.js' }, (response) => {
      chrome.browserAction.setIcon({ tabId, path: 'icons/play.png' });
      chrome.browserAction.setTitle({ tabId, title: 'Collapse brackets' });
      chrome.tabs.insertCSS(tabId, { file: 'css/toggle.css' });
      chrome.tabs.executeScript(tabId, { file: 'src/bg/toggle_collapse.js' }, () => {
        resolve([loadedTabs[tabId]] = response);
      });
    });
  });
}

function doAction(tabId, action) {
  const state = action === 'play' ?
    { message: 'Pause collapsing', collapse: true, icon: 'pause' } :
    { message: 'Collapse brackets', collapse: false, icon: 'play' };
  activeTabs[tabId] = state.collapse;
  chrome.browserAction.setIcon({ tabId, path: `icons/${state.icon}.png` });
  chrome.browserAction.setTitle({ tabId, title: state.message });
  chrome.tabs.sendMessage(tabId, { collapse: state.collapse });
}

function setUpContextMenus() {
  chrome.contextMenus.create({
    id: 'bracketless',
    title: 'Bracketless action',
  });
}
// activate play pause update?
// chrome.contextMenus.update();

function listenerAction(tabId) {
  if (!loadedTabs[tabId]) {
    load(tabId);
  } else if (!activeTabs[tabId]) {
    doAction(tabId, 'play');
  } else if (activeTabs[tabId]) {
    doAction(tabId, 'pause');
  }
}

function optionalPermsCheck() {
  return new Promise((resolve, reject) => chrome.permissions.contains({
    permissions: ['tabs'],
    origins: ['http://*/', 'https://*/'],
  }, (permission) => {
    if (permission) resolve(permission);
    else reject(new Error(`Tabs at any origins was not granted. Permission: ${permission}`));
  }));
}

function autoAction() {
  chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
    if (changeInfo.status === 'complete' && tab.active && !/(chrome)(?:[/:-])/.test(tab.url)) {
      chrome.storage.sync.get(null, (options) => {
        if (options.autoLoad && options.autoPlay === false) load(tabId);
        if (options.autoLoad && options.autoPlay) {
          load(tabId)
            .then(() => doAction(tabId, 'play'));
        }
      });
    }
  });
}

chrome.runtime.onInstalled.addListener(() => {
  syncDefaultOptions();
  setUpContextMenus();
  chrome.browserAction.onClicked.addListener(tab => listenerAction(tab.id));
  chrome.contextMenus.onClicked.addListener((_, tab) => listenerAction(tab.id));
  optionalPermsCheck()
    .then(autoAction);
});
<|endoftext|>


<commit_before>/* eslint global-require:0 */

module.exports = {
  map: { inline: false },
  plugins: [
    require('postcss-custom-properties')({
      preserve: false
    }),
  ]
};
<commit_msg>Forgot to commit something<commit_after>/* eslint global-require:0 */

module.exports = {
  map: { inline: false }
};
<|endoftext|>


<commit_before>const themeOptions = require('gatsby-theme-apollo-docs/theme-options');

module.exports = {
  pathPrefix: '/docs/react',
  plugins: [
    {
      resolve: 'gatsby-theme-apollo-docs',
      options: {
        ...themeOptions,
        root: __dirname,
        subtitle: 'Client (React)',
        description: 'A guide to using the Apollo GraphQL Client with React',
        githubRepo: 'apollographql/apollo-client',
        localVersion: '3.0 beta',
        defaultVersion: '2.6',
        versions: {
          '2.6': 'version-2.6-relative',
          '2.5': 'version-2.5',
          '2.4': 'version-2.4',
        },
        checkLinksOptions: {
          exceptions: [
            '/api/apollo-client/',
            '/v3.0-beta/api/core/',
            '/v2.5/api/apollo-client/',
            '/v2.4/api/apollo-client/',
          ],
        },
        typescriptApiBox: {
          data: require('./docs.json'),
          filepathPrefix: 'src/',
        },
        sidebarCategories: {
          null: ['index', 'why-apollo', 'get-started'],
          'Fetching data': [
            'data/queries',
            'data/mutations',
            'data/local-state',
            'data/subscriptions',
            'data/pagination',
            'data/fragments',
            'data/error-handling',
          ],
          Caching: ['caching/cache-configuration', 'caching/cache-interaction'],
          'Development & Testing': [
            'development-testing/static-typing',
            'development-testing/testing',
            'development-testing/client-schema-mocking',
            'development-testing/developer-tooling',
          ],
          Performance: [
            'performance/performance',
            'performance/optimistic-ui',
            'performance/server-side-rendering',
            'performance/babel',
          ],
          Integrations: [
            'integrations/integrations',
            'integrations/react-native',
            'integrations/meteor',
            'integrations/webpack',
          ],
          Networking: [
            'networking/basic-http-networking',
            'networking/advanced-http-networking',
            'networking/authentication',
          ],
          'API - Core': [
            'api/core/ApolloClient',
            'api/core/ObservableQuery'
          ],
          'API - React': [
            'api/react/hooks',
            'api/react/testing',
            'api/react/ssr',
            'api/react/components',
            'api/react/hoc'
          ],
          'API - Link': [
            'api/link/introduction',
            'api/link/apollo-link-batch-http',
            'api/link/apollo-link-context',
            'api/link/apollo-link-error',
            'api/link/apollo-link-rest',
            'api/link/apollo-link-retry',
            'api/link/apollo-link-schema',
            'api/link/apollo-link-ws'
          ],
          Migrating: ['migrating/hooks-migration', 'migrating/boost-migration'],
        },
      },
    },
  ],
};
<commit_msg>Remove the now-unused filepathPrefix option from the docs tsapibox config (#5822)<commit_after>const themeOptions = require('gatsby-theme-apollo-docs/theme-options');

module.exports = {
  pathPrefix: '/docs/react',
  plugins: [
    {
      resolve: 'gatsby-theme-apollo-docs',
      options: {
        ...themeOptions,
        root: __dirname,
        subtitle: 'Client (React)',
        description: 'A guide to using the Apollo GraphQL Client with React',
        githubRepo: 'apollographql/apollo-client',
        localVersion: '3.0 beta',
        defaultVersion: '2.6',
        versions: {
          '2.6': 'version-2.6-relative',
          '2.5': 'version-2.5',
          '2.4': 'version-2.4',
        },
        checkLinksOptions: {
          exceptions: [
            '/api/apollo-client/',
            '/v3.0-beta/api/core/',
            '/v2.5/api/apollo-client/',
            '/v2.4/api/apollo-client/',
          ],
        },
        typescriptApiBox: {
          data: require('./docs.json')
        },
        sidebarCategories: {
          null: ['index', 'why-apollo', 'get-started'],
          'Fetching data': [
            'data/queries',
            'data/mutations',
            'data/local-state',
            'data/subscriptions',
            'data/pagination',
            'data/fragments',
            'data/error-handling',
          ],
          Caching: ['caching/cache-configuration', 'caching/cache-interaction'],
          'Development & Testing': [
            'development-testing/static-typing',
            'development-testing/testing',
            'development-testing/client-schema-mocking',
            'development-testing/developer-tooling',
          ],
          Performance: [
            'performance/performance',
            'performance/optimistic-ui',
            'performance/server-side-rendering',
            'performance/babel',
          ],
          Integrations: [
            'integrations/integrations',
            'integrations/react-native',
            'integrations/meteor',
            'integrations/webpack',
          ],
          Networking: [
            'networking/basic-http-networking',
            'networking/advanced-http-networking',
            'networking/authentication',
          ],
          'API - Core': [
            'api/core/ApolloClient',
            'api/core/ObservableQuery'
          ],
          'API - React': [
            'api/react/hooks',
            'api/react/testing',
            'api/react/ssr',
            'api/react/components',
            'api/react/hoc'
          ],
          'API - Link': [
            'api/link/introduction',
            'api/link/apollo-link-batch-http',
            'api/link/apollo-link-context',
            'api/link/apollo-link-error',
            'api/link/apollo-link-rest',
            'api/link/apollo-link-retry',
            'api/link/apollo-link-schema',
            'api/link/apollo-link-ws'
          ],
          Migrating: ['migrating/hooks-migration', 'migrating/boost-migration'],
        },
      },
    },
  ],
};
<|endoftext|>


<commit_before>module.exports = {
  __experimentalThemes: [
    {
      resolve: 'gatsby-theme-apollo-docs',
      options: {
        root: __dirname,
        subtitle: 'Apollo Server',
        description: 'A guide to using Apollo Server',
        contentDir: 'docs/source',
        basePath: '/docs/apollo-server',
        githubRepo: 'apollographql/apollo-server',
        versions: [
          {
            value: '1',
            ref: 'version-1',
          },
          {
            value: '2',
            ref: 'HEAD',
            default: true,
          }
        ],
        sidebarCategories: {
          null: ['index', 'getting-started', 'whats-new'],
          Essentials: [
            'essentials/schema',
            'essentials/server',
            'essentials/data',
          ],
          Features: [
            'features/mocking',
            'features/errors',
            'features/data-sources',
            'features/subscriptions',
            'features/metrics',
            'features/graphql-playground',
            'features/scalars-enums',
            'features/unions-interfaces',
            'features/directives',
            'features/creating-directives',
            'features/authentication',
            'features/testing',
          ],
          // 'Schema stitching': [
          //   'features/schema-stitching',
          //   'features/remote-schemas',
          //   'features/schema-delegation',
          //   'features/schema-transforms',
          // ],
          Deployment: [
            // 'deployment/index',
            'deployment/heroku',
            'deployment/lambda',
            'deployment/now',
            // 'deployment/netlify',
          ],
          'API Reference': ['api/apollo-server', 'api/graphql-tools'],
          Migration: [
            'migration-two-dot',
            'migration-engine',
            'migration-file-uploads',
          ],
        },
      },
    },
  ],
};
<commit_msg>Address lint error<commit_after>module.exports = {
  __experimentalThemes: [
    {
      resolve: 'gatsby-theme-apollo-docs',
      options: {
        root: __dirname,
        subtitle: 'Apollo Server',
        description: 'A guide to using Apollo Server',
        contentDir: 'docs/source',
        basePath: '/docs/apollo-server',
        githubRepo: 'apollographql/apollo-server',
        versions: [
          {
            value: '1',
            ref: 'version-1',
          },
          {
            value: '2',
            ref: 'HEAD',
            default: true,
          },
        ],
        sidebarCategories: {
          null: ['index', 'getting-started', 'whats-new'],
          Essentials: [
            'essentials/schema',
            'essentials/server',
            'essentials/data',
          ],
          Features: [
            'features/mocking',
            'features/errors',
            'features/data-sources',
            'features/subscriptions',
            'features/metrics',
            'features/graphql-playground',
            'features/scalars-enums',
            'features/unions-interfaces',
            'features/directives',
            'features/creating-directives',
            'features/authentication',
            'features/testing',
          ],
          // 'Schema stitching': [
          //   'features/schema-stitching',
          //   'features/remote-schemas',
          //   'features/schema-delegation',
          //   'features/schema-transforms',
          // ],
          Deployment: [
            // 'deployment/index',
            'deployment/heroku',
            'deployment/lambda',
            'deployment/now',
            // 'deployment/netlify',
          ],
          'API Reference': ['api/apollo-server', 'api/graphql-tools'],
          Migration: [
            'migration-two-dot',
            'migration-engine',
            'migration-file-uploads',
          ],
        },
      },
    },
  ],
};
<|endoftext|>


<commit_before>;(function($$){ 'use strict';

  // Functions for layouts on nodes
  ////////////////////////////////////////////////////////////////////////////////////////////////////

  $$.fn.eles({

    // using standard layout options, apply position function (w/ or w/o animation)
    layoutPositions: function( layout, options, fn ){
      var nodes = this.nodes();
      var cy = this.cy();

      layout.trigger({ type: 'layoutstart', layout: layout });

      if( options.animate ){
        for( var i = 0; i < nodes.length; i++ ){
          var node = nodes[i];
          var lastNode = i === nodes.length - 1;

          var newPos = fn.call( node, i, node );
          var pos = node.position();

          if( !$$.is.number(pos.x) || !$$.is.number(pos.y) ){
            node.silentPosition({ x: 0, y: 0 });
          }

          layout.one('layoutready', options.ready);
          layout.trigger({ type: 'layoutready', layout: layout });

          node.animate({
            position: newPos
          }, {
            duration: options.animationDuration,
            step: lastNode ? undefined : function(){
              if( options.fit ){
                cy.fit( options.padding );
              }
            },
            complete: lastNode ? undefined : function(){
              if( options.zoom != null ){
                cy.zoom( options.zoom );
              }

              if( options.pan ){
                cy.pan( options.pan );
              }

              if( options.fit ){
                cy.fit( options.padding );
              }

              layout.one('layoutstop', options.stop);
              layout.trigger({ type: 'layoutstop', layout: layout });
            }
          });
        }
      } else {
        nodes.positions( fn );

        if( options.fit ){
          cy.fit( options.padding );
        }

        if( options.zoom != null ){
          cy.zoom( options.zoom );
        }

        if( options.pan ){
          cy.pan( options.pan );
        }

        layout.one('layoutready', options.ready);
        layout.trigger({ type: 'layoutready', layout: layout });

        layout.one('layoutstop', options.stop);
        layout.trigger({ type: 'layoutstop', layout: layout });
      }

      return this; // chaining
    },

    layout: function( options ){
      var cy = this.cy();

      cy.layout( $$.util.extend({}, options, {
        eles: this
      }) );

      return this;
    },

    makeLayout: function( options ){
      var cy = this.cy();

      return cy.makeLayout( $$.util.extend({}, options, {
        eles: this
      }) );
    }

  });

})( cytoscape );
<commit_msg> Animated discrete layouts trigger layoutstop multiple times #693<commit_after>;(function($$){ 'use strict';

  // Functions for layouts on nodes
  ////////////////////////////////////////////////////////////////////////////////////////////////////

  $$.fn.eles({

    // using standard layout options, apply position function (w/ or w/o animation)
    layoutPositions: function( layout, options, fn ){
      var nodes = this.nodes();
      var cy = this.cy();

      layout.trigger({ type: 'layoutstart', layout: layout });

      if( options.animate ){
        for( var i = 0; i < nodes.length; i++ ){
          var node = nodes[i];
          var lastNode = i === nodes.length - 1;

          var newPos = fn.call( node, i, node );
          var pos = node.position();

          if( !$$.is.number(pos.x) || !$$.is.number(pos.y) ){
            node.silentPosition({ x: 0, y: 0 });
          }

          node.animate({
            position: newPos
          }, {
            duration: options.animationDuration,
            step: !lastNode ? undefined : function(){
              if( options.fit ){
                cy.fit( options.padding );
              }
            },
            complete: !lastNode ? undefined : function(){
              if( options.zoom != null ){
                cy.zoom( options.zoom );
              }

              if( options.pan ){
                cy.pan( options.pan );
              }

              if( options.fit ){
                cy.fit( options.padding );
              }

              layout.one('layoutstop', options.stop);
              layout.trigger({ type: 'layoutstop', layout: layout });
            }
          });
        }

        layout.one('layoutready', options.ready);
        layout.trigger({ type: 'layoutready', layout: layout });
      } else {
        nodes.positions( fn );

        if( options.fit ){
          cy.fit( options.padding );
        }

        if( options.zoom != null ){
          cy.zoom( options.zoom );
        }

        if( options.pan ){
          cy.pan( options.pan );
        }

        layout.one('layoutready', options.ready);
        layout.trigger({ type: 'layoutready', layout: layout });

        layout.one('layoutstop', options.stop);
        layout.trigger({ type: 'layoutstop', layout: layout });
      }

      return this; // chaining
    },

    layout: function( options ){
      var cy = this.cy();

      cy.layout( $$.util.extend({}, options, {
        eles: this
      }) );

      return this;
    },

    makeLayout: function( options ){
      var cy = this.cy();

      return cy.makeLayout( $$.util.extend({}, options, {
        eles: this
      }) );
    }

  });

})( cytoscape );
<|endoftext|>