<commit_before>import Realm from 'realm';

import { getTotal, createRecord } from '../utilities';

export class StocktakeItem extends Realm.Object {
  destructor(database) {
    if (this.stocktake && this.stocktake.isFinalised) {
      throw new Error('Cannot delete a StocktakeItem belonging to a finalised stocktake');
    }
    database.delete('StocktakeBatch', this.batches);
  }

  get snapshotTotalQuantity() {
    return getTotal(this.batches, 'snapshotTotalQuantity');
  }

  get countedTotalQuantity() {
    return getTotal(this.batches, 'countedTotalQuantity');
  }

  get difference() {
    return getTotal(this.batches, 'difference');
  }

  get itemId() {
    return this.item ? this.item.id : '';
  }

  get itemName() {
    return this.item ? this.item.name : '';
  }

  get itemCode() {
    return this.item ? this.item.code : '';
  }

  /**
   * Check to see if any StocktakeBatches have been adjusted for this StocktakeItem
   * Will return true if any StocktakeBatches were changed, even if net quantity change
   * for StocktakeItem is 0.
   * @return  {boolean} True if StocktakeBatches have adjustments
   */
  get hasCountedBatches() {
    return this.batches.some(stocktakeBatch => stocktakeBatch.hasBeenCounted);
  }

  get numberOfBatches() {
    return this.batches.length;
  }

  /**
   * If the stock has increased or not been changed since the item quantity was
   * snapshot, the minimum is 0. If the stock has reduced since the item was
   * snapshot, the minimum total quantity that can be sensibly considered as the
   * counted quantity is the snapshot quantity minus the current stock on hand.
   * This is because it doesn't make sense to count an amount that represents a
   * reduction greater than the current stock on hand, which would create negative
   * inventory.
   * @return {integer} The minimum total quantity that can be sensibly counted
   */
  get minimumTotalQuantity() {
    return this.item ? Math.max(0, this.snapshotTotalQuantity - this.item.totalQuantity) : 0;
  }

  /**
   * Returns true if this stocktake item's counted quantity would reduce the amount
   * of stock in inventory to negative levels, if it were finalised. This can happen
   * if, for example, an item is added to a stocktake with a snapshot quantity of
   * 10, then is counted to have a quantity of 8, but concurrently there has been
   * a reduction in the stock in inventory, e.g. a customer invoice for 9. In this
   * example, the stock on hand is now 1, so a reduction of 2 caused by this stocktake
   * item would result in negative inventory.
   * @return {Boolean} Whether the counted quantity is below the minimum for this item
   */
  get isReducedBelowMinimum() {
    return this.batches.some(batch => batch.isReducedBelowMinimum);
  }

  /**
   * Function is used when adjusting inventory for the StocktakeItem
   * rather then individual StocktakeBatches. Logic:
   * Increasing -> From last expiry to first expiry StocktakeBatch, increase
   * to the maximum of snapshot quantity, after that if still have quantity
   * to increase, apply it to first expiry batch.
   * Reducing -> From first expiry to last expiry StocktakeBatch, decrease
   * quantity by maximum of current counted quantity
   * @param  {Realm}  database   App wide local database
   * @param  {number} quantity   Change in StocktakeItem counted quantity
   */
  setCountedTotalQuantity(database, quantity) {
    database.write(() => {
      // If there are no batches, create one
      if (this.batches.length === 0) {
        this.createNewBatch(database);
      }

      let difference = quantity - this.countedTotalQuantity;
      // In case number is entered in Actual Quantity field, but it's the same as
      // snapshot quantity, we want to remove 'Not Counted' placeholder
      if (quantity === this.snapshotTotalQuantity) {
        this.batches.forEach(stocktakeBatch => {
          stocktakeBatch.countedTotalQuantity = stocktakeBatch.snapshotTotalQuantity;
          database.save('StocktakeBatch', stocktakeBatch);
        });
        return;
      }

      const isIncreasingQuantity = difference > 0;
      const sortedBatches = this.batches.sorted('expiryDate', isIncreasingQuantity);

      sortedBatches.some(stocktakeBatch => {
        const batchTotalQuantity = stocktakeBatch.countedTotalQuantity;

        let thisBatchChangeQuantity = 0;
        if (isIncreasingQuantity) {
          thisBatchChangeQuantity = Math.min(stocktakeBatch.snapshotTotalQuantity -
                                            batchTotalQuantity, difference);

          if (thisBatchChangeQuantity <= 0) return false;
        } else {
          thisBatchChangeQuantity = Math.min(batchTotalQuantity, -difference);
          thisBatchChangeQuantity = -thisBatchChangeQuantity;
        }

        stocktakeBatch.countedTotalQuantity = batchTotalQuantity + thisBatchChangeQuantity;

        database.save('StocktakeBatch', stocktakeBatch);

        difference -= thisBatchChangeQuantity;
        return difference === 0;
      });
      // If increasing and we still have difference to add to a batch, add it to the
      // earliest expiry batch
      if (difference > 0) {
        const earliestExpiryBatch = sortedBatches[sortedBatches.length - 1];

        earliestExpiryBatch.countedTotalQuantity += difference;
        database.save('StocktakeBatch', earliestExpiryBatch);
      }
    });
  }

  addBatch(stocktakeBatch) {
    this.batches.push(stocktakeBatch);
  }

  createNewBatch(database) {
    const batchString = `stocktake_${this.stocktake.serialNumber}`;
    const itemBatch = createRecord(database, 'ItemBatch', this.item, batchString);
    createRecord(database, 'StocktakeBatch', this, itemBatch, true);
  }
}

StocktakeItem.schema = {
  name: 'StocktakeItem',
  primaryKey: 'id',
  properties: {
    id: 'string',
    item: 'Item',
    stocktake: 'Stocktake',
    batches: { type: 'list', objectType: 'StocktakeBatch' },
  },
};
<commit_msg>removed redundant getter<commit_after>import Realm from 'realm';

import { getTotal, createRecord } from '../utilities';

export class StocktakeItem extends Realm.Object {
  destructor(database) {
    if (this.stocktake && this.stocktake.isFinalised) {
      throw new Error('Cannot delete a StocktakeItem belonging to a finalised stocktake');
    }
    database.delete('StocktakeBatch', this.batches);
  }

  get snapshotTotalQuantity() {
    return getTotal(this.batches, 'snapshotTotalQuantity');
  }

  get countedTotalQuantity() {
    return getTotal(this.batches, 'countedTotalQuantity');
  }

  get difference() {
    return getTotal(this.batches, 'difference');
  }

  get itemId() {
    return this.item ? this.item.id : '';
  }

  get itemName() {
    return this.item ? this.item.name : '';
  }

  get itemCode() {
    return this.item ? this.item.code : '';
  }

  /**
   * Check to see if any StocktakeBatches have been adjusted for this StocktakeItem
   * Will return true if any StocktakeBatches were changed, even if net quantity change
   * for StocktakeItem is 0.
   * @return  {boolean} True if StocktakeBatches have adjustments
   */
  get hasCountedBatches() {
    return this.batches.some(stocktakeBatch => stocktakeBatch.hasBeenCounted);
  }

  get numberOfBatches() {
    return this.batches.length;
  }

  /**
   * Returns true if this stocktake item's counted quantity would reduce the amount
   * of stock in inventory to negative levels, if it were finalised. This can happen
   * if, for example, an item is added to a stocktake with a snapshot quantity of
   * 10, then is counted to have a quantity of 8, but concurrently there has been
   * a reduction in the stock in inventory, e.g. a customer invoice for 9. In this
   * example, the stock on hand is now 1, so a reduction of 2 caused by this stocktake
   * item would result in negative inventory.
   * @return {Boolean} Whether the counted quantity is below the minimum for this item
   */
  get isReducedBelowMinimum() {
    return this.batches.some(batch => batch.isReducedBelowMinimum);
  }

  /**
   * Function is used when adjusting inventory for the StocktakeItem
   * rather then individual StocktakeBatches. Logic:
   * Increasing -> From last expiry to first expiry StocktakeBatch, increase
   * to the maximum of snapshot quantity, after that if still have quantity
   * to increase, apply it to first expiry batch.
   * Reducing -> From first expiry to last expiry StocktakeBatch, decrease
   * quantity by maximum of current counted quantity
   * @param  {Realm}  database   App wide local database
   * @param  {number} quantity   Change in StocktakeItem counted quantity
   */
  setCountedTotalQuantity(database, quantity) {
    database.write(() => {
      // If there are no batches, create one
      if (this.batches.length === 0) {
        this.createNewBatch(database);
      }

      let difference = quantity - this.countedTotalQuantity;
      // In case number is entered in Actual Quantity field, but it's the same as
      // snapshot quantity, we want to remove 'Not Counted' placeholder
      if (quantity === this.snapshotTotalQuantity) {
        this.batches.forEach(stocktakeBatch => {
          stocktakeBatch.countedTotalQuantity = stocktakeBatch.snapshotTotalQuantity;
          database.save('StocktakeBatch', stocktakeBatch);
        });
        return;
      }

      const isIncreasingQuantity = difference > 0;
      const sortedBatches = this.batches.sorted('expiryDate', isIncreasingQuantity);

      sortedBatches.some(stocktakeBatch => {
        const batchTotalQuantity = stocktakeBatch.countedTotalQuantity;

        let thisBatchChangeQuantity = 0;
        if (isIncreasingQuantity) {
          thisBatchChangeQuantity = Math.min(stocktakeBatch.snapshotTotalQuantity -
                                            batchTotalQuantity, difference);

          if (thisBatchChangeQuantity <= 0) return false;
        } else {
          thisBatchChangeQuantity = Math.min(batchTotalQuantity, -difference);
          thisBatchChangeQuantity = -thisBatchChangeQuantity;
        }

        stocktakeBatch.countedTotalQuantity = batchTotalQuantity + thisBatchChangeQuantity;

        database.save('StocktakeBatch', stocktakeBatch);

        difference -= thisBatchChangeQuantity;
        return difference === 0;
      });
      // If increasing and we still have difference to add to a batch, add it to the
      // earliest expiry batch
      if (difference > 0) {
        const earliestExpiryBatch = sortedBatches[sortedBatches.length - 1];

        earliestExpiryBatch.countedTotalQuantity += difference;
        database.save('StocktakeBatch', earliestExpiryBatch);
      }
    });
  }

  addBatch(stocktakeBatch) {
    this.batches.push(stocktakeBatch);
  }

  createNewBatch(database) {
    const batchString = `stocktake_${this.stocktake.serialNumber}`;
    const itemBatch = createRecord(database, 'ItemBatch', this.item, batchString);
    createRecord(database, 'StocktakeBatch', this, itemBatch, true);
  }
}

StocktakeItem.schema = {
  name: 'StocktakeItem',
  primaryKey: 'id',
  properties: {
    id: 'string',
    item: 'Item',
    stocktake: 'Stocktake',
    batches: { type: 'list', objectType: 'StocktakeBatch' },
  },
};
<|endoftext|>


<commit_before>/**
 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
 *
 * ownCloud - Mail
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License, version 3,
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

define(function(require) {
	'use strict';

	var $ = require('jquery');
	var Backbone = require('backbone');
	var Handlebars = require('handlebars');
	var Radio = require('radio');
	var MessagesItemView = require('views/messagesitem');
	var MessageListTemplate = require('text!templates/message-list.html');
	var NoSearchResultMessageListView = require('views/nosearchresultmessagelistview');

	return Backbone.Marionette.CompositeView.extend({
		collection: null,
		$scrollContainer: undefined,
		childView: MessagesItemView,
		childViewContainer: '#mail-message-list',
		template: Handlebars.compile(MessageListTemplate),
		currentMessageId: null,
		loadingMore: false,
		reloaded: false,
		filterCriteria: null,
		initialize: function() {
			var _this = this;
			Radio.ui.reply('messagesview:collection', function() {
				return _this.collection;
			});
			this.listenTo(Radio.ui, 'messagesview:messages:update', this.refresh);
			this.listenTo(Radio.ui, 'messagesview:messages:add', this.addMessages);
			this.listenTo(Radio.ui, 'messagesview:messageflag:set', this.setMessageFlag);
			this.listenTo(Radio.ui, 'messagesview:filter', this.filterCurrentMailbox);
			this.listenTo(Radio.ui, 'messagesview:filter:clear', this.clearFilter);
			this.listenTo(Radio.ui, 'messagesview:message:setactive', this.setActiveMessage);
			this.listenTo(Radio.message, 'messagesview:message:next', this.selectNextMessage);
			this.listenTo(Radio.message, 'messagesview:message:prev', this.selectPreviousMessage);
		},
		onShow: function() {
			this.$scrollContainer = this.$el.parent();
			this.$scrollContainer.scroll(_.bind(this.onScroll, this));
		},
		getEmptyView: function() {
			if (this.filterCriteria) {
				return NoSearchResultMessageListView;
			}
		},
		emptyViewOptions: function() {
			return {filterCriteria: this.filterCriteria};
		},
		setMessageFlag: function(messageId, flag, val) {
			var message = this.collection.get(messageId);
			if (message) {
				// TODO: globals are bad :-/
				var account = require('state').currentAccount;
				var folder = require('state').currentFolder;

				Radio.message.trigger('flag', account, folder, message, flag, val);
			}
		},
		setActiveMessage: function(messageId) {
			// Set active class for current message and remove it from old one

			var message = null;
			if (this.currentMessageId !== null) {
				message = this.collection.get(this.currentMessageId);
				if (message) {
					message.set('active', false);
				}
			}

			this.currentMessageId = messageId;

			if (messageId !== null) {
				message = this.collection.get(this.currentMessageId);
				if (message) {
					message.set('active', true);
				}
			}

			require('state').currentMessageId = messageId;
			Radio.ui.trigger('title:update');

		},
		selectNextMessage: function() {
			if (this.currentMessageId === null) {
				return;
			}

			var message = this.collection.get(this.currentMessageId);
			if (message === null) {
				return;
			}

			if (this.collection.indexOf(message) === (this.collection.length - 1)) {
				// Last message, nothing to do
				return;
			}

			var nextMessage = this.collection.at(this.collection.indexOf(message) + 1);
			if (nextMessage) {
				var account = require('state').currentAccount;
				var folder = require('state').currentFolder;
				Radio.message.trigger('load', account, folder, nextMessage, {
					force: true
				});
			}
		},
		selectPreviousMessage: function() {
			if (this.currentMessageId === null) {
				return;
			}

			var message = this.collection.get(this.currentMessageId);
			if (message === null) {
				return;
			}

			if (this.collection.indexOf(message) === 0) {
				// First message, nothing to do
				return;
			}

			var previousMessage = this.collection.at(this.collection.indexOf(message) - 1);
			if (previousMessage) {
				var account = require('state').currentAccount;
				var folder = require('state').currentFolder;
				Radio.message.trigger('load', account, folder, previousMessage, {
					force: true
				});
			}
		},
		refresh: function() {
			if (!require('state').currentAccount) {
				return;
			}
			if (!require('state').currentFolder) {
				return;
			}
			this.loadMessages(true);
		},
		loadMore: function() {
			this.loadMessages(false);
		},
		onScroll: function() {
			if (this.reloaded) {
				this.reloaded = false;
				return;
			}
			if (this.loadingMore === true) {
				// Ignore events until loading has finished
				return;
			}
			if (this.$scrollContainer.scrollTop() === 0) {
				// Scrolled to top -> refresh
				this.loadMessages(true);
				return;
			}
			if ((this.$scrollContainer.scrollTop() + this.$scrollContainer.height()) > (this.$el.height() - 150)) {
				// Scrolled all the way down -> load more
				this.loadingMore = true;
				this.loadMessages(false);
				return;
			}
		},
		filterCurrentMailbox: function(query) {
			this.filterCriteria = {
				text: query
			};
			this.loadMessages(true);
		},
		clearFilter: function() {
			$('#searchbox').val('');
			this.filterCriteria = null;
		},
		loadMessages: function(reload) {
			reload = reload || false;
			var from = this.collection.size();
			if (reload) {
				from = 0;
			}
			// Add loading feedback
			$('#load-more-mail-messages').addClass('icon-loading-small');
			$('#mail-message-list-loading').css('opacity', 0)
				.slideDown('slow')
				.animate(
					{ opacity: 1 },
					{ queue: false, duration: 'slow' }
				);

			var _this = this;
			var loadingMessages = Radio.message.request('entities',
				require('state').currentAccount,
				require('state').currentFolder,
				{
					from: from,
					to: from + 20,
					filter: this.filterCriteria ? this.filterCriteria.text : null,
					force: true,
					// Replace cached message list on reload
					replace: reload
				});

			$.when(loadingMessages).done(function() {
				Radio.ui.trigger('messagesview:message:setactive', require('state').currentMessageId);
			});

			$.when(loadingMessages).fail(function() {
				Radio.ui.trigger('error:show', t('mail', 'Error while loading messages.'));
				// Set the old folder as being active
				var account = require('state').currentAccount;
				var folder = require('state').currentFolder;
				Radio.folder.trigger('setactive', account, folder);
			});

			$.when(loadingMessages).always(function() {
				// Remove loading feedback again
				$('#mail-message-list-loading').css('opacity', 1)
					.slideUp('slow')
					.animate(
						{ opacity: 0 },
						{ queue: false, duration: 'slow' }
					);
				$('#load-more-mail-messages').removeClass('icon-loading-small');
				_this.loadingMore = false;
				// Reload scrolls the list to the top, hence a unwanted
				// scroll event is fired, which we want to ignore
				_this.reloaded = reload;
			});
		},
		addMessages: function(message) {
			var _this = this;
			// TODO: merge?
			message.each(function(msg) {
				_this.collection.add(msg);
			});
		}
	});
});
<commit_msg>show only one spinner when loading the next page<commit_after>/**
 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
 *
 * ownCloud - Mail
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License, version 3,
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

define(function(require) {
	'use strict';

	var $ = require('jquery');
	var Backbone = require('backbone');
	var Handlebars = require('handlebars');
	var Radio = require('radio');
	var MessagesItemView = require('views/messagesitem');
	var MessageListTemplate = require('text!templates/message-list.html');
	var NoSearchResultMessageListView = require('views/nosearchresultmessagelistview');

	return Backbone.Marionette.CompositeView.extend({
		collection: null,
		$scrollContainer: undefined,
		childView: MessagesItemView,
		childViewContainer: '#mail-message-list',
		template: Handlebars.compile(MessageListTemplate),
		currentMessageId: null,
		loadingMore: false,
		reloaded: false,
		filterCriteria: null,
		initialize: function() {
			var _this = this;
			Radio.ui.reply('messagesview:collection', function() {
				return _this.collection;
			});
			this.listenTo(Radio.ui, 'messagesview:messages:update', this.refresh);
			this.listenTo(Radio.ui, 'messagesview:messages:add', this.addMessages);
			this.listenTo(Radio.ui, 'messagesview:messageflag:set', this.setMessageFlag);
			this.listenTo(Radio.ui, 'messagesview:filter', this.filterCurrentMailbox);
			this.listenTo(Radio.ui, 'messagesview:filter:clear', this.clearFilter);
			this.listenTo(Radio.ui, 'messagesview:message:setactive', this.setActiveMessage);
			this.listenTo(Radio.message, 'messagesview:message:next', this.selectNextMessage);
			this.listenTo(Radio.message, 'messagesview:message:prev', this.selectPreviousMessage);
		},
		onShow: function() {
			this.$scrollContainer = this.$el.parent();
			this.$scrollContainer.scroll(_.bind(this.onScroll, this));
		},
		getEmptyView: function() {
			if (this.filterCriteria) {
				return NoSearchResultMessageListView;
			}
		},
		emptyViewOptions: function() {
			return {filterCriteria: this.filterCriteria};
		},
		setMessageFlag: function(messageId, flag, val) {
			var message = this.collection.get(messageId);
			if (message) {
				// TODO: globals are bad :-/
				var account = require('state').currentAccount;
				var folder = require('state').currentFolder;

				Radio.message.trigger('flag', account, folder, message, flag, val);
			}
		},
		setActiveMessage: function(messageId) {
			// Set active class for current message and remove it from old one

			var message = null;
			if (this.currentMessageId !== null) {
				message = this.collection.get(this.currentMessageId);
				if (message) {
					message.set('active', false);
				}
			}

			this.currentMessageId = messageId;

			if (messageId !== null) {
				message = this.collection.get(this.currentMessageId);
				if (message) {
					message.set('active', true);
				}
			}

			require('state').currentMessageId = messageId;
			Radio.ui.trigger('title:update');

		},
		selectNextMessage: function() {
			if (this.currentMessageId === null) {
				return;
			}

			var message = this.collection.get(this.currentMessageId);
			if (message === null) {
				return;
			}

			if (this.collection.indexOf(message) === (this.collection.length - 1)) {
				// Last message, nothing to do
				return;
			}

			var nextMessage = this.collection.at(this.collection.indexOf(message) + 1);
			if (nextMessage) {
				var account = require('state').currentAccount;
				var folder = require('state').currentFolder;
				Radio.message.trigger('load', account, folder, nextMessage, {
					force: true
				});
			}
		},
		selectPreviousMessage: function() {
			if (this.currentMessageId === null) {
				return;
			}

			var message = this.collection.get(this.currentMessageId);
			if (message === null) {
				return;
			}

			if (this.collection.indexOf(message) === 0) {
				// First message, nothing to do
				return;
			}

			var previousMessage = this.collection.at(this.collection.indexOf(message) - 1);
			if (previousMessage) {
				var account = require('state').currentAccount;
				var folder = require('state').currentFolder;
				Radio.message.trigger('load', account, folder, previousMessage, {
					force: true
				});
			}
		},
		refresh: function() {
			if (!require('state').currentAccount) {
				return;
			}
			if (!require('state').currentFolder) {
				return;
			}
			this.loadMessages(true);
		},
		loadMore: function() {
			this.loadMessages(false);
		},
		onScroll: function() {
			if (this.reloaded) {
				this.reloaded = false;
				return;
			}
			if (this.loadingMore === true) {
				// Ignore events until loading has finished
				return;
			}
			if (this.$scrollContainer.scrollTop() === 0) {
				// Scrolled to top -> refresh
				this.loadMessages(true);
				return;
			}
			if ((this.$scrollContainer.scrollTop() + this.$scrollContainer.height()) > (this.$el.height() - 150)) {
				// Scrolled all the way down -> load more
				this.loadingMore = true;
				this.loadMessages(false);
				return;
			}
		},
		filterCurrentMailbox: function(query) {
			this.filterCriteria = {
				text: query
			};
			this.loadMessages(true);
		},
		clearFilter: function() {
			$('#searchbox').val('');
			this.filterCriteria = null;
		},
		loadMessages: function(reload) {
			reload = reload || false;
			var from = this.collection.size();
			if (reload) {
				from = 0;
			}
			// Add loading feedback
			$('#load-more-mail-messages').addClass('icon-loading-small');
			if (reload) {
				$('#mail-message-list-loading').css('opacity', 0)
					.slideDown('slow')
					.animate(
						{ opacity: 1 },
						{ queue: false, duration: 'slow' }
					);
			}

			var _this = this;
			var loadingMessages = Radio.message.request('entities',
				require('state').currentAccount,
				require('state').currentFolder,
				{
					from: from,
					to: from + 20,
					filter: this.filterCriteria ? this.filterCriteria.text : null,
					force: true,
					// Replace cached message list on reload
					replace: reload
				});

			$.when(loadingMessages).done(function() {
				Radio.ui.trigger('messagesview:message:setactive', require('state').currentMessageId);
			});

			$.when(loadingMessages).fail(function() {
				Radio.ui.trigger('error:show', t('mail', 'Error while loading messages.'));
				// Set the old folder as being active
				var account = require('state').currentAccount;
				var folder = require('state').currentFolder;
				Radio.folder.trigger('setactive', account, folder);
			});

			$.when(loadingMessages).always(function() {
				// Remove loading feedback again
				$('#load-more-mail-messages').removeClass('icon-loading-small');
				if (reload) {
					$('#mail-message-list-loading').css('opacity', 1)
						.slideUp('slow')
						.animate(
							{ opacity: 0 },
							{ queue: false, duration: 'slow' }
						);
				}
				_this.loadingMore = false;
				// Reload scrolls the list to the top, hence a unwanted
				// scroll event is fired, which we want to ignore
				_this.reloaded = reload;
			});
		},
		addMessages: function(message) {
			var _this = this;
			// TODO: merge?
			message.each(function(msg) {
				_this.collection.add(msg);
			});
		}
	});
});
<|endoftext|>


<commit_before>/* global App */

/**
 * Navigation module
 *
 * Modified: 2015/10/20
 * @author softwarespot
 */
App.namespace().navigation = (function (window, document, $, core, undefined) {
    // Constants

    // SemVer version number of the module
    var VERSION = '1.0.0';

    // Unique global identifier. Internal usage only
    // var GUID = 'AEE5249D-6F2D-44C7-8FFB-5391C69A4CF2';

    // Fields

    // Has the events been binded
    var _isEventsBound = false;

    // Store if the module has been initialised
    var _isInitialised = false;

    // Store the body jQuery selector object
    var $_body = null;

    // Store the navigation hyperlinks jQuery selector object
    var $_navigationLinks = null;

    // Events object
    var _events = {
        // Click event string
        navigation: 'click.app.navigation',

        // When the click event is invoked, call the following function
        navigationFn: function navigationFn(event) {
            // Get the href attribute using vanilla JavaScript
            var href = event.currentTarget.getAttribute('href');

            // The following idea was taken from sizzle.js, URL: https://github.com/jquery/sizzle/blob/master/dist/sizzle.js

            // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
            var identifier = '(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+';

            // Create a regular expression object to test valid id fragments
            var reIdentifier = new window.RegExp('^#' + identifier + '$');

            // Only bind if it's a valid anchor link
            if (!reIdentifier.test(href)) {
                return true;
            }

            // Prevent default click propagation
            event.preventDefault();

            // Get the jQuery selector object based on the href attribute value
            var $element = $(href);

            // If an error occurred i.e. no node was found in the DOM, the return false
            if (!core.isjQueryNotEmpty($element)) {
                return false;
            }

            // Scroll to the element intended
            _scrollTo($element, 500);

            // Fallback to prevent jitter
            return false;
        }
    };

    // Methods

    /**
     * Initialise the module
     *
     * @param {object} config Options to configure the module
     * @return {undefined}
     */
    function init( /*config*/ ) {
        // Default config that can be overwritten by passing through the config variable
        // var defaultConfig = {};

        // Combine the passed config
        // $.extend(defaultConfig, config);

        _cacheDom();
        _bindEvents();

        _isInitialised = true;
    }

    /**
     * Destroy the module
     *
     * @return {undefined}
     */
    function destroy() {
        _unbindEvents();

        $_body = null;
        $_navigationLinks = null;

        _isInitialised = false;
    }

    /**
     * Get the version number of the module
     *
     * @returns {number} Version number
     */
    function getVersion() {
        return VERSION;
    }

    /**
     * Initialise all DOM cachable variables
     *
     * @return {undefined}
     */
    function _cacheDom() {
        $_body = $('html, body');
        $_navigationLinks = $('header').find('a');
    }

    /**
     * Bind events
     *
     * @return {undefined}
     */
    function _bindEvents() {
        if (_isEventsBound) {
            _unbindEvents();
        }

        // Navigation hyperlink elements
        $_navigationLinks.on(_events.navigation, _events.navigationFn);

        _isEventsBound = true;
    }

    /**
     * Unbind events
     *
     * @return {undefined}
     */
    function _unbindEvents() {
        if (!_isEventsBound) {
            return;
        }

        $_navigationLinks.off(_events.navigation, _events.navigationFn);

        _isEventsBound = false;
    }

    /**
     * Scroll to an element on the page
     *
     * @param{HTMLElement} $element jQuery HTMLElement node object
     * @param{number|string} speed Speed at which to scroll the element
     * @return {undefined}
     */
    function _scrollTo($element, speed) {
        if (core.isUndefined(speed)) {
            speed = 500;
        }

        // Animate from the body to the top of the element's offset position
        $_body.animate({
            scrollTop: $element.offset().top
        }, speed);
    }

    // Initialise the module
    $(function () {
        // init();
    });

    // Public API
    return {
        init: init,
        destroy: destroy,
        getVersion: getVersion
    };
})(this, this.document, this.jQuery, this.App.core);
<commit_msg>Changed order of namespaces for click events<commit_after>/* global App */

/**
 * Navigation module
 *
 * Modified: 2015/10/20
 * @author softwarespot
 */
App.namespace().navigation = (function (window, document, $, core, undefined) {
    // Constants

    // SemVer version number of the module
    var VERSION = '1.0.0';

    // Unique global identifier. Internal usage only
    // var GUID = 'AEE5249D-6F2D-44C7-8FFB-5391C69A4CF2';

    // Fields

    // Has the events been binded
    var _isEventsBound = false;

    // Store if the module has been initialised
    var _isInitialised = false;

    // Store the body jQuery selector object
    var $_body = null;

    // Store the navigation hyperlinks jQuery selector object
    var $_navigationLinks = null;

    // Events object
    var _events = {
        // Click event string
        navigation: 'click.navigation.app',

        // When the click event is invoked, call the following function
        navigationFn: function navigationFn(event) {
            // Get the href attribute using vanilla JavaScript
            var href = event.currentTarget.getAttribute('href');

            // The following idea was taken from sizzle.js, URL: https://github.com/jquery/sizzle/blob/master/dist/sizzle.js

            // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
            var identifier = '(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+';

            // Create a regular expression object to test valid id fragments
            var reIdentifier = new window.RegExp('^#' + identifier + '$');

            // Only bind if it's a valid anchor link
            if (!reIdentifier.test(href)) {
                return true;
            }

            // Prevent default click propagation
            event.preventDefault();

            // Get the jQuery selector object based on the href attribute value
            var $element = $(href);

            // If an error occurred i.e. no node was found in the DOM, the return false
            if (!core.isjQueryNotEmpty($element)) {
                return false;
            }

            // Scroll to the element intended
            _scrollTo($element, 500);

            // Fallback to prevent jitter
            return false;
        }
    };

    // Methods

    /**
     * Initialise the module
     *
     * @param {object} config Options to configure the module
     * @return {undefined}
     */
    function init( /*config*/ ) {
        // Default config that can be overwritten by passing through the config variable
        // var defaultConfig = {};

        // Combine the passed config
        // $.extend(defaultConfig, config);

        _cacheDom();
        _bindEvents();

        _isInitialised = true;
    }

    /**
     * Destroy the module
     *
     * @return {undefined}
     */
    function destroy() {
        _unbindEvents();

        $_body = null;
        $_navigationLinks = null;

        _isInitialised = false;
    }

    /**
     * Get the version number of the module
     *
     * @returns {number} Version number
     */
    function getVersion() {
        return VERSION;
    }

    /**
     * Initialise all DOM cachable variables
     *
     * @return {undefined}
     */
    function _cacheDom() {
        $_body = $('html, body');
        $_navigationLinks = $('header').find('a');
    }

    /**
     * Bind events
     *
     * @return {undefined}
     */
    function _bindEvents() {
        if (_isEventsBound) {
            _unbindEvents();
        }

        // Navigation hyperlink elements
        $_navigationLinks.on(_events.navigation, _events.navigationFn);

        _isEventsBound = true;
    }

    /**
     * Unbind events
     *
     * @return {undefined}
     */
    function _unbindEvents() {
        if (!_isEventsBound) {
            return;
        }

        $_navigationLinks.off(_events.navigation, _events.navigationFn);

        _isEventsBound = false;
    }

    /**
     * Scroll to an element on the page
     *
     * @param{HTMLElement} $element jQuery HTMLElement node object
     * @param{number|string} speed Speed at which to scroll the element
     * @return {undefined}
     */
    function _scrollTo($element, speed) {
        if (core.isUndefined(speed)) {
            speed = 500;
        }

        // Animate from the body to the top of the element's offset position
        $_body.animate({
            scrollTop: $element.offset().top
        }, speed);
    }

    // Initialise the module
    $(function () {
        // init();
    });

    // Public API
    return {
        init: init,
        destroy: destroy,
        getVersion: getVersion
    };
})(this, this.document, this.jQuery, this.App.core);
<|endoftext|>


<commit_before>'use strict';

var instagram = require('../services/instagram.js'),
    twitter = require('../services/twitter.js');

exports.images = function(req, res, cache) {
    var fromTime;
    if (req.query.fromTime) {
        fromTime = req.query.fromtime;
    }
    var cacheKey = 'collection-images' + fromTime;
    cache.get(cacheKey, function(err, value) {
        if (typeof value[cacheKey] !== 'undefined') {
            res.jsonp(value[cacheKey]);
        } else {
            instagram.getImagesByTags([
                'meitsie',
                'viski',
                'stubbselfie',
                'ylevero',
                'venäjä',
                'velkatakuut',
                'maakaasu',
                'putin',
                'kehäkolme',
                'susiraja',
                'pirkanmaa',
                'alibi',
                'julkinensektori',
                'virkamies',
                'säännöstely',
                'kontrolli',
                'suomettuminen'
            ], 6, fromTime, function(err, images) {
                if (err) {
                    console.log(err.message);
                    res.status(500);
                    res.jsonp({
                        error: 'Error while getting data from instagram'
                    });
                } else {
                    var response = {
                        count: images.length,
                        images: images
                    };
                    cache.set(cacheKey, response, 3 * 60, function() {
                        res.jsonp(response);
                    });
                }
            });
        }
    });
};

exports.text = function(req, res, cache) {
    var fromTime;
    if (req.query.fromTime) {
        fromTime = req.query.fromtime;
    }
    var cacheKey = 'collection-text' + fromTime;
    cache.get(cacheKey, function(err, value) {
        if (typeof value[cacheKey] !== 'undefined') {
            res.jsonp(value[cacheKey]);
        } else {
            twitter.getTweetsByHashtags([
                'suomi',
                'helsinki',
                'tampere',
                'bbsuomi',
                'vainelämää',
                'huuhkajat',
                'liiga',
                'politiikka',
                'viski',
                'olut',
                'ylevero',
                'anteeksi',
                'a2ilta',
                'venäjä',
                'susiraja',
                'pirkanmaa',
                'alibi',
                'julkinensektori',
                'virkamies'
            ], 30, fromTime, 'fi', function(err, tweets) {
                if (err) {
                    console.log(err);
                    res.status(500);
                    res.jsonp({
                        error: 'Error while getting data from twitter'
                    });
                } else {
                    tweets.push({
                        timestamp: moment(new Date()).format('X'),
                        text: '#BeatingForeheadVeinCredits Wesa Aapro, Sami Rouhiainen, @Zeikko, @arttusilvast Special Thanks to Ville Hautakangas for #Nocturne #sibhack'
                    });
                    var response = {
                        count: tweets.length,
                        tweets: tweets
                    };
                    cache.set(cacheKey, response, 3 * 60, function() {
                        res.jsonp(response);
                    });
                }
            });
        }
    });
};
<commit_msg>Add moment dependency<commit_after>'use strict';

var instagram = require('../services/instagram.js'),
    twitter = require('../services/twitter.js'),
    moment = require('moment');

exports.images = function(req, res, cache) {
    var fromTime;
    if (req.query.fromTime) {
        fromTime = req.query.fromtime;
    }
    var cacheKey = 'collection-images' + fromTime;
    cache.get(cacheKey, function(err, value) {
        if (typeof value[cacheKey] !== 'undefined') {
            res.jsonp(value[cacheKey]);
        } else {
            instagram.getImagesByTags([
                'meitsie',
                'viski',
                'stubbselfie',
                'ylevero',
                'venäjä',
                'velkatakuut',
                'maakaasu',
                'putin',
                'kehäkolme',
                'susiraja',
                'pirkanmaa',
                'alibi',
                'julkinensektori',
                'virkamies',
                'säännöstely',
                'kontrolli',
                'suomettuminen'
            ], 6, fromTime, function(err, images) {
                if (err) {
                    console.log(err.message);
                    res.status(500);
                    res.jsonp({
                        error: 'Error while getting data from instagram'
                    });
                } else {
                    var response = {
                        count: images.length,
                        images: images
                    };
                    cache.set(cacheKey, response, 3 * 60, function() {
                        res.jsonp(response);
                    });
                }
            });
        }
    });
};

exports.text = function(req, res, cache) {
    var fromTime;
    if (req.query.fromTime) {
        fromTime = req.query.fromtime;
    }
    var cacheKey = 'collection-text' + fromTime;
    cache.get(cacheKey, function(err, value) {
        if (typeof value[cacheKey] !== 'undefined') {
            res.jsonp(value[cacheKey]);
        } else {
            twitter.getTweetsByHashtags([
                'suomi',
                'helsinki',
                'tampere',
                'bbsuomi',
                'vainelämää',
                'huuhkajat',
                'liiga',
                'politiikka',
                'viski',
                'olut',
                'ylevero',
                'anteeksi',
                'a2ilta',
                'venäjä',
                'susiraja',
                'pirkanmaa',
                'alibi',
                'julkinensektori',
                'virkamies'
            ], 30, fromTime, 'fi', function(err, tweets) {
                if (err) {
                    console.log(err);
                    res.status(500);
                    res.jsonp({
                        error: 'Error while getting data from twitter'
                    });
                } else {
                    tweets.push({
                        timestamp: moment(new Date()).format('X'),
                        text: '#BeatingForeheadVeinCredits Wesa Aapro, Sami Rouhiainen, @Zeikko, @arttusilvast Special Thanks to Ville Hautakangas for #Nocturne #sibhack'
                    });
                    var response = {
                        count: tweets.length,
                        tweets: tweets
                    };
                    cache.set(cacheKey, response, 3 * 60, function() {
                        res.jsonp(response);
                    });
                }
            });
        }
    });
};
<|endoftext|>


<commit_before>#!/usr/bin/env node --harmony

var program = require('commander')
var packageJson = require('./package.json')
var path = require('path').posix
var fs = require('fs')
var rollup = require('rollup')
var commonjs = require('rollup-plugin-commonjs')
var nodeResolve = require('rollup-plugin-node-resolve')
var globals = require('rollup-plugin-node-globals')
var builtins = require('rollup-plugin-node-builtins')
var json = require('rollup-plugin-json')
var babel = require('rollup-plugin-babel')
var uglify = require('rollup-plugin-uglify')
var writeJsonFile = require('write-json-file')
var semver = require('semver')
var loadJsonFile = require('load-json-file')
var exec = require('child_process').exec

process.on('uncaughtException', function (err) {
  console.error('An exception was thrown: ', err)
})

function exit (err) {
  console.error(err)
  process.exit(1)
}

program
  .version(packageJson.version)
  .description(packageJson.description)

program.on('--help', function () {
  console.log(`
  Example:

    $ ez-build-lib init ./src/test.js

    Updated package.json:

    ..
      "main": "./dist/test.cjs.js",
      "jsnext:main": "./dist/test.mjs",
      "browser": "./dist/test.umd.js",
      "scripts": {
        "lint": "standard",
        "build": "ez-build-lib build src/test.js",
        "build:watch": "ez-build-lib watch src/test.js",
        "publish": "npm run lint && npm run build && ez-build-lib publish ."
        ..
      }
    ..

    Updated bower.json:

    ..
      "main": "./dist/test.umd.js"
    ..

    Updated .gitignore

    ..
      ./dist/
    ..

    It is encouraged to use \`npm run <cmd>\` from now on:

    $ npm run build
    $ npm run build:watch % for development

  `)
})

program
  .command('init <entry>')
  .description('Sets reasonable properties in the package.json, .gitignore, and bower.json files (optional)')
  .action(async(function * (getCallback, entry, options) {
    var [err, [dir, p]] = yield getPackageJson(entry)
    entry = path.relative(dir, path.resolve(entry)) // convert e.g. absolute paths to relative ones
    p.main = `./dist/${p.name}.cjs.js`
    p['jsnext:main'] = entry // `./dist/${p.name}.mjs`
    p['browser'] = `./dist/${p.name}.umd.js`
    if (p.scripts == null) {
      p.scripts = {}
    }
    p.scripts.build = `ez-build-lib build ${entry}`
    p.scripts['build:watch'] = `ez-build-lib build:watch ${entry}`
    p.scripts['publish'] = 'npm run lint && npm run build && ez-build-lib publish .'

    ;[err] = yield writeJsonFile(path.join(dir, 'package.json'), p, { indent: 2 })
    if (err != null) exit(err)

    // try to get bower.json
    var bowerPath = path.join(dir, 'bower.json')
    ;[err] = yield fs.access(bowerPath, fs.F_OK, getCallback())
    if (!err) {
      var bower
      ;[err, bower] = yield loadJsonFile(bowerPath)
      if (err) exit(err)
      bower.main = p.browser // the umd module
      ;[err] = yield writeJsonFile(bowerPath, bower, { indent: 2 })
      if (err != null) exit(err)
    } else {
      console.warn('You did not specify a bower.json file.')
    }

    // set .gitignore
    var gitignorePath = path.join(dir, '.gitignore')
    var gitignore
    ;[err, gitignore] = yield fs.readFile(gitignorePath, 'utf8', getCallback())
    if (gitignore == null || gitignore.split('\n').every(function (line) { return line !== './dist' })) {
      gitignore = 'dist\n' + (gitignore || '')
      ;[err] = yield fs.writeFile(gitignorePath, gitignore, getCallback())
      if (err) exit(err)
    }
  }))

program
  .command('build <entry>')
  .description('Builds your project')
  .action(function (entry, options) {
    computeConfig(entry).then(function (config) {
      runBuildCjs(config)
      runBuildUmd(config)
    })
  })

program
  .command('build:watch')
  .description('Builds the project whenever a file changes')

program
  .command('publish <dir>')
  .description('Publish the project including distribution files:\n               Build > version bump > commit > create git tag > publish to npm ')
  .action(async(function * (getCallback, dir) {
    var rl = require('readline').createInterface({
      input: process.stdin,
      output: process.stdout
    })
    dir = path.resolve(dir)
    var [err, p] = yield loadJsonFile(path.join(dir, 'package.json'))
    if (err) exit('package.json does not exist in this directory!')

    // ask for version increment type
    var validTypes = ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease']
    var [type] = yield rl.question(`How do you want to increment the version? [${validTypes.join('|')}]\n=>`, getCallback())

    var validType = validTypes.some(function (t) { return t === type })

    if (validType) {
      p.version = semver.inc(p.version, type, 'alpha')
      if (p.version == null) exit('Invalid semver version in package.json!')
    } else {
      exit('You must choose one of of these: ' + validTypes.join(' | '))
    }

    var [message] = yield rl.question(`Insert the release message (press double enter to continue)\n=>`, getCallback())
    var input = message
    while (input !== '') {
      ;[input] = yield rl.question('..', getCallback())
      message = message + '\n' + input
    }

    // ask user if sHe really want's to publish
    var [answer] = yield rl.question(`Publishing version ${p.version}. Message: ${message}. Okay? [y|N]\n=>`, getCallback())
    if (['y', 'Y', 'yes'].every(function (a) { return a !== answer })) {
      exit('Interrupt publish')
    }

    // update package.json
    [err] = yield writeJsonFile(path.join(dir, 'package.json'), p, { indent: 2 })
    if (err != null) exit(err)

    // try to update bower.json
    var bowerPath = path.join(dir, 'bower.json')
    ;[err] = yield fs.access(bowerPath, fs.W_OK, getCallback())
    if (!err) {
      var bower
      ;[err, bower] = yield loadJsonFile(bowerPath)
      bower.version = p.version
      ;[err] = yield writeJsonFile(bowerPath, bower, { indent: 2 })
      if (err != null) exit(err)
    }

    var stdout, stderr
    var opts = { cwd: dir }

    // commit remaining changes (changes to package.json & bower.json)
    ;[err, stdout, stderr] = yield exec(`git commit -am "Publish v${p.version}\n\n${message}""`, opts, getCallback())
    if (err) {
      exit(`Unable to commit remaining changes:\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Committed remaining changes')
    }

    // push commit
    ;[err, stdout, stderr] = yield exec('git push', opts, getCallback())
    if (err) {
      exit(`Unable to push changes:\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Pushed changes')
    }

    // detach head
    ;[err, stdout, stderr] = yield exec('git checkout --detach', opts, getCallback())
    if (err) {
      exit(`Unable to detach head:\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Detached head')
    }

    // check if dist files exist
    var [distributionFilesExist] = yield fs.access(path.join(dir, 'dist'), fs.F_OK, getCallback())
    if (!distributionFilesExist) {
      // add dist files
      ;[err, stdout, stderr] = yield exec('git add ./dist/* -f', opts, getCallback())
      if (err) {
        exit(`Unable to add dist files:\n\n${stdout}\n\n${stderr}`)
      } else {
        console.log('✓ Added dist files to index')
      }

      // commit dist files
      ;[err, stdout, stderr] = yield exec(`git commit -am "Publish v${p.version} -- added dist files"`, opts, getCallback())
      if (err) {
        exit(`Unable to commit dist files:\n\n${stdout}\n\n${stderr}`)
      } else {
        console.log('✓ Committed dist files')
      }
    }

    // tag releasefiles
    ;[err, stdout, stderr] = yield exec(`git tag v${p.version} -m "${message}"`, opts, getCallback())
    if (err) {
      exit(`Unable to tag commit:\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Tagged release')
    }

    // push tag
    ;[err, stdout, stderr] = yield exec(`git push origin v${p.version}`, opts, getCallback())
    if (err) {
      exit(`Unable to tag commit:\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Pushed tag')
    }

    // Publish to npm
    ;[err, stdout, stderr] = yield exec('npm publish', opts, getCallback())
    if (err) {
      console.log('❌ Failed to publish to npm. Please call `npm publish` yourself')
    } else {
      console.log('✓ Published to npm')
    }

    // check out master
    ;[err, stdout, stderr] = yield exec('git checkout master', opts, getCallback())
    if (err) {
      exit(`Unable to checkout branch 'master':\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Checked out master branch')
    }
  }))

var cjsBundle = null
var runBuildCjs = function runBuildCjs (config) {
  rollup.rollup({
    entry: config.entry,
    cache: cjsBundle,
    external: config.external
  }).then(function (bundle) {
    cjsBundle = bundle

    bundle.write({
      moduleName: config.name,
      format: 'cjs',
      dest: config.cjs,
      sourceMap: true
    })
  })
}

var umdBundle = null
var runBuildUmd = function runBuildUmd (config) {
  rollup.rollup({
    entry: config.entry,
    cache: umdBundle,
    plugins: [
      builtins(),
      nodeResolve({
        // preferBuiltins: false,
        jsnext: true,
        main: true,
        browser: true
      }),
      commonjs({
        ignoreGlobal: true
      }),
      globals(),
      json(),
      babel({
        exclude: 'node_modules/**',
        presets: ['es2015-rollup'],
        babelrc: false
      }),
      uglify()
    ]
  }).then(function (bundle) {
    umdBundle = bundle
    bundle.write({
      moduleName: config.name,
      format: 'umd',
      dest: config.umd,
      sourceMap: true
    })
  })
}

function computeConfig (entry) {
  return getPackageJson(entry).then(function ([dir, p]) {
    if (p.main == null) {
      exit('You must specify the `main` property in your package.json!')
    }

    // all dependencies are external modules (exclude them in cjs builds)
    var external = []
    for (var name in p.dependencies) {
      external.push(name)
    }

    return Promise.resolve({
      name: p.name,
      entry: entry,
      cjs: path.join(dir, p.main),
      umd: path.join(dir, p['browser']),
      external: external
    })
  })
}

var getPackageJson = async(function * (getCallback, entry) {
  var dir = path.resolve(path.dirname(entry))

  while (true) {
    var [err] = yield fs.access(dir, fs.F_OK, getCallback())
    if (!err) {
      var pDir = path.join(dir, 'package.json')
      ;[err] = yield fs.access(pDir, fs.F_OK, getCallback())
      if (!err) {
        var p
        ;[err, p] = yield loadJsonFile(pDir)
        if (err) exit(err)
        return [dir, p]
      } else {
        dir = path.join(dir, '..')
      }
    } else exit('You need to specify a package.json file!')
  }
})

function async (generator) {
  return function () {
    var args = Array.prototype.slice.call(arguments)
    return new Promise(function (resolve, reject) {
      function getCallback (name) {
        callbacks++
        if (name == null) {
          if (callbacks !== 1) {
            gen.throw('You may either request several named callbacks or one unnamed callback!')
          } else {
            return function cb () {
              callbacks--
              handleResult(gen.next(Array.prototype.slice.call(arguments)))
            }
          }
        } else {
          if (callbackValues[name] != null) {
            gen.throw('You already used that callback namespace! You need to `yield` first!')
          } else {
            return function cb () {
              callbacks--
              callbackValues[name] = Array.prototype.slice.call(arguments)
              if (callbacks === 0) {
                handleResult(gen.next(callbackValues))
                callbackValues = {}
              }
            }
          }
        }
      }
      var callbacks = 0
      var callbackValues = {}
      var gen = generator.apply(this, [getCallback].concat(args))

      function handleResult (result) {
        if (result.done) {
          resolve(result.value)
        } else if (result.value instanceof Promise) {
          result.value.then(function (val) {
            handleResult(gen.next([null, val]))
          }, function (err) {
            handleResult(gen.next([err || 'Promise was rejected']))
          })
        } else if (callbacks === 0) {
          gen.throw('This object is not yield-able, also you did not request any callbacks!')
        }
      }
      handleResult(gen.next())
    })
  }
}

program
  .command('*')
  .action(function (env) {
    program.help()
  })

program
  .parse(process.argv)

if (program.args.length === 0) {
  program.help()
}
<commit_msg>Exit process when finished<commit_after>#!/usr/bin/env node --harmony

var program = require('commander')
var packageJson = require('./package.json')
var path = require('path').posix
var fs = require('fs')
var rollup = require('rollup')
var commonjs = require('rollup-plugin-commonjs')
var nodeResolve = require('rollup-plugin-node-resolve')
var globals = require('rollup-plugin-node-globals')
var builtins = require('rollup-plugin-node-builtins')
var json = require('rollup-plugin-json')
var babel = require('rollup-plugin-babel')
var uglify = require('rollup-plugin-uglify')
var writeJsonFile = require('write-json-file')
var semver = require('semver')
var loadJsonFile = require('load-json-file')
var exec = require('child_process').exec

process.on('uncaughtException', function (err) {
  console.error('An exception was thrown: ', err)
})

function exit (err) {
  console.error(err)
  process.exit(1)
}

program
  .version(packageJson.version)
  .description(packageJson.description)

program.on('--help', function () {
  console.log(`
  Example:

    $ ez-build-lib init ./src/test.js

    Updated package.json:

    ..
      "main": "./dist/test.cjs.js",
      "jsnext:main": "./dist/test.mjs",
      "browser": "./dist/test.umd.js",
      "scripts": {
        "lint": "standard",
        "build": "ez-build-lib build src/test.js",
        "build:watch": "ez-build-lib watch src/test.js",
        "publish": "npm run lint && npm run build && ez-build-lib publish ."
        ..
      }
    ..

    Updated bower.json:

    ..
      "main": "./dist/test.umd.js"
    ..

    Updated .gitignore

    ..
      ./dist/
    ..

    It is encouraged to use \`npm run <cmd>\` from now on:

    $ npm run build
    $ npm run build:watch % for development

  `)
})

program
  .command('init <entry>')
  .description('Sets reasonable properties in the package.json, .gitignore, and bower.json files (optional)')
  .action(async(function * (getCallback, entry, options) {
    var [err, [dir, p]] = yield getPackageJson(entry)
    entry = path.relative(dir, path.resolve(entry)) // convert e.g. absolute paths to relative ones
    p.main = `./dist/${p.name}.cjs.js`
    p['jsnext:main'] = entry // `./dist/${p.name}.mjs`
    p['browser'] = `./dist/${p.name}.umd.js`
    if (p.scripts == null) {
      p.scripts = {}
    }
    p.scripts.build = `ez-build-lib build ${entry}`
    p.scripts['build:watch'] = `ez-build-lib build:watch ${entry}`
    p.scripts['publish'] = 'npm run lint && npm run build && ez-build-lib publish .'

    ;[err] = yield writeJsonFile(path.join(dir, 'package.json'), p, { indent: 2 })
    if (err != null) exit(err)

    // try to get bower.json
    var bowerPath = path.join(dir, 'bower.json')
    ;[err] = yield fs.access(bowerPath, fs.F_OK, getCallback())
    if (!err) {
      var bower
      ;[err, bower] = yield loadJsonFile(bowerPath)
      if (err) exit(err)
      bower.main = p.browser // the umd module
      ;[err] = yield writeJsonFile(bowerPath, bower, { indent: 2 })
      if (err != null) exit(err)
    } else {
      console.warn('You did not specify a bower.json file.')
    }

    // set .gitignore
    var gitignorePath = path.join(dir, '.gitignore')
    var gitignore
    ;[err, gitignore] = yield fs.readFile(gitignorePath, 'utf8', getCallback())
    if (gitignore == null || gitignore.split('\n').every(function (line) { return line !== './dist' })) {
      gitignore = 'dist\n' + (gitignore || '')
      ;[err] = yield fs.writeFile(gitignorePath, gitignore, getCallback())
      if (err) exit(err)
    }
  }))

program
  .command('build <entry>')
  .description('Builds your project')
  .action(function (entry, options) {
    computeConfig(entry).then(function (config) {
      runBuildCjs(config)
      runBuildUmd(config)
    })
  })

program
  .command('build:watch')
  .description('Builds the project whenever a file changes')

program
  .command('publish <dir>')
  .description('Publish the project including distribution files:\n               Build > version bump > commit > create git tag > publish to npm ')
  .action(async(function * (getCallback, dir) {
    var rl = require('readline').createInterface({
      input: process.stdin,
      output: process.stdout
    })
    dir = path.resolve(dir)
    var [err, p] = yield loadJsonFile(path.join(dir, 'package.json'))
    if (err) exit('package.json does not exist in this directory!')

    // ask for version increment type
    var validTypes = ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease']
    var [type] = yield rl.question(`How do you want to increment the version? [${validTypes.join('|')}]\n=>`, getCallback())

    var validType = validTypes.some(function (t) { return t === type })

    if (validType) {
      p.version = semver.inc(p.version, type, 'alpha')
      if (p.version == null) exit('Invalid semver version in package.json!')
    } else {
      exit('You must choose one of of these: ' + validTypes.join(' | '))
    }

    var [message] = yield rl.question(`Insert the release message (press double enter to continue)\n=>`, getCallback())
    var input = message
    while (input !== '') {
      ;[input] = yield rl.question('..', getCallback())
      message = message + '\n' + input
    }

    // ask user if sHe really want's to publish
    var [answer] = yield rl.question(`Publishing version ${p.version}. Message: ${message}. Okay? [y|N]\n=>`, getCallback())
    if (['y', 'Y', 'yes'].every(function (a) { return a !== answer })) {
      exit('Interrupt publish')
    }

    // update package.json
    [err] = yield writeJsonFile(path.join(dir, 'package.json'), p, { indent: 2 })
    if (err != null) exit(err)

    // try to update bower.json
    var bowerPath = path.join(dir, 'bower.json')
    ;[err] = yield fs.access(bowerPath, fs.W_OK, getCallback())
    if (!err) {
      var bower
      ;[err, bower] = yield loadJsonFile(bowerPath)
      bower.version = p.version
      ;[err] = yield writeJsonFile(bowerPath, bower, { indent: 2 })
      if (err != null) exit(err)
    }

    var stdout, stderr
    var opts = { cwd: dir }

    // commit remaining changes (changes to package.json & bower.json)
    ;[err, stdout, stderr] = yield exec(`git commit -am "Publish v${p.version}\n\n${message}""`, opts, getCallback())
    if (err) {
      exit(`Unable to commit remaining changes:\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Committed remaining changes')
    }

    // push commit
    ;[err, stdout, stderr] = yield exec('git push', opts, getCallback())
    if (err) {
      exit(`Unable to push changes:\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Pushed changes')
    }

    // detach head
    ;[err, stdout, stderr] = yield exec('git checkout --detach', opts, getCallback())
    if (err) {
      exit(`Unable to detach head:\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Detached head')
    }

    // check if dist files exist
    var [distributionFilesExist] = yield fs.access(path.join(dir, 'dist'), fs.F_OK, getCallback())
    if (!distributionFilesExist) {
      // add dist files
      ;[err, stdout, stderr] = yield exec('git add ./dist/* -f', opts, getCallback())
      if (err) {
        exit(`Unable to add dist files:\n\n${stdout}\n\n${stderr}`)
      } else {
        console.log('✓ Added dist files to index')
      }

      // commit dist files
      ;[err, stdout, stderr] = yield exec(`git commit -am "Publish v${p.version} -- added dist files"`, opts, getCallback())
      if (err) {
        exit(`Unable to commit dist files:\n\n${stdout}\n\n${stderr}`)
      } else {
        console.log('✓ Committed dist files')
      }
    }

    // tag releasefiles
    ;[err, stdout, stderr] = yield exec(`git tag v${p.version} -m "${message}"`, opts, getCallback())
    if (err) {
      exit(`Unable to tag commit:\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Tagged release')
    }

    // push tag
    ;[err, stdout, stderr] = yield exec(`git push origin v${p.version}`, opts, getCallback())
    if (err) {
      exit(`Unable to tag commit:\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Pushed tag')
    }

    // Publish to npm
    ;[err, stdout, stderr] = yield exec('npm publish', opts, getCallback())
    if (err) {
      console.log('❌ Failed to publish to npm. Please call `npm publish` yourself')
    } else {
      console.log('✓ Published to npm')
    }

    // check out master
    ;[err, stdout, stderr] = yield exec('git checkout master', opts, getCallback())
    if (err) {
      exit(`Unable to checkout branch 'master':\n\n${stdout}\n\n${stderr}`)
    } else {
      console.log('✓ Checked out master branch')
      process.exit(0)
    }
  }))

var cjsBundle = null
var runBuildCjs = function runBuildCjs (config) {
  rollup.rollup({
    entry: config.entry,
    cache: cjsBundle,
    external: config.external
  }).then(function (bundle) {
    cjsBundle = bundle

    bundle.write({
      moduleName: config.name,
      format: 'cjs',
      dest: config.cjs,
      sourceMap: true
    })
  })
}

var umdBundle = null
var runBuildUmd = function runBuildUmd (config) {
  rollup.rollup({
    entry: config.entry,
    cache: umdBundle,
    plugins: [
      builtins(),
      nodeResolve({
        // preferBuiltins: false,
        jsnext: true,
        main: true,
        browser: true
      }),
      commonjs({
        ignoreGlobal: true
      }),
      globals(),
      json(),
      babel({
        exclude: 'node_modules/**',
        presets: ['es2015-rollup'],
        babelrc: false
      }),
      uglify()
    ]
  }).then(function (bundle) {
    umdBundle = bundle
    bundle.write({
      moduleName: config.name,
      format: 'umd',
      dest: config.umd,
      sourceMap: true
    })
  })
}

function computeConfig (entry) {
  return getPackageJson(entry).then(function ([dir, p]) {
    if (p.main == null) {
      exit('You must specify the `main` property in your package.json!')
    }

    // all dependencies are external modules (exclude them in cjs builds)
    var external = []
    for (var name in p.dependencies) {
      external.push(name)
    }

    return Promise.resolve({
      name: p.name,
      entry: entry,
      cjs: path.join(dir, p.main),
      umd: path.join(dir, p['browser']),
      external: external
    })
  })
}

var getPackageJson = async(function * (getCallback, entry) {
  var dir = path.resolve(path.dirname(entry))

  while (true) {
    var [err] = yield fs.access(dir, fs.F_OK, getCallback())
    if (!err) {
      var pDir = path.join(dir, 'package.json')
      ;[err] = yield fs.access(pDir, fs.F_OK, getCallback())
      if (!err) {
        var p
        ;[err, p] = yield loadJsonFile(pDir)
        if (err) exit(err)
        return [dir, p]
      } else {
        dir = path.join(dir, '..')
      }
    } else exit('You need to specify a package.json file!')
  }
})

function async (generator) {
  return function () {
    var args = Array.prototype.slice.call(arguments)
    return new Promise(function (resolve, reject) {
      function getCallback (name) {
        callbacks++
        if (name == null) {
          if (callbacks !== 1) {
            gen.throw('You may either request several named callbacks or one unnamed callback!')
          } else {
            return function cb () {
              callbacks--
              handleResult(gen.next(Array.prototype.slice.call(arguments)))
            }
          }
        } else {
          if (callbackValues[name] != null) {
            gen.throw('You already used that callback namespace! You need to `yield` first!')
          } else {
            return function cb () {
              callbacks--
              callbackValues[name] = Array.prototype.slice.call(arguments)
              if (callbacks === 0) {
                handleResult(gen.next(callbackValues))
                callbackValues = {}
              }
            }
          }
        }
      }
      var callbacks = 0
      var callbackValues = {}
      var gen = generator.apply(this, [getCallback].concat(args))

      function handleResult (result) {
        if (result.done) {
          resolve(result.value)
        } else if (result.value instanceof Promise) {
          result.value.then(function (val) {
            handleResult(gen.next([null, val]))
          }, function (err) {
            handleResult(gen.next([err || 'Promise was rejected']))
          })
        } else if (callbacks === 0) {
          gen.throw('This object is not yield-able, also you did not request any callbacks!')
        }
      }
      handleResult(gen.next())
    })
  }
}

program
  .command('*')
  .action(function (env) {
    program.help()
  })

program
  .parse(process.argv)

if (program.args.length === 0) {
  program.help()
}
<|endoftext|>


<commit_before>'use strict';

angular.module('projetobrasil.ufc.jogo.services', [])
	.factory('GerenciadorJogo', ['$rootScope', '$state', 'Angularytics', 'ngDialog', '$timeout',
	 function ($rootScope, $state, Angularytics, ngDialog, $timeout){
		var maxRounds = 3;

		var gerenciador = {
			roundAtual : 0,
			maxGolpesRound : 10,
			minRoundsParaVitoria: parseInt(maxRounds/2)+1,
			totalGolpesSessaoCount: 0,
			candidatos : {}
		};

		gerenciador.totalGopesRound = function(){
			var totalGolpesSofridos = 0;
			_.each(this.candidatos, function(c){
				totalGolpesSofridos += c.golpesSofridos;
			});
			return totalGolpesSofridos;
		};

		// Gerencia o jogo
		gerenciador.inicializaNovoJogo = function(){
			Angularytics.trackEvent('Jogo', 'Jogo iniciado');
			this.roundAtual = 0;
			var idsCandidatos = $rootScope.idsCandidatos;
			var candidatos = this.candidatos;

			_.each(idsCandidatos,function(id){
				if(!candidatos[id]){
					candidatos[id] = {};
				}
				candidatos[id].roundsGanhos = 0;
				candidatos[id].golpesSofridos = 0;
				candidatos[id].adversario = id === idsCandidatos[0] ? idsCandidatos[1] : idsCandidatos[0];
			});
			gerenciador.dialogRound();
		};

		gerenciador.reiniciaJogo = function(){
			this.roundAtual = 0;
			var idsCandidatos = $rootScope.idsCandidatos;
			var candidatos = this.candidatos;

			_.each(idsCandidatos,function(id){
				candidatos[id].roundsGanhos = 0;
				candidatos[id].golpesSofridos = 0;
			});
		};

		gerenciador.finalizaJogo = function(vencedor){
			Angularytics.trackEvent('Jogo', 'Jogo finalizado', $rootScope.nomesCandidatos[vencedor]);
			gerenciador.reiniciaJogo();
			$state.go('resultado');
		};

		// Gerencia os rounds
		gerenciador.comecaNovoRound = function(){
			Angularytics.trackEvent('Jogo', 'Round iniciado');
			this.roundAtual++;
			_.each(this.candidatos,function(c){
				c.golpesSofridos = 0;
			});
			gerenciador.dialogRound();
		};

		gerenciador.finalizaRound = function(vencedor){
			Angularytics.trackEvent('Jogo', 'Round finalizado');
			var roundsGanhos = ++this.candidatos[vencedor].roundsGanhos;
			if(roundsGanhos >= this.minRoundsParaVitoria){
				this.finalizaJogo(vencedor);
			}else{
				this.comecaNovoRound();
			}
		};

		gerenciador.dialogRound = function() {
			var roundDialogId = ngDialog.open({
				template: 'modules/interface/novo-round.html',
				showClose: false,
				className: 'ngdialog-theme-default dialog-round',
				controller: function ($scope) {
					$scope.mensagem = 'Round ' + (gerenciador.roundAtual + 1) ;
					$timeout(function() {
						$scope.mensagem = 'Fight!';
					},1200);
				}
			});
			$timeout(function() {
				ngDialog.close(roundDialogId);
			},1600);
		};

		// Gerencia os golpes
		gerenciador.atualizaPlacar = function(escolhido){
			var adversario = this.candidatos[escolhido].adversario;
			var pontuacao = ++this.candidatos[adversario].golpesSofridos;
			if(pontuacao === this.maxGolpesRound){
				this.finalizaRound(escolhido);
			}
			Angularytics.trackEvent('Jogo', 'Proposta escolhida', 'Round ' + this.roundAtual, this.totalGopesRound());

		};

		// Funções GET
		gerenciador.placarRound = function(){
			var candidatos = this.candidatos;
			var placar = [];
			_.each(candidatos,function(c){
				placar.push(c.golpesSofridos);
			});
			return placar;
		};

		gerenciador.placarGeral = function(){
			var candidatos = this.candidatos;
			var placar = [];
			_.each(candidatos,function(c){
				placar.push(c.roundsGanhos);
			});
			return placar;
		};

		gerenciador.placarCandidato = function(candidato){
			return this.candidatos[candidato].golpesSofridos;
		};

		gerenciador.inicializaNovoJogo();
		return gerenciador;
	}]);
<commit_msg>Adiciona um dialog ao final do jogo para informar o vencedor<commit_after>'use strict';

angular.module('projetobrasil.ufc.jogo.services', [])
	.factory('GerenciadorJogo', ['$rootScope', '$state', 'Angularytics', 'ngDialog', '$timeout',
	 function ($rootScope, $state, Angularytics, ngDialog, $timeout){
		var maxRounds = 3;

		var gerenciador = {
			roundAtual : 0,
			maxGolpesRound : 10,
			minRoundsParaVitoria: parseInt(maxRounds/2)+1,
			totalGolpesSessaoCount: 0,
			candidatos : {}
		};

		gerenciador.totalGopesRound = function(){
			var totalGolpesSofridos = 0;
			_.each(this.candidatos, function(c){
				totalGolpesSofridos += c.golpesSofridos;
			});
			return totalGolpesSofridos;
		};

		// Gerencia o jogo
		gerenciador.inicializaNovoJogo = function(){
			Angularytics.trackEvent('Jogo', 'Jogo iniciado');
			this.roundAtual = 0;
			var idsCandidatos = $rootScope.idsCandidatos;
			var candidatos = this.candidatos;

			_.each(idsCandidatos,function(id){
				if(!candidatos[id]){
					candidatos[id] = {};
				}
				candidatos[id].roundsGanhos = 0;
				candidatos[id].golpesSofridos = 0;
				candidatos[id].adversario = id === idsCandidatos[0] ? idsCandidatos[1] : idsCandidatos[0];
			});
			gerenciador.dialogRound();
		};

		gerenciador.reiniciaJogo = function(){
			this.roundAtual = 0;
			var idsCandidatos = $rootScope.idsCandidatos;
			var candidatos = this.candidatos;

			_.each(idsCandidatos,function(id){
				candidatos[id].roundsGanhos = 0;
				candidatos[id].golpesSofridos = 0;
			});
		};

		gerenciador.finalizaJogo = function(vencedor){
			Angularytics.trackEvent('Jogo', 'Jogo finalizado', $rootScope.nomesCandidatos[vencedor]);

			// FIXME: Colocar uma classe mais genérica para o modal e uma função mais genérica para utilizá-lo.
			var finalDialog = ngDialog.open({
				template: '

{{mensagem}}

',
				plain: true,
				showClose: false,
				className: 'ngdialog-theme-default dialog-round',
				controller: function ($scope) {
					switch(vencedor){
						case 'b6bc0250-0d10-11e4-b416-b9cab1b63b1e':
							$scope.mensagem = 'DILMA WINS!!!';
							break;
						case '827c9cc0-0d10-11e4-a4de-3d18690f2356':
							$scope.mensagem = 'AÉCIO WINS!!!';
							break;
						default:
							$scope.mensagem = 'YOU WIN!!!';
					}
				}
			});

			$timeout(function() {
				ngDialog.close(finalDialog);
				gerenciador.reiniciaJogo();
				$state.go('resultado');
			},1600);

		};

		// Gerencia os rounds
		gerenciador.comecaNovoRound = function(){
			Angularytics.trackEvent('Jogo', 'Round iniciado');
			this.roundAtual++;
			_.each(this.candidatos,function(c){
				c.golpesSofridos = 0;
			});
			gerenciador.dialogRound();
		};

		gerenciador.finalizaRound = function(vencedor){
			Angularytics.trackEvent('Jogo', 'Round finalizado');
			var roundsGanhos = ++this.candidatos[vencedor].roundsGanhos;
			if(roundsGanhos >= this.minRoundsParaVitoria){
				this.finalizaJogo(vencedor);
			}else{
				this.comecaNovoRound();
			}
		};

		gerenciador.dialogRound = function() {
			var roundDialogId = ngDialog.open({
				template: 'modules/interface/novo-round.html',
				showClose: false,
				className: 'ngdialog-theme-default dialog-round',
				controller: function ($scope) {
					$scope.mensagem = 'Round ' + (gerenciador.roundAtual + 1) ;
					$timeout(function() {
						$scope.mensagem = 'Fight!';
					},1200);
				}
			});
			$timeout(function() {
				ngDialog.close(roundDialogId);
			},1600);
		};

		// Gerencia os golpes
		gerenciador.atualizaPlacar = function(escolhido){
			var adversario = this.candidatos[escolhido].adversario;
			var pontuacao = ++this.candidatos[adversario].golpesSofridos;
			if(pontuacao === this.maxGolpesRound){
				this.finalizaRound(escolhido);
			}
			Angularytics.trackEvent('Jogo', 'Proposta escolhida', 'Round ' + this.roundAtual, this.totalGopesRound());

		};

		// Funções GET
		gerenciador.placarRound = function(){
			var candidatos = this.candidatos;
			var placar = [];
			_.each(candidatos,function(c){
				placar.push(c.golpesSofridos);
			});
			return placar;
		};

		gerenciador.placarGeral = function(){
			var candidatos = this.candidatos;
			var placar = [];
			_.each(candidatos,function(c){
				placar.push(c.roundsGanhos);
			});
			return placar;
		};

		gerenciador.placarCandidato = function(candidato){
			return this.candidatos[candidato].golpesSofridos;
		};

		gerenciador.inicializaNovoJogo();
		return gerenciador;
	}]);
<|endoftext|>


<commit_before>define(["backbone", "underscore", "places/models/CategoryModel", "moxie.conf"], function(Backbone, _, Category, conf) {

    var CategoryCollection = Backbone.Collection.extend({
        model: Category,
        url: conf.urlFor('places_categories'),
        parse: function(data) {
            // The data Moxie presents for categories is in a (sensible) tree structure.
            // This kind of structure doesn't fit the Model/Collection paradigm in Backbone
            // too well, so we flatten the structure. We leave behind markers for 'depth'
            // and build a '/' delimited path for the branches of the tree. That way we can
            // make fast queries on the content at depth 2 without any need to traverse again.
            var flattened_cats = [];
            function flatten_categories(prefix, depth, cats) {
                depth++;
                for (var i=0; i < cats.length; i++) {
                    var cat_data = cats[i];
                    // Since the top level type is simply '/' we need this logic to stop us prefixing with '//'
                    var new_prefix = (prefix.length > 1) ? prefix + '/' + cat_data.type : prefix + cat_data.type;
                    // A '/' delimited path which reflects the tree structure
                    // eg. /transport/car-park/park-and-ride
                    cat_data.type_prefixed = new_prefix;
                    // How far into the tree are we? This is kept around as a convenience.
                    cat_data.depth = depth;
                    if (cat_data.types) {
                        cat_data.hasTypes = true;
                        flatten_categories(new_prefix, depth, cat_data.types);
                    }
                    // Don't include the recursive structure in the models
                    flattened_cats.push(_.omit(cat_data, ['types']));
                }
            }
            data.type = data.type || '/';
            flatten_categories('', 0, [data]);
            return flattened_cats;
        }
    });

    return CategoryCollection;

});
<commit_msg>Added depth limit to categories import<commit_after>define(["backbone", "underscore", "places/models/CategoryModel", "moxie.conf"], function(Backbone, _, Category, conf) {

    var DEPTH_LIMIT = 3;
    var CategoryCollection = Backbone.Collection.extend({
        model: Category,
        url: conf.urlFor('places_categories'),
        parse: function(data) {
            // The data Moxie presents for categories is in a (sensible) tree structure.
            // This kind of structure doesn't fit the Model/Collection paradigm in Backbone
            // too well, so we flatten the structure. We leave behind markers for 'depth'
            // and build a '/' delimited path for the branches of the tree. That way we can
            // make fast queries on the content at depth 2 without any need to traverse again.
            var flattened_cats = [];
            function flatten_categories(prefix, depth, cats) {
                depth++;
                for (var i=0; i < cats.length; i++) {
                    var cat_data = cats[i];
                    // Since the top level type is simply '/' we need this logic to stop us prefixing with '//'
                    var new_prefix = (prefix.length > 1) ? prefix + '/' + cat_data.type : prefix + cat_data.type;
                    // A '/' delimited path which reflects the tree structure
                    // eg. /transport/car-park/park-and-ride
                    cat_data.type_prefixed = new_prefix;
                    // How far into the tree are we? This is kept around as a convenience.
                    cat_data.depth = depth;
                    if (cat_data.types && depth < DEPTH_LIMIT) {
                        cat_data.hasTypes = true;
                        flatten_categories(new_prefix, depth, cat_data.types);
                    }
                    // Don't include the recursive structure in the models
                    flattened_cats.push(_.omit(cat_data, ['types']));
                }
            }
            data.type = data.type || '/';
            flatten_categories('', 0, [data]);
            return flattened_cats;
        }
    });

    return CategoryCollection;

});
<|endoftext|>


<commit_before>const { performance } = require(`perf_hooks`)

const { sync: glob } = require(`fast-glob`)
const nodeFetch = require(`node-fetch`)
const uuidv4 = require(`uuid/v4`)
const { execSync } = require(`child_process`)
const fs = require(`fs`)

const bootstrapTime = performance.now()

const BENCHMARK_REPORTING_URL =
  process.env.BENCHMARK_REPORTING_URL === `cli`
    ? undefined
    : process.env.BENCHMARK_REPORTING_URL

// Track the last received `api` because not all events in this plugin will receive one
let lastApi
function reportInfo(...args) {
  ;(lastApi ? lastApi.reporter : console).info(...args)
}
function reportError(...args) {
  ;(lastApi ? lastApi.reporter : console).error(...args)
}

function execToStr(cmd) {
  return String(
    execSync(cmd, {
      encoding: `utf8`,
    }) ?? ``
  ).trim()
}
function execToInt(cmd) {
  // `parseInt` can return `NaN` for unexpected args
  // `Number` can return undefined for unexpected args
  // `0 | x` (bitwise or) will always return 0 for unexpected args, or 32bit int
  return execToStr(cmd) | 0
}

class BenchMeta {
  constructor() {
    this.flushing = undefined // Promise of flushing if that has started
    this.flushed = false // Completed flushing?
    this.localTime = new Date().toISOString()
    this.timestamps = {
      // TODO: we should also have access to node's timing data and see how long it took before bootstrapping this script
      bootstrapTime, // Start of this file
      instantiationTime: performance.now(), // Instantiation time of this class
      benchmarkStart: 0, // Start of benchmark itself
      preInit: 0, // Gatsby onPreInit life cycle
      preBootstrap: 0, // Gatsby onPreBootstrap life cycle
      preBuild: 0, // Gatsby onPreBuild life cycle
      postBuild: 0, // Gatsby onPostBuild life cycle
      benchmarkEnd: 0, // End of benchmark itself
    }
    this.started = false
  }

  getData() {
    // Get memory usage snapshot first (just in case)
    const { rss, heapTotal, heapUsed, external } = process.memoryUsage()
    const memory = {
      rss: rss ?? 0,
      heapTotal: heapTotal ?? 0,
      heapUsed: heapUsed ?? 0,
      external: external ?? 0,
    }

    for (const key in this.timestamps) {
      this.timestamps[key] = Math.floor(this.timestamps[key])
    }

    // For the time being, our target benchmarks are part of the main repo
    // And we will want to know what version of the repo we're testing with
    // This won't work as intended when running a site not in our repo (!)
    const gitHash = execToStr(`git rev-parse HEAD`)
    // Git only supports UTC tz through env var, but the unix time stamp is UTC
    const unixStamp = execToStr(`git show --quiet --date=unix --format="%cd"`)
    const commitTime = new Date(parseInt(unixStamp, 10) * 1000).toISOString()

    const nodejsVersion = process.version

    // This assumes the benchmark is started explicitly from `node_modules/.bin/gatsby`, and not a global install
    // (This is what `gatsby --version` does too, ultimately)
    const gatsbyCliVersion = require(`gatsby-cli/package.json`).version

    const gatsbyVersion = require(`gatsby/package.json`).version

    const sharpVersion = fs.existsSync(`node_modules/sharp/package.json`)
      ? require(`sharp/package.json`).version
      : `none`

    const webpackVersion = require(`webpack/package.json`).version

    const publicJsSize = glob(`public/*.js`).reduce(
      (t, file) => t + fs.statSync(file).size,
      0
    )

    const jpgCount = execToInt(
      `find public .cache  -type f -iname "*.jpg" -or -iname "*.jpeg" | wc -l`
    )

    const pngCount = execToInt(
      `find public .cache  -type f -iname "*.png" | wc -l`
    )

    const gifCount = execToInt(
      `find public .cache  -type f -iname "*.gif" | wc -l`
    )

    const otherCount = execToInt(
      `find public .cache  -type f -iname "*.bmp" -or -iname "*.tif" -or -iname "*.webp" -or -iname "*.svg" | wc -l`
    )

    const pageCount = glob(`**/**.json`, {
      cwd: `./public/page-data`,
      nocase: true,
    }).length

    let siteId = ``
    try {
      // The tags ought to be a json string, but we try/catch it just in case it's not, or not a valid json string
      siteId =
        JSON.parse(process.env?.GATSBY_TELEMETRY_TAGS ?? `{}`)?.siteId ?? `` // Set by server
    } catch (e) {
      siteId = `error`
      reportInfo(
        `Suppressed an error trying to JSON.parse(GATSBY_TELEMETRY_TAGS): ${e}`
      )
    }

    return {
      time: this.localTime,
      sessionId: process.gatsbyTelemetrySessionId || uuidv4(),
      siteId,
      cwd: process.cwd() ?? ``,
      timestamps: this.timestamps,
      gitHash,
      commitTime,
      ci: process.env.CI || false,
      ciName: process.env.CI_NAME || `local`,
      versions: {
        nodejs: nodejsVersion,
        gatsby: gatsbyVersion,
        gatsbyCli: gatsbyCliVersion,
        sharp: sharpVersion,
        webpack: webpackVersion,
      },
      counts: {
        pages: pageCount,
        jpgs: jpgCount,
        pngs: pngCount,
        gifs: gifCount,
        other: otherCount,
      },
      memory,
      publicJsSize,
    }
  }

  markStart() {
    if (this.started) {
      reportError(
        `gatsby-plugin-benchmark-reporting: `,
        new Error(`Error: Should not call markStart() more than once`)
      )
      process.exit(1)
    }
    this.timestamps.benchmarkStart = performance.now()
    this.started = true
  }

  markDataPoint(name) {
    if (BENCHMARK_REPORTING_URL) {
      if (!(name in this.timestamps)) {
        reportError(
          `Attempted to record a timestamp with a name (\`${name}\`) that wasn't expected`
        )
        process.exit(1)
      }
    }
    this.timestamps[name] = performance.now()
  }

  async markEnd() {
    if (!this.timestamps.benchmarkStart) {
      reportError(
        `gatsby-plugin-benchmark-reporting:`,
        new Error(`Error: Should not call markEnd() before calling markStart()`)
      )
      process.exit(1)
    }
    this.timestamps.benchmarkEnd = performance.now()
    return this.flush()
  }

  async flush() {
    const data = this.getData()
    const json = JSON.stringify(data, null, 2)

    if (!BENCHMARK_REPORTING_URL) {
      reportInfo(`Gathered data: ` + json)
      reportInfo(`BENCHMARK_REPORTING_URL not set, not submitting data`)

      this.flushed = true
      return (this.flushing = Promise.resolve())
    }

    reportInfo(`Gathered data: ` + json)
    reportInfo(`Flushing benchmark data to remote server...`)

    let lastStatus = 0
    this.flushing = nodeFetch(`${BENCHMARK_REPORTING_URL}`, {
      method: `POST`,
      headers: {
        "content-type": `application/json`,
        // "user-agent": this.getUserAgent(),
      },
      body: json,
    }).then(res => {
      lastStatus = res.status
      if (lastStatus === 500) {
        reportInfo(`Got 500 response, waiting for text`)
        res.text().then(content => {
          reportError(
            `Response error`,
            new Error(`Server responded with a 500 error: ${content}`)
          )
          process.exit(1)
        })
      }
      this.flushed = true
      // Note: res.text returns a promise
      return res.text()
    })

    this.flushing.then(text =>
      reportInfo(`Server response: ${lastStatus}: ${text}`)
    )

    return this.flushing
  }
}

process.on(`exit`, () => {
  if (!benchMeta.flushed) {
    // This is probably already a non-zero exit as otherwise node should wait for the last promise to complete
    reportError(
      `gatsby-plugin-benchmark-reporting error`,
      new Error(
        `This is process.exit(); Benchmark plugin has not completely flushed yet`
      )
    )
    process.exit(1)
  }
})

const benchMeta = new BenchMeta()

async function onPreInit(api) {
  lastApi = api
  // This should be set in the gatsby-config of the site when enabling this plugin
  reportInfo(
    `gatsby-plugin-benchmark-reporting: Will post benchmark data to: ${BENCHMARK_REPORTING_URL ||
      `the CLI`}`
  )

  benchMeta.markStart()
  benchMeta.markDataPoint(`preInit`)
}

async function onPreBootstrap(api) {
  lastApi = api
  benchMeta.markDataPoint(`preBootstrap`)
}

async function onPreBuild(api) {
  lastApi = api
  benchMeta.markDataPoint(`preBuild`)
}

async function onPostBuild(api) {
  lastApi = api
  benchMeta.markDataPoint(`postBuild`)
  return benchMeta.markEnd()
}

module.exports = { onPreInit, onPreBootstrap, onPreBuild, onPostBuild }
<commit_msg>fix(gatsby-plugin-benchmark-reporting): Read gatsby-cli version from n_m (#21742)<commit_after>const { performance } = require(`perf_hooks`)

const { sync: glob } = require(`fast-glob`)
const nodeFetch = require(`node-fetch`)
const uuidv4 = require(`uuid/v4`)
const { execSync } = require(`child_process`)
const fs = require(`fs`)

const bootstrapTime = performance.now()

const BENCHMARK_REPORTING_URL =
  process.env.BENCHMARK_REPORTING_URL === `cli`
    ? undefined
    : process.env.BENCHMARK_REPORTING_URL

// Track the last received `api` because not all events in this plugin will receive one
let lastApi
function reportInfo(...args) {
  ;(lastApi ? lastApi.reporter : console).info(...args)
}
function reportError(...args) {
  ;(lastApi ? lastApi.reporter : console).error(...args)
}

function execToStr(cmd) {
  return String(
    execSync(cmd, {
      encoding: `utf8`,
    }) ?? ``
  ).trim()
}
function execToInt(cmd) {
  // `parseInt` can return `NaN` for unexpected args
  // `Number` can return undefined for unexpected args
  // `0 | x` (bitwise or) will always return 0 for unexpected args, or 32bit int
  return execToStr(cmd) | 0
}

class BenchMeta {
  constructor() {
    this.flushing = undefined // Promise of flushing if that has started
    this.flushed = false // Completed flushing?
    this.localTime = new Date().toISOString()
    this.timestamps = {
      // TODO: we should also have access to node's timing data and see how long it took before bootstrapping this script
      bootstrapTime, // Start of this file
      instantiationTime: performance.now(), // Instantiation time of this class
      benchmarkStart: 0, // Start of benchmark itself
      preInit: 0, // Gatsby onPreInit life cycle
      preBootstrap: 0, // Gatsby onPreBootstrap life cycle
      preBuild: 0, // Gatsby onPreBuild life cycle
      postBuild: 0, // Gatsby onPostBuild life cycle
      benchmarkEnd: 0, // End of benchmark itself
    }
    this.started = false
  }

  getData() {
    // Get memory usage snapshot first (just in case)
    const { rss, heapTotal, heapUsed, external } = process.memoryUsage()
    const memory = {
      rss: rss ?? 0,
      heapTotal: heapTotal ?? 0,
      heapUsed: heapUsed ?? 0,
      external: external ?? 0,
    }

    for (const key in this.timestamps) {
      this.timestamps[key] = Math.floor(this.timestamps[key])
    }

    // For the time being, our target benchmarks are part of the main repo
    // And we will want to know what version of the repo we're testing with
    // This won't work as intended when running a site not in our repo (!)
    const gitHash = execToStr(`git rev-parse HEAD`)
    // Git only supports UTC tz through env var, but the unix time stamp is UTC
    const unixStamp = execToStr(`git show --quiet --date=unix --format="%cd"`)
    const commitTime = new Date(parseInt(unixStamp, 10) * 1000).toISOString()

    const nodejsVersion = process.version

    // This assumes the benchmark is started explicitly from `node_modules/.bin/gatsby`, and not a global install
    // (This is what `gatsby --version` does too, ultimately)
    const gatsbyCliVersion = execToStr(`node_modules/.bin/gatsby --version`)

    const gatsbyVersion = require(`gatsby/package.json`).version

    const sharpVersion = fs.existsSync(`node_modules/sharp/package.json`)
      ? require(`sharp/package.json`).version
      : `none`

    const webpackVersion = require(`webpack/package.json`).version

    const publicJsSize = glob(`public/*.js`).reduce(
      (t, file) => t + fs.statSync(file).size,
      0
    )

    const jpgCount = execToInt(
      `find public .cache  -type f -iname "*.jpg" -or -iname "*.jpeg" | wc -l`
    )

    const pngCount = execToInt(
      `find public .cache  -type f -iname "*.png" | wc -l`
    )

    const gifCount = execToInt(
      `find public .cache  -type f -iname "*.gif" | wc -l`
    )

    const otherCount = execToInt(
      `find public .cache  -type f -iname "*.bmp" -or -iname "*.tif" -or -iname "*.webp" -or -iname "*.svg" | wc -l`
    )

    const pageCount = glob(`**/**.json`, {
      cwd: `./public/page-data`,
      nocase: true,
    }).length

    let siteId = ``
    try {
      // The tags ought to be a json string, but we try/catch it just in case it's not, or not a valid json string
      siteId =
        JSON.parse(process.env?.GATSBY_TELEMETRY_TAGS ?? `{}`)?.siteId ?? `` // Set by server
    } catch (e) {
      siteId = `error`
      reportInfo(
        `Suppressed an error trying to JSON.parse(GATSBY_TELEMETRY_TAGS): ${e}`
      )
    }

    return {
      time: this.localTime,
      sessionId: process.gatsbyTelemetrySessionId || uuidv4(),
      siteId,
      cwd: process.cwd() ?? ``,
      timestamps: this.timestamps,
      gitHash,
      commitTime,
      ci: process.env.CI || false,
      ciName: process.env.CI_NAME || `local`,
      versions: {
        nodejs: nodejsVersion,
        gatsby: gatsbyVersion,
        gatsbyCli: gatsbyCliVersion,
        sharp: sharpVersion,
        webpack: webpackVersion,
      },
      counts: {
        pages: pageCount,
        jpgs: jpgCount,
        pngs: pngCount,
        gifs: gifCount,
        other: otherCount,
      },
      memory,
      publicJsSize,
    }
  }

  markStart() {
    if (this.started) {
      reportError(
        `gatsby-plugin-benchmark-reporting: `,
        new Error(`Error: Should not call markStart() more than once`)
      )
      process.exit(1)
    }
    this.timestamps.benchmarkStart = performance.now()
    this.started = true
  }

  markDataPoint(name) {
    if (BENCHMARK_REPORTING_URL) {
      if (!(name in this.timestamps)) {
        reportError(
          `Attempted to record a timestamp with a name (\`${name}\`) that wasn't expected`
        )
        process.exit(1)
      }
    }
    this.timestamps[name] = performance.now()
  }

  async markEnd() {
    if (!this.timestamps.benchmarkStart) {
      reportError(
        `gatsby-plugin-benchmark-reporting:`,
        new Error(`Error: Should not call markEnd() before calling markStart()`)
      )
      process.exit(1)
    }
    this.timestamps.benchmarkEnd = performance.now()
    return this.flush()
  }

  async flush() {
    const data = this.getData()
    const json = JSON.stringify(data, null, 2)

    if (!BENCHMARK_REPORTING_URL) {
      reportInfo(`Gathered data: ` + json)
      reportInfo(`BENCHMARK_REPORTING_URL not set, not submitting data`)

      this.flushed = true
      return (this.flushing = Promise.resolve())
    }

    reportInfo(`Gathered data: ` + json)
    reportInfo(`Flushing benchmark data to remote server...`)

    let lastStatus = 0
    this.flushing = nodeFetch(`${BENCHMARK_REPORTING_URL}`, {
      method: `POST`,
      headers: {
        "content-type": `application/json`,
        // "user-agent": this.getUserAgent(),
      },
      body: json,
    }).then(res => {
      lastStatus = res.status
      if (lastStatus === 500) {
        reportInfo(`Got 500 response, waiting for text`)
        res.text().then(content => {
          reportError(
            `Response error`,
            new Error(`Server responded with a 500 error: ${content}`)
          )
          process.exit(1)
        })
      }
      this.flushed = true
      // Note: res.text returns a promise
      return res.text()
    })

    this.flushing.then(text =>
      reportInfo(`Server response: ${lastStatus}: ${text}`)
    )

    return this.flushing
  }
}

process.on(`exit`, () => {
  if (!benchMeta.flushed) {
    // This is probably already a non-zero exit as otherwise node should wait for the last promise to complete
    reportError(
      `gatsby-plugin-benchmark-reporting error`,
      new Error(
        `This is process.exit(); Benchmark plugin has not completely flushed yet`
      )
    )
    process.exit(1)
  }
})

const benchMeta = new BenchMeta()

async function onPreInit(api) {
  lastApi = api
  // This should be set in the gatsby-config of the site when enabling this plugin
  reportInfo(
    `gatsby-plugin-benchmark-reporting: Will post benchmark data to: ${BENCHMARK_REPORTING_URL ||
      `the CLI`}`
  )

  benchMeta.markStart()
  benchMeta.markDataPoint(`preInit`)
}

async function onPreBootstrap(api) {
  lastApi = api
  benchMeta.markDataPoint(`preBootstrap`)
}

async function onPreBuild(api) {
  lastApi = api
  benchMeta.markDataPoint(`preBuild`)
}

async function onPostBuild(api) {
  lastApi = api
  benchMeta.markDataPoint(`postBuild`)
  return benchMeta.markEnd()
}

module.exports = { onPreInit, onPreBootstrap, onPreBuild, onPostBuild }
<|endoftext|>