<commit_before>// Copyright 2002-2014, University of Colorado Boulder

/**
 * Eye dropper, with a button for dispensing whatever is in the dropper.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Property = require( 'AXON/Property' );
  var RoundMomentaryButton = require( 'SUN/buttons/RoundMomentaryButton' );
  var Shape = require( 'KITE/Shape' );

  // images
  var foregroundImage = require( 'image!SCENERY_PHET/eye_dropper_foreground.png' );
  var backgroundImage = require( 'image!SCENERY_PHET/eye_dropper_background.png' );

  // constants
  var DEBUG_ORIGIN = false; // if true, put a red dot at the dropper's origin (bottom center)

  // constants specific to the image files
  var TIP_WIDTH = 15;
  var TIP_HEIGHT = 4;
  var GLASS_WIDTH = 46;
  var GLASS_MAX_Y = -18; // relative to bottom center
  var GLASS_MIN_Y = -124; // relative to bottom center
  var BUTTON_CENTER_Y_OFFSET = 32; // y-offset of button's center in dropper image file

  /**
   * @param {Object} [options]
   * @constructor
   */
  function EyeDropperNode( options ) {

    options = _.extend( {
      cursor: 'pointer',
      dispensingProperty: new Property( false ), // is the dropper dispensing?
      enabledProperty: new Property( true ), // is the button enabled?
      emptyProperty: new Property( false ), // does the dropper appear to be empty?
      buttonTouchAreaDilation: 15, // dilation of the button's radius for touchArea
      fluidColor: 'yellow', // {Color|String} color of the fluid in the glass

      // Note: EyeDropperNode is not draggable and hence only registers its button with tandem.
      tandem: null
    }, options );

    var thisNode = this;

    // @public
    this.dispensingProperty = options.dispensingProperty;
    this.enabledProperty = options.enabledProperty;
    this.emptyProperty = options.emptyProperty;

    // @private fluid fills the glass portion of the dropper, shape is specific to the dropper image file
    this.fluidNode = new Path( new Shape()
      .moveTo( -TIP_WIDTH / 2, 0 )
      .lineTo( -TIP_WIDTH / 2, -TIP_HEIGHT )
      .lineTo( -GLASS_WIDTH / 2, GLASS_MAX_Y )
      .lineTo( -GLASS_WIDTH / 2, GLASS_MIN_Y )
      .lineTo( GLASS_WIDTH / 2, GLASS_MIN_Y )
      .lineTo( GLASS_WIDTH / 2, GLASS_MAX_Y )
      .lineTo( TIP_WIDTH / 2, -TIP_HEIGHT )
      .lineTo( TIP_WIDTH / 2, 0 )
      .close(), {
      fill: options.fluidColor
    } );

    // images, origin moved to bottom center
    var foreground = new Image( foregroundImage );
    var background = new Image( backgroundImage );
    foreground.x = -foreground.width / 2;
    foreground.y = -foreground.height;
    background.x = -background.width / 2;
    background.y = -background.height;

    // button, centered in the dropper's bulb
    // @public but only so it can be accessed for together instrumentation
    this.button = new RoundMomentaryButton( this.dispensingProperty, {
      baseColor: 'red',
      radius: 18,
      tandem: options.tandem && options.tandem.createTandem( 'button' )
    } );
    this.enabledProperty.link( function( enabled ) { thisNode.button.enabled = enabled; } );
    this.button.touchArea = Shape.circle( 0, 0, ( this.button.width / 2 ) + options.buttonTouchAreaDilation );
    this.button.centerX = foreground.centerX;
    this.button.centerY = foreground.top + BUTTON_CENTER_Y_OFFSET;

    // make the background visible only when the dropper is empty
    this.emptyProperty.link( function( empty ) {
      thisNode.fluidNode.visible = !empty;
      background.visible = empty;
    } );

    options.children = [ this.fluidNode, background, foreground, this.button ];

    // add a red dot at the origin
    if ( DEBUG_ORIGIN ) {
      options.children.push( new Circle( { radius: 3, fill: 'red' } ) );
    }

    Node.call( this, options );

    // Tandem support
    // Note that tandem.addInstance() is not called here since this node is not draggable or configurable
    // tandem was only passed in for the button
    // Subclasses are responsible for registering the EyeDropperNode if desired (see BLLDropperNode)
  }

  return inherit( Node, EyeDropperNode, {

    set dispensing( value ) { this.dispensingProperty = value; },

    get dispensing() { return this.dispensingProperty.value; },

    set enabled( value ) { this.enabledProperty = value; },

    get enabled() { return this.enabledProperty.value; },

    set empty( value ) { this.emptyProperty = value; },

    get empty() { return this.emptyProperty.value; },

    set fluidColor( value ) { this.fluidNode.fill = value; },

    get fluidColor() { return this.fluidNode.fill; }

  }, {

    // @static
    // You'll need these if you want to create fluid coming out of the tip, or put a label on the glass.
    TIP_WIDTH: TIP_WIDTH,
    TIP_HEIGHT: TIP_HEIGHT,
    GLASS_WIDTH: GLASS_WIDTH,
    GLASS_MIN_Y: GLASS_MIN_Y,
    GLASS_MAX_Y: GLASS_MAX_Y,
  } );
} );
<commit_msg>lint fix<commit_after>// Copyright 2002-2014, University of Colorado Boulder

/**
 * Eye dropper, with a button for dispensing whatever is in the dropper.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Property = require( 'AXON/Property' );
  var RoundMomentaryButton = require( 'SUN/buttons/RoundMomentaryButton' );
  var Shape = require( 'KITE/Shape' );

  // images
  var foregroundImage = require( 'image!SCENERY_PHET/eye_dropper_foreground.png' );
  var backgroundImage = require( 'image!SCENERY_PHET/eye_dropper_background.png' );

  // constants
  var DEBUG_ORIGIN = false; // if true, put a red dot at the dropper's origin (bottom center)

  // constants specific to the image files
  var TIP_WIDTH = 15;
  var TIP_HEIGHT = 4;
  var GLASS_WIDTH = 46;
  var GLASS_MAX_Y = -18; // relative to bottom center
  var GLASS_MIN_Y = -124; // relative to bottom center
  var BUTTON_CENTER_Y_OFFSET = 32; // y-offset of button's center in dropper image file

  /**
   * @param {Object} [options]
   * @constructor
   */
  function EyeDropperNode( options ) {

    options = _.extend( {
      cursor: 'pointer',
      dispensingProperty: new Property( false ), // is the dropper dispensing?
      enabledProperty: new Property( true ), // is the button enabled?
      emptyProperty: new Property( false ), // does the dropper appear to be empty?
      buttonTouchAreaDilation: 15, // dilation of the button's radius for touchArea
      fluidColor: 'yellow', // {Color|String} color of the fluid in the glass

      // Note: EyeDropperNode is not draggable and hence only registers its button with tandem.
      tandem: null
    }, options );

    var thisNode = this;

    // @public
    this.dispensingProperty = options.dispensingProperty;
    this.enabledProperty = options.enabledProperty;
    this.emptyProperty = options.emptyProperty;

    // @private fluid fills the glass portion of the dropper, shape is specific to the dropper image file
    this.fluidNode = new Path( new Shape()
      .moveTo( -TIP_WIDTH / 2, 0 )
      .lineTo( -TIP_WIDTH / 2, -TIP_HEIGHT )
      .lineTo( -GLASS_WIDTH / 2, GLASS_MAX_Y )
      .lineTo( -GLASS_WIDTH / 2, GLASS_MIN_Y )
      .lineTo( GLASS_WIDTH / 2, GLASS_MIN_Y )
      .lineTo( GLASS_WIDTH / 2, GLASS_MAX_Y )
      .lineTo( TIP_WIDTH / 2, -TIP_HEIGHT )
      .lineTo( TIP_WIDTH / 2, 0 )
      .close(), {
      fill: options.fluidColor
    } );

    // images, origin moved to bottom center
    var foreground = new Image( foregroundImage );
    var background = new Image( backgroundImage );
    foreground.x = -foreground.width / 2;
    foreground.y = -foreground.height;
    background.x = -background.width / 2;
    background.y = -background.height;

    // button, centered in the dropper's bulb
    // @public but only so it can be accessed for together instrumentation
    this.button = new RoundMomentaryButton( this.dispensingProperty, {
      baseColor: 'red',
      radius: 18,
      tandem: options.tandem && options.tandem.createTandem( 'button' )
    } );
    this.enabledProperty.link( function( enabled ) { thisNode.button.enabled = enabled; } );
    this.button.touchArea = Shape.circle( 0, 0, ( this.button.width / 2 ) + options.buttonTouchAreaDilation );
    this.button.centerX = foreground.centerX;
    this.button.centerY = foreground.top + BUTTON_CENTER_Y_OFFSET;

    // make the background visible only when the dropper is empty
    this.emptyProperty.link( function( empty ) {
      thisNode.fluidNode.visible = !empty;
      background.visible = empty;
    } );

    options.children = [ this.fluidNode, background, foreground, this.button ];

    // add a red dot at the origin
    if ( DEBUG_ORIGIN ) {
      options.children.push( new Circle( { radius: 3, fill: 'red' } ) );
    }

    Node.call( this, options );

    // Tandem support
    // Note that tandem.addInstance() is not called here since this node is not draggable or configurable
    // tandem was only passed in for the button
    // Subclasses are responsible for registering the EyeDropperNode if desired (see BLLDropperNode)
  }

  return inherit( Node, EyeDropperNode, {

    set dispensing( value ) { this.dispensingProperty = value; },

    get dispensing() { return this.dispensingProperty.value; },

    set enabled( value ) { this.enabledProperty = value; },

    get enabled() { return this.enabledProperty.value; },

    set empty( value ) { this.emptyProperty = value; },

    get empty() { return this.emptyProperty.value; },

    set fluidColor( value ) { this.fluidNode.fill = value; },

    get fluidColor() { return this.fluidNode.fill; }

  }, {

    // @static
    // You'll need these if you want to create fluid coming out of the tip, or put a label on the glass.
    TIP_WIDTH: TIP_WIDTH,
    TIP_HEIGHT: TIP_HEIGHT,
    GLASS_WIDTH: GLASS_WIDTH,
    GLASS_MIN_Y: GLASS_MIN_Y,
    GLASS_MAX_Y: GLASS_MAX_Y
  } );
} );
<|endoftext|>


<commit_before>describe("selectable model", function(){
  var Model = Backbone.Model.extend({
    initialize: function(){
      var selectable = new Backbone.Picky.Selectable();
      _.extend(this, selectable);
    }
  });

  describe("when selecting a model", function(){
    var model;

    beforeEach(function(){
      model = new Model();
      spyOn(model, "trigger").andCallThrough();

      model.select();
    });

    it("should be selected", function(){
      expect(model.selected).toBe(true);
    });

    it("should notify of selection", function(){
      expect(model.trigger).toHaveBeenCalledWith("selected");
    });
  });

});
<commit_msg>core selectable specs<commit_after>describe("selectable model", function(){
  var Model = Backbone.Model.extend({
    initialize: function(){
      var selectable = new Backbone.Picky.Selectable();
      _.extend(this, selectable);
    }
  });

  describe("when selecting a model", function(){
    var model;

    beforeEach(function(){
      model = new Model();
      spyOn(model, "trigger").andCallThrough();

      model.select();
    });

    it("should be selected", function(){
      expect(model.selected).toBe(true);
    });

    it("should notify of selection", function(){
      expect(model.trigger).toHaveBeenCalledWith("selected");
    });
  });

  describe("when selecting a model that is already selected", function(){
    var model;

    beforeEach(function(){
      model = new Model();
      model.select();

      spyOn(model, "trigger").andCallThrough();
      model.select();
    });

    it("should still be selected", function(){
      expect(model.selected).toBe(true);
    });

    it("should not notify of selection", function(){
      expect(model.trigger).not.toHaveBeenCalledWith("selected");
    });
  });

  describe("when deselecting a model that has been selected", function(){
    var model;

    beforeEach(function(){
      model = new Model();
      model.select();

      spyOn(model, "trigger").andCallThrough();
      model.deselect();
    });

    it("should not be selected", function(){
      expect(model.selected).toBe(false);
    });

    it("should notify of deselection", function(){
      expect(model.trigger).toHaveBeenCalledWith("deselected");
    });
  });

  describe("when deselecting a model that is not selected", function(){
    var model;

    beforeEach(function(){
      model = new Model();

      spyOn(model, "trigger").andCallThrough();
      model.deselect();
    });

    it("should not be selected", function(){
      expect(model.selected).toBeFalsy();
    });

    it("should not notify of deselection", function(){
      expect(model.trigger).not.toHaveBeenCalledWith("deselected");
    });
  });

});
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';

import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native';

import { PageContentModal } from './PageContentModal';

import { COMPONENT_HEIGHT } from '../../globalStyles';

export default class GenericChooseModal extends React.PureComponent {
  keyExtractor = (item, index) => {
    const { field } = this.props;
    const content = field && item ? item[field] : item;
    return `${content}${index}`;
  };

  renderRow = ({ item, index }) => {
    const { onPress, field } = this.props;
    const { row, text } = localStyles;

    const rowStyle = index === 0 ? { ...row, backgroundColor: '#E95C30' } : row;
    const textStyle = index === 0 ? { ...text, color: '#FFF' } : text;
    return (
      <TouchableOpacity onPress={() => onPress({ item, index, field })}>
        <View style={rowStyle}>
          <Text style={textStyle}>{field ? item[field] : item}</Text>
        </View>
      </TouchableOpacity>
    );
  };

  render() {
    const { data, isOpen, title } = this.props;
    if (!data) return null;
    return (
      <PageContentModal isOpen={isOpen} style={{ alignItems: 'center' }} title={title}>
        <FlatList
          data={data}
          renderItem={this.renderRow}
          style={localStyles.list}
          keyExtractor={this.keyExtractor}
        />
      </PageContentModal>
    );
  }
}

const localStyles = StyleSheet.create({
  row: {
    flexDirection: 'row',
    paddingLeft: 30,
    justifyContent: 'flex-start',
    alignItems: 'center',
    backgroundColor: '#FFFFFF',
    height: COMPONENT_HEIGHT,
  },
  list: {
    marginTop: 10,
    minWidth: '70%',
  },
  text: {
    fontSize: 20,
    marginLeft: 20,
  },
});

GenericChooseModal.defaultProps = {
  field: PropTypes.null,
};

GenericChooseModal.propTypes = {
  field: PropTypes.string,
  onPress: PropTypes.func.isRequired,
  data: PropTypes.array.isRequired,
  title: PropTypes.string.isRequired,
  isOpen: PropTypes.bool.isRequired,
};
<commit_msg>Add update to GenericChooseModal to cover the full screen<commit_after>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';

import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native';

import { PageContentModal } from './PageContentModal';

import { COMPONENT_HEIGHT } from '../../globalStyles';

export default class GenericChooseModal extends React.PureComponent {
  keyExtractor = (item, index) => {
    const { field } = this.props;
    const content = field && item ? item[field] : item;
    return `${content}${index}`;
  };

  renderRow = ({ item, index }) => {
    const { onPress, field } = this.props;
    const { row, text } = localStyles;

    const rowStyle = index === 0 ? { ...row, backgroundColor: '#E95C30' } : row;
    const textStyle = index === 0 ? { ...text, color: '#FFF' } : text;
    return (
      <TouchableOpacity onPress={() => onPress({ item, index, field })}>
        <View style={rowStyle}>
          <Text style={textStyle}>{field ? item[field] : item}</Text>
        </View>
      </TouchableOpacity>
    );
  };

  render() {
    const { data, isOpen, title } = this.props;
    if (!data) return null;
    return (
      <PageContentModal isOpen={isOpen} style={{ alignItems: 'center' }} title={title} coverScreen>
        <FlatList
          data={data}
          renderItem={this.renderRow}
          style={localStyles.list}
          keyExtractor={this.keyExtractor}
        />
      </PageContentModal>
    );
  }
}

const localStyles = StyleSheet.create({
  row: {
    flexDirection: 'row',
    paddingLeft: 30,
    justifyContent: 'flex-start',
    alignItems: 'center',
    backgroundColor: '#FFFFFF',
    height: COMPONENT_HEIGHT,
  },
  list: {
    marginTop: 10,
    minWidth: '70%',
  },
  text: {
    fontSize: 20,
    marginLeft: 20,
  },
});

GenericChooseModal.defaultProps = {
  field: PropTypes.null,
};

GenericChooseModal.propTypes = {
  field: PropTypes.string,
  onPress: PropTypes.func.isRequired,
  data: PropTypes.array.isRequired,
  title: PropTypes.string.isRequired,
  isOpen: PropTypes.bool.isRequired,
};
<|endoftext|>


<commit_before>define(['jquery', 'underscore', 'backbone',
	'text!./app/views/templates/main.html',
	'text!./app/views/templates/item-listing.html',
	'text!./app/views/templates/item-listing-empty.html',
	'models', 'utils', 'facebook'
], function($, _, Backbone, mainTemplate, itemListingTemplate, itemListingEmptyTemplate, Models, Utils) {
	var Views = {};
	Views.Main = Backbone.View.extend({
		initialize: function() {
			this.$el.html(_.template(mainTemplate, {}));
			this._setUpCollections();
		},

		_setUpCollections: function() {
			var userId = this.options.userId;
			yourCollection = new Models.Wants([], {
				url: '/service/me/wants'
			});
			friendCollection = new Models.Wants([], {
				url: '/service/me/friendsWants'
			});
			featuredCollection = new Models.Wants([], {
				url: '/service/me/recommended'
			});
			publicCollection = new Models.Wants([], {
				url: '/service/public/wants' // I don't think this exists, but hey.
			});

			yourView = new Views.ListingView({
				collection: yourCollection,
				el: document.getElementById('you-section'),
				id: "you",
				'userId': userId
			});

			friendView = new Views.ListingView({
				collection: friendCollection,
				el: document.getElementById('friend-section'),
				id: "friend",
				'userId': userId
			});

			featuredView = new Views.ListingView({
				collection: featuredCollection,
				el: document.getElementById('featured-section'),
				id: "featured",
				'userId': userId
			});

			publicView = new Views.ListingView({
				collection: publicCollection,
				el: document.getElementById('public-section'),
				id: "public",
				'userId': userId
			});
		}
	});

	Views.ItemView = Backbone.View.extend({
		template: _.template(itemListingTemplate),

		events: {
			"click button#btn-pledge" : 'pledgeClicked',
			"click button#btn-unpledge" : 'unpledgeClicked'
		},

		initialize: function() {
			this.listenTo(this.model, "change", this.render);
			this.id = "item-" + this.model.attributes.id;
		},

		render: function() {
			this.$el.html(this.template(this.model.attributes));
			return this;
		},

		pledgeClicked: function() {
			$.ajax({
				url: '/service/me/want',
				dataType: 'json',
				type: 'POST',
				data: {
					product_listing_id: this.model.attributes.id
				}
			});
		},

		unpledgeClicked: function() {
			$.ajax({
				url: '/service/me/dontWant',
				dataType: 'json',
				type: 'POST',
				data: {
					product_listing_id: this.model.attributes.id
				}
			});
		}
	});

	Views.ListingView = Backbone.View.extend({
		initialize: function() {
			this.listenTo(this.collection, 'add', this.collectionAdded);
			this.listenTo(this.collection, 'change', this.collectionChanged);
			this.listenTo(this.collection, 'remove', this.collectionRemoved);

			this.childViews = [];
		},

		render: function() {
			var fragment = document.createDocumentFragment();

			if (this.childViews.length > 0) {
				_(this.childViews).each(function(currentView) {
					fragment.appendChild(currentView.render().el);
				});
			} else {
				$(itemListingEmptyTemplate).appendTo(fragment);
			}

			this.$('.item-listing').html(fragment);
			return this;
		},

		collectionAdded: function(item) {
			var that = this;
			item.set({userId: that.options.userId});
			this._addViewForModel(item);
			this.render();
		},

		collectionRemoved: function(item) {
			this.childViews = this.childViews.filter(function(view) {
				return view.model.id !== item.id;
			});
			this.render();
		},

		collectionChanged: function() {
			this.childViews = [];
			this._addAllModels();
		},

		_addViewForModel: function(item) {
			this.childViews.push(new Views.ItemView({
				model: item
			}));
		},

		_addAllModels: function() {
			var that = this;
			this.collection.each(function(item) {
				item.set({userId: that.options.userId});
				that._addViewForModel(item);
			});
			this.render();
		}
	});

	return Views;
});<commit_msg>Don't update view if we're just setting the user ID.<commit_after>define(['jquery', 'underscore', 'backbone',
	'text!./app/views/templates/main.html',
	'text!./app/views/templates/item-listing.html',
	'text!./app/views/templates/item-listing-empty.html',
	'models', 'utils', 'facebook'
], function($, _, Backbone, mainTemplate, itemListingTemplate, itemListingEmptyTemplate, Models, Utils) {
	var Views = {};
	Views.Main = Backbone.View.extend({
		initialize: function() {
			this.$el.html(_.template(mainTemplate, {}));
			this._setUpCollections();
		},

		_setUpCollections: function() {
			var userId = this.options.userId;
			yourCollection = new Models.Wants([], {
				url: '/service/me/wants'
			});
			friendCollection = new Models.Wants([], {
				url: '/service/me/friendsWants'
			});
			featuredCollection = new Models.Wants([], {
				url: '/service/me/recommended'
			});
			publicCollection = new Models.Wants([], {
				url: '/service/public/wants' // I don't think this exists, but hey.
			});

			yourView = new Views.ListingView({
				collection: yourCollection,
				el: document.getElementById('you-section'),
				id: "you",
				'userId': userId
			});

			friendView = new Views.ListingView({
				collection: friendCollection,
				el: document.getElementById('friend-section'),
				id: "friend",
				'userId': userId
			});

			featuredView = new Views.ListingView({
				collection: featuredCollection,
				el: document.getElementById('featured-section'),
				id: "featured",
				'userId': userId
			});

			publicView = new Views.ListingView({
				collection: publicCollection,
				el: document.getElementById('public-section'),
				id: "public",
				'userId': userId
			});
		}
	});

	Views.ItemView = Backbone.View.extend({
		template: _.template(itemListingTemplate),

		events: {
			"click button#btn-pledge" : 'pledgeClicked',
			"click button#btn-unpledge" : 'unpledgeClicked'
		},

		initialize: function() {
			this.listenTo(this.model, "change", this.render);
			this.id = "item-" + this.model.attributes.id;
		},

		render: function() {
			this.$el.html(this.template(this.model.attributes));
			return this;
		},

		pledgeClicked: function() {
			$.ajax({
				url: '/service/me/want',
				dataType: 'json',
				type: 'POST',
				data: {
					product_listing_id: this.model.attributes.id
				}
			});
		},

		unpledgeClicked: function() {
			$.ajax({
				url: '/service/me/dontWant',
				dataType: 'json',
				type: 'POST',
				data: {
					product_listing_id: this.model.attributes.id
				}
			});
		}
	});

	Views.ListingView = Backbone.View.extend({
		initialize: function() {
			this.listenTo(this.collection, 'add', this.collectionAdded);
			this.listenTo(this.collection, 'change', this.collectionChanged);
			this.listenTo(this.collection, 'remove', this.collectionRemoved);

			this.childViews = [];
		},

		render: function() {
			var fragment = document.createDocumentFragment();

			if (this.childViews.length > 0) {
				_(this.childViews).each(function(currentView) {
					fragment.appendChild(currentView.render().el);
				});
			} else {
				$(itemListingEmptyTemplate).appendTo(fragment);
			}

			this.$('.item-listing').html(fragment);
			return this;
		},

		collectionAdded: function(item) {
			var that = this;
			item.set({userId: that.options.userId});
			this._addViewForModel(item);
			this.render();
		},

		collectionRemoved: function(item) {
			this.childViews = this.childViews.filter(function(view) {
				return view.model.id !== item.id;
			});
			this.render();
		},

		collectionChanged: function(item) {
			if (!item.changed.userId) {
				this.childViews = [];
				this._addAllModels();
			}
		},

		_addViewForModel: function(item) {
			this.childViews.push(new Views.ItemView({
				model: item
			}));
		},

		_addAllModels: function() {
			var that = this;
			this.collection.each(function(item) {
				item.set({userId: that.options.userId});
				that._addViewForModel(item);
			});
			this.render();
		}
	});

	return Views;
});<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var path = require('path');
var chokidar = require('chokidar');
var md5File = require('md5-file');
var app = require('express')();
var multer = require('multer');
var authCheck = require('../util').authCheck;
var bundles = require('../bundles');
var Replicant = require('../replicant');
var log = require('../logger')('nodecg/lib/uploads');
var UploadedFile = require('./UploadedFile');
var UPLOADS_ROOT = path.resolve(__dirname, '../../uploads/');
var uploadReplicants = [];

// Create UPLOADS_ROOT folder if it does not exist.
/* istanbul ignore next: Simple directory creation. */
if (!fs.existsSync(UPLOADS_ROOT)) {
	fs.mkdirSync(UPLOADS_ROOT);
}

bundles.all().forEach(function (bundle) {
	if (bundle.uploads && bundle.uploads.enabled) {
		/* istanbul ignore next: Simple directory creation. */
		var bundleUploadsPath = path.join(UPLOADS_ROOT, bundle.name);
		if (!fs.existsSync(bundleUploadsPath)) {
			fs.mkdirSync(bundleUploadsPath);
		}

		uploadReplicants[bundle.name] = new Replicant('uploads', bundle.name, {
			defaultValue: [],
			persistent: false
		});
	}
});

var watcher = chokidar.watch(path.join(UPLOADS_ROOT, '*/*'), {
	ignored: /[\/\\]\./
});

watcher.on('add', function fileAdded(filepath) {
	md5File(filepath, function (err, sum) {
		if (err) {
			log.error(err);
			return;
		}

		var uploadedFile = new UploadedFile(filepath, sum);
		uploadReplicants[uploadedFile.bundleName].value.push(uploadedFile);
	});
});

watcher.on('change', function fileChanged(filepath) {
	md5File(filepath, function (err, sum) {
		if (err) {
			log.error(err);
			return;
		}

		var uploadedFile = new UploadedFile(filepath, sum);
		var index;
		uploadReplicants[uploadedFile.bundleName].value.some(function (uf, i) {
			if (uf.url === uploadedFile.url) {
				index = i;
				return true;
			}

			return false;
		});

		if (index) {
			uploadReplicants[uploadedFile.bundleName].value.splice(index, 1, uploadedFile);
		} else {
			uploadReplicants[uploadedFile.bundleName].value.push(uploadedFile);
		}
	});
});

watcher.on('unlink', function fileUnlinked(filepath) {
	var deletedFile = new UploadedFile(filepath);
	uploadReplicants[deletedFile.bundleName].value.some(function (uploadedFile, index) {
		if (uploadedFile.url === deletedFile.url) {
			uploadReplicants[deletedFile.bundleName].value.splice(index, 1);
			log.debug('"%s" was deleted', deletedFile.url);
			return true;
		}

		return false;
	});
});

watcher.on('error', function (e) {
	log.error(e.stack);
});

var upload = multer({
	storage: multer.diskStorage({
		destination: UPLOADS_ROOT,
		filename: function (req, file, cb) {
			cb(null, req.params.bundleName + '/' + req.params.filePath);
		}
	})
});

// Retrieving existing files
app.get('/uploads/:bundleName/:filePath', function (req, res) {
	var fullPath = path.join(UPLOADS_ROOT, req.params.bundleName, req.params.filePath);
	fs.exists(fullPath, function (exists) {
		if (!exists) {
			res.status(404).send('File not found:' + req.path);
			return;
		}

		res.sendFile(fullPath);
	});
});

// Uploading new files
app.post('/uploads/:bundleName/:filePath',

	// Check if the user is authorized
	authCheck,

	// Then receive the files they are sending, up to a max of 64
	upload.array('file', 64),

	// Then send a response.
	function (req, res) {
		if (req.files) {
			res.status(200).send('Success');
		} else {
			res.status(400).send('Bad Request');
		}
	}
);

// Deleting existing files
app.delete('/uploads/:bundleName/:filename', authCheck, function (req, res) {
	var bundleName = req.params.bundleName;
	var filename = req.params.filename;

	var fullpath = path.join(UPLOADS_ROOT, bundleName, filename);
	fs.exists(fullpath, function (exists) {
		if (!exists) {
			res.status(410).send('The file to delete does not exist: ' + filename);
			return;
		}

		fs.unlink(fullpath, function (err) {
			if (err) {
				log.error('Failed to delete file:', err.message);
				res.status(500).send('Failed to delete file: ' + filename);
				return;
			}

			res.sendStatus(200);
		});
	});
});

module.exports = app;
<commit_msg>fix(uploads): prevent crash related to uninstalled bundles<commit_after>'use strict';

var fs = require('fs');
var path = require('path');
var chokidar = require('chokidar');
var md5File = require('md5-file');
var app = require('express')();
var multer = require('multer');
var authCheck = require('../util').authCheck;
var bundles = require('../bundles');
var Replicant = require('../replicant');
var log = require('../logger')('nodecg/lib/uploads');
var UploadedFile = require('./UploadedFile');
var UPLOADS_ROOT = path.resolve(__dirname, '../../uploads/');
var uploadReplicants = [];

// Create UPLOADS_ROOT folder if it does not exist.
/* istanbul ignore next: Simple directory creation. */
if (!fs.existsSync(UPLOADS_ROOT)) {
	fs.mkdirSync(UPLOADS_ROOT);
}

var foldersToWatch = [];

bundles.all().forEach(function (bundle) {
	if (bundle.uploads && bundle.uploads.enabled) {
		/* istanbul ignore next: Simple directory creation. */
		var bundleUploadsPath = path.join(UPLOADS_ROOT, bundle.name);
		if (!fs.existsSync(bundleUploadsPath)) {
			fs.mkdirSync(bundleUploadsPath);
		}

		uploadReplicants[bundle.name] = new Replicant('uploads', bundle.name, {
			defaultValue: [],
			persistent: false
		});

		foldersToWatch.push(bundleUploadsPath + '/**/*');
	}
});

var watcher = chokidar.watch(foldersToWatch, {
	ignored: /[\/\\]\./
});

watcher.on('add', function fileAdded(filepath) {
	md5File(filepath, function (err, sum) {
		if (err) {
			log.error(err);
			return;
		}

		var uploadedFile = new UploadedFile(filepath, sum);
		uploadReplicants[uploadedFile.bundleName].value.push(uploadedFile);
	});
});

watcher.on('change', function fileChanged(filepath) {
	md5File(filepath, function (err, sum) {
		if (err) {
			log.error(err);
			return;
		}

		var uploadedFile = new UploadedFile(filepath, sum);
		var index;
		uploadReplicants[uploadedFile.bundleName].value.some(function (uf, i) {
			if (uf.url === uploadedFile.url) {
				index = i;
				return true;
			}

			return false;
		});

		if (index) {
			uploadReplicants[uploadedFile.bundleName].value.splice(index, 1, uploadedFile);
		} else {
			uploadReplicants[uploadedFile.bundleName].value.push(uploadedFile);
		}
	});
});

watcher.on('unlink', function fileUnlinked(filepath) {
	var deletedFile = new UploadedFile(filepath);
	uploadReplicants[deletedFile.bundleName].value.some(function (uploadedFile, index) {
		if (uploadedFile.url === deletedFile.url) {
			uploadReplicants[deletedFile.bundleName].value.splice(index, 1);
			log.debug('"%s" was deleted', deletedFile.url);
			return true;
		}

		return false;
	});
});

watcher.on('error', function (e) {
	log.error(e.stack);
});

var upload = multer({
	storage: multer.diskStorage({
		destination: UPLOADS_ROOT,
		filename: function (req, file, cb) {
			cb(null, req.params.bundleName + '/' + req.params.filePath);
		}
	})
});

// Retrieving existing files
app.get('/uploads/:bundleName/:filePath', function (req, res) {
	var fullPath = path.join(UPLOADS_ROOT, req.params.bundleName, req.params.filePath);
	fs.exists(fullPath, function (exists) {
		if (!exists) {
			res.status(404).send('File not found:' + req.path);
			return;
		}

		res.sendFile(fullPath);
	});
});

// Uploading new files
app.post('/uploads/:bundleName/:filePath',

	// Check if the user is authorized
	authCheck,

	// Then receive the files they are sending, up to a max of 64
	upload.array('file', 64),

	// Then send a response.
	function (req, res) {
		if (req.files) {
			res.status(200).send('Success');
		} else {
			res.status(400).send('Bad Request');
		}
	}
);

// Deleting existing files
app.delete('/uploads/:bundleName/:filename', authCheck, function (req, res) {
	var bundleName = req.params.bundleName;
	var filename = req.params.filename;

	var fullpath = path.join(UPLOADS_ROOT, bundleName, filename);
	fs.exists(fullpath, function (exists) {
		if (!exists) {
			res.status(410).send('The file to delete does not exist: ' + filename);
			return;
		}

		fs.unlink(fullpath, function (err) {
			if (err) {
				log.error('Failed to delete file:', err.message);
				res.status(500).send('Failed to delete file: ' + filename);
				return;
			}

			res.sendStatus(200);
		});
	});
});

module.exports = app;
<|endoftext|>


<commit_before>//
// See: https://github.com/funjs/book-source/blob/master/chapter02.js
//

require("./importAll.js").importAll("chapter01.js");
var _ = require("underscore");

function cat() {
  var head = _.first(arguments);
  if (existy(head)) {
    return head.concat.apply(head, _.rest(arguments));
  } else {
    return [];
  }
}

exports.cat = cat;
<commit_msg>[chapter 2] Added construct<commit_after>//
// See: https://github.com/funjs/book-source/blob/master/chapter02.js
//

require("./importAll.js").importAll("chapter01.js");
var _ = require("underscore");

function cat() {
  var head = _.first(arguments);
  if (existy(head)) {
    return head.concat.apply(head, _.rest(arguments));
  } else {
    return [];
  }
}

function construct(head, tail) {
  return cat([head], _.toArray(tail));
}

exports.cat = cat;
exports.construct = construct;
<|endoftext|>


<commit_before>jsio('from common.javascript import Class, bind');

jsio('import browser.css as css');
jsio('import browser.events as events');
jsio('import browser.dimensions as dimensions');
jsio('import browser.dom as dom');

jsio('import browser.resizeManager');
// jsio('import browser.panelManager'); jsio import bug - am using gPanelManager for now
jsio('import browser.UIComponent');
jsio('import browser.Label');
jsio('import browser.ItemView');
jsio('import browser.panels.ListPanel');

var logger = logging.getLogger(jsio.__path);

css.loadStyles(jsio.__path);

exports = Class(browser.UIComponent, function(supr) {

	var margin = { top: 10, bottom: 50 };
	var padding = 3;
	var handleWidth = 10;
	var minHeight = 100;
	var width = 170;
	var marginLeft = 10;

	this.init = function() {
		supr(this, 'init');
		this._itemViewClickCallback = bind(this, '_onItemViewClick');
		this._labelListPanel = new browser.panels.ListPanel(this, 'Drawer');
		this._labelListPanel._contentMargin = 0;
	}

	this.createContent = function() {
		this.addClassName('Drawer');
		this._labelListPanel.appendTo(this._element);
		this._labelListPanel.addClassName('labelListPanel')
 		this._labelListPanel.subscribe('ItemSelected', bind(this, '_onLabelSelected'));

		var addLabelLink = dom.create({ parent: this._labelListPanel.getElement(),
			className: 'addLabelLink', html: '+ add label' });
		events.add(addLabelLink, 'click', gCreateLabelFn);

		this.layout();
	}

	this.focusLeftmost = function() {
		this._labelListPanel.focus();
	}
	this.focus = function() {
		if (this._labelViewPanel) {
			this._labelViewPanel.focus();
		} else {
			this._labelListPanel.focus();
		}
	}

	this.focusPanel = function() {
		if (!this._labelViewPanel) { return; }
		this.addClassName('labelListOpen');
		this._labelViewPanel.focus();
	}
	this.removePanel = function(panel) {
		if (!panel || panel != this._labelViewPanel) { return; }
		this.removeClassName('labelListOpen');
		this._labelViewPanel.unsubscribe('ItemSelected', this._itemViewClickCallback);
		dom.remove(this._labelViewPanel.getElement());
		this._labelViewPanel = null;
		browser.resizeManager.fireResize();
		this.focus();
	}

	this.layout = function() {
		var size = dimensions.getSize(window);
		var height = Math.max(minHeight, size.height - margin.top - margin.bottom);
		var panelWidth = 0;
		this._labelListPanel.layout({ height: height, width: width, top: margin.top, left: marginLeft });
		if (this._labelViewPanel) {
			if (this._labelViewPanel.isMinimized()) {
				panelWidth = 30;
				this._labelViewPanel.layout({ height: height });
			} else {
				panelWidth = 320;
				this._labelViewPanel.layout({ width: panelWidth + 2, height: height,
					top: margin.top, left: width + marginLeft + 6 });
			}
		}
		return { width: width + panelWidth + marginLeft, height: height, top: margin.top };
	}

	this.addLabels = function(labels) {
		for (var i=0, label; label = labels[i]; i++) {
			var item = new browser.Label(label);
			this._labelListPanel.addItem(item);
		}
	}

	this._onLabelSelected = function(label) {
		if (this._labelViewPanel && this._labelViewPanel.getLabel() == label) { return; }

		this.removePanel(this._labelViewPanel);

		gClient.getItemsForLabel(label, bind(this, '_onLabelItemsReceived'));
		this._labelViewPanel = new browser.panels.ListPanel(this, label);
		this._labelViewPanel.appendTo(this._element);
		this._labelViewPanel.addClassName('labelViewPanel');
		this._labelViewPanel.subscribe('ItemSelected', this._itemViewClickCallback);
		this._labelViewPanel.close = bind(this, function(){
			if (this._labelViewPanel.isMinimized()) {
				this._labelViewPanel.maximize();
			} else {
				this._labelViewPanel.minimize();
			}
			this._labelViewPanel.focus();
			browser.resizeManager.fireResize();
		});

		this.focusPanel();
		browser.resizeManager.fireResize();
	}

	this._onLabelItemsReceived = function(label, items) {
		for (var i=0, item; item = items[i]; i++) {
			var itemView = new browser.ItemView(item, item.getType(), 'list');
			this._labelViewPanel.addItem(itemView);
		}
	}

	this._onItemViewClick = function(itemView) {
		gPanelManager.showItem(itemView.getItem());
	}
})
<commit_msg>No longer using the browser.panelManager singleton instance, but rather the global gPanelManager<commit_after>jsio('from common.javascript import Class, bind');

jsio('import browser.css as css');
jsio('import browser.events as events');
jsio('import browser.dimensions as dimensions');
jsio('import browser.dom as dom');

jsio('import browser.resizeManager');
jsio('import browser.UIComponent');
jsio('import browser.Label');
jsio('import browser.ItemView');
jsio('import browser.panels.ListPanel');

var logger = logging.getLogger(jsio.__path);

css.loadStyles(jsio.__path);

exports = Class(browser.UIComponent, function(supr) {

	var margin = { top: 10, bottom: 50 };
	var padding = 3;
	var handleWidth = 10;
	var minHeight = 100;
	var width = 170;
	var marginLeft = 10;

	this.init = function() {
		supr(this, 'init');
		this._itemViewClickCallback = bind(this, '_onItemViewClick');
		this._labelListPanel = new browser.panels.ListPanel(this, 'Drawer');
		this._labelListPanel._contentMargin = 0;
	}

	this.createContent = function() {
		this.addClassName('Drawer');
		this._labelListPanel.appendTo(this._element);
		this._labelListPanel.addClassName('labelListPanel')
 		this._labelListPanel.subscribe('ItemSelected', bind(this, '_onLabelSelected'));

		var addLabelLink = dom.create({ parent: this._labelListPanel.getElement(),
			className: 'addLabelLink', html: '+ add label' });
		events.add(addLabelLink, 'click', gCreateLabelFn);

		this.layout();
	}

	this.focusLeftmost = function() {
		this._labelListPanel.focus();
	}
	this.focus = function() {
		if (this._labelViewPanel) {
			this._labelViewPanel.focus();
		} else {
			this._labelListPanel.focus();
		}
	}

	this.focusPanel = function() {
		if (!this._labelViewPanel) { return; }
		this.addClassName('labelListOpen');
		this._labelViewPanel.focus();
	}
	this.removePanel = function(panel) {
		if (!panel || panel != this._labelViewPanel) { return; }
		this.removeClassName('labelListOpen');
		this._labelViewPanel.unsubscribe('ItemSelected', this._itemViewClickCallback);
		dom.remove(this._labelViewPanel.getElement());
		this._labelViewPanel = null;
		browser.resizeManager.fireResize();
		this.focus();
	}

	this.layout = function() {
		var size = dimensions.getSize(window);
		var height = Math.max(minHeight, size.height - margin.top - margin.bottom);
		var panelWidth = 0;
		this._labelListPanel.layout({ height: height, width: width, top: margin.top, left: marginLeft });
		if (this._labelViewPanel) {
			if (this._labelViewPanel.isMinimized()) {
				panelWidth = 30;
				this._labelViewPanel.layout({ height: height });
			} else {
				panelWidth = 320;
				this._labelViewPanel.layout({ width: panelWidth + 2, height: height,
					top: margin.top, left: width + marginLeft + 6 });
			}
		}
		return { width: width + panelWidth + marginLeft, height: height, top: margin.top };
	}

	this.addLabels = function(labels) {
		for (var i=0, label; label = labels[i]; i++) {
			var item = new browser.Label(label);
			this._labelListPanel.addItem(item);
		}
	}

	this._onLabelSelected = function(label) {
		if (this._labelViewPanel && this._labelViewPanel.getLabel() == label) { return; }

		this.removePanel(this._labelViewPanel);

		gClient.getItemsForLabel(label, bind(this, '_onLabelItemsReceived'));
		this._labelViewPanel = new browser.panels.ListPanel(this, label);
		this._labelViewPanel.appendTo(this._element);
		this._labelViewPanel.addClassName('labelViewPanel');
		this._labelViewPanel.subscribe('ItemSelected', this._itemViewClickCallback);
		this._labelViewPanel.close = bind(this, function(){
			if (this._labelViewPanel.isMinimized()) {
				this._labelViewPanel.maximize();
			} else {
				this._labelViewPanel.minimize();
			}
			this._labelViewPanel.focus();
			browser.resizeManager.fireResize();
		});

		this.focusPanel();
		browser.resizeManager.fireResize();
	}

	this._onLabelItemsReceived = function(label, items) {
		for (var i=0, item; item = items[i]; i++) {
			var itemView = new browser.ItemView(item, item.getType(), 'list');
			this._labelViewPanel.addItem(itemView);
		}
	}

	this._onItemViewClick = function(itemView) {
		gPanelManager.showItem(itemView.getItem());
	}
})
<|endoftext|>


<commit_before>const shell = require('shelljs');

// Store installation start date.
const silent = process.env.npm_config_debug !== 'true';
const installationStartDate = new Date();
const watcher = (label, cmd, withSuccess = true) => {
  if (label.length > 0) {
    shell.echo(label);
  }

  const data = shell.exec(cmd, {
    silent
  });

  if (data.stderr && data.code !== 0) {
    console.error(data.stderr);
    process.exit(1);
  }

  if (label.length > 0 && withSuccess) {
    shell.echo('✅  Success');
    shell.echo('');
  }
};

shell.echo('');
shell.echo('🕓  The setup process can take few minutes.');
shell.echo('');

// Remove existing binary.
shell.rm('-f', '/usr/local/bin/strapi.js');

shell.cd('packages/strapi-utils');
watcher('📦  Linking strapi-utils...', 'npm link');

shell.cd('../strapi-generate');
watcher('', 'npm install ../strapi-utils');
watcher('📦  Linking strapi-generate...', 'npm link');

shell.cd('../strapi-generate-api');
watcher('📦  Linking strapi-generate-api...', 'npm link');

shell.cd('../strapi-helper-plugin');
watcher('📦  Linking strapi-helper-plugin...', 'npm link');

shell.cd('../strapi-admin');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
watcher('', 'npm install ../strapi-utils --no-optional');
shell.rm('-f', 'package-lock.json');

// Without these line Travis failed.
if (shell.test('-e', 'admin/src/config/plugins.json') === false) {
  shell.config.silent = silent;
  shell.cd('admin/src/config/');
  shell.ShellString('[]').to('plugins.json');
  shell.cd('../../../');
}

watcher('📦  Linking strapi-admin', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-generate-admin');
watcher('', 'npm install ../strapi-admin');
watcher('📦  Linking strapi-generate-admin...', 'npm link');

shell.cd('../strapi-generate-new');
watcher('', 'npm install ../strapi-utils');
watcher('📦  Linking strapi-generate-new', 'npm link');

shell.cd('../strapi-mongoose');
watcher('', 'npm install ../strapi-utils');
watcher('📦  Linking strapi-mongoose...', 'npm link');

shell.cd('../strapi');
watcher('', 'npm install ../strapi-generate ../strapi-generate-admin ../strapi-generate-api ../strapi-generate-new ../strapi-generate-plugin ../strapi-generate-policy ../strapi-generate-service ../strapi-utils');
watcher('📦  Linking strapi...', 'npm link');

shell.cd('../strapi-plugin-email');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-email...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-plugin-users-permissions');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-users-permissions...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-plugin-content-manager');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-content-manager...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-plugin-settings-manager');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-settings-manager...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-plugin-upload');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-upload...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-upload-local');
watcher('📦  Linking strapi-plugin-upload...', 'npm link --no-optional', false);
shell.cd('../strapi-upload-aws-s3');
watcher('📦  Linking strapi-plugin-upload...', 'npm link --no-optional', false);

shell.cd('../strapi-plugin-content-type-builder');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
watcher('', 'npm install ../strapi-generate --no-optional');
watcher('', 'npm install ../strapi-generate-api --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-content-type-builder...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

// Log installation duration.
const installationEndDate = new Date();
const duration = (installationEndDate.getTime() - installationStartDate.getTime()) / 1000;
shell.echo('✅  Strapi has been succesfully installed.');
shell.echo(`⏳  The installation took ${Math.floor(duration / 60) > 0 ? `${Math.floor(duration / 60)} minutes and ` : ''}${Math.floor(duration % 60)} seconds.`);
<commit_msg>Fix test symlink<commit_after>const shell = require('shelljs');

// Store installation start date.
const silent = process.env.npm_config_debug !== 'true';
const installationStartDate = new Date();
const watcher = (label, cmd, withSuccess = true) => {
  if (label.length > 0) {
    shell.echo(label);
  }

  const data = shell.exec(cmd, {
    silent
  });

  if (data.stderr && data.code !== 0) {
    console.error(data.stderr);
    process.exit(1);
  }

  if (label.length > 0 && withSuccess) {
    shell.echo('✅  Success');
    shell.echo('');
  }
};

shell.echo('');
shell.echo('🕓  The setup process can take few minutes.');
shell.echo('');

// Remove existing binary.
shell.rm('-f', '/usr/local/bin/strapi.js');

shell.cd('packages/strapi-utils');
watcher('📦  Linking strapi-utils...', 'npm link');

shell.cd('../strapi-generate');
watcher('', 'npm install ../strapi-utils');
watcher('📦  Linking strapi-generate...', 'npm link');

shell.cd('../strapi-generate-api');
watcher('📦  Linking strapi-generate-api...', 'npm link');

shell.cd('../strapi-helper-plugin');
watcher('📦  Linking strapi-helper-plugin...', 'npm link');

shell.cd('../strapi-admin');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
watcher('', 'npm install ../strapi-utils --no-optional');
shell.rm('-f', 'package-lock.json');

// Without these line Travis failed.
if (shell.test('-e', 'admin/src/config/plugins.json') === false) {
  shell.config.silent = silent;
  shell.cd('admin/src/config/');
  shell.ShellString('[]').to('plugins.json');
  shell.cd('../../../');
}

watcher('📦  Linking strapi-admin', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-generate-admin');
watcher('', 'npm install ../strapi-admin');
watcher('📦  Linking strapi-generate-admin...', 'npm link');

shell.cd('../strapi-generate-new');
watcher('', 'npm install ../strapi-utils');
watcher('📦  Linking strapi-generate-new', 'npm link');

shell.cd('../strapi-mongoose');
watcher('', 'npm install ../strapi-utils');
watcher('📦  Linking strapi-mongoose...', 'npm link');

shell.cd('../strapi');
watcher('', 'npm install ../strapi-generate ../strapi-generate-admin ../strapi-generate-api ../strapi-generate-new ../strapi-generate-plugin ../strapi-generate-policy ../strapi-generate-service ../strapi-utils');
watcher('📦  Linking strapi...', 'npm link');

shell.cd('../strapi-plugin-email');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-email...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-plugin-users-permissions');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-users-permissions...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-plugin-content-manager');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-content-manager...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-plugin-settings-manager');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-settings-manager...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-upload-local');
watcher('📦  Linking strapi-plugin-upload...', 'npm link --no-optional', false);
shell.cd('../strapi-upload-aws-s3');
watcher('📦  Linking strapi-plugin-upload...', 'npm link --no-optional', false);

shell.cd('../strapi-plugin-upload');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-upload...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

shell.cd('../strapi-plugin-content-type-builder');
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
watcher('', 'npm install ../strapi-generate --no-optional');
watcher('', 'npm install ../strapi-generate-api --no-optional');
shell.rm('-f', 'package-lock.json');
watcher('📦  Linking strapi-plugin-content-type-builder...', 'npm link --no-optional', false);
watcher('🏗  Building...', 'npm run build');

// Log installation duration.
const installationEndDate = new Date();
const duration = (installationEndDate.getTime() - installationStartDate.getTime()) / 1000;
shell.echo('✅  Strapi has been succesfully installed.');
shell.echo(`⏳  The installation took ${Math.floor(duration / 60) > 0 ? `${Math.floor(duration / 60)} minutes and ` : ''}${Math.floor(duration % 60)} seconds.`);
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015 Martin Nordholts
 * All rights reserved.
 *
 *
 * This file contains some code copied from http://victorjs.org/, which
 * is licensed  as follows:
 *
 * MIT License
 *
 * Copyright (c) 2011 Max Kueng, George Crabtree
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
 (function(win) {
'use strict';

// Part of http://victorjs.org/ library. Licence above.

function Victor (x, y) {
    this.x = x || 0;
    this.y = y || 0;
};

Victor.prototype.subtract = function (vec) {
    this.x -= vec.x;
    this.y -= vec.y;
    return this;
};

Victor.prototype.multiplyScalar = function (scalar) {
    this.x *= scalar;
    this.y *= scalar;
    return this;
};

Victor.prototype.copyX = function (vec) {
    this.x = vec.x;
    return this;
};

Victor.prototype.copyY = function (vec) {
    this.y = vec.y;
    return this;
};

Victor.prototype.copy = function (vec) {
    this.copyX(vec);
    this.copyY(vec);
    return this;
};

Victor.prototype.dot = function (vec2) {
    return this.x * vec2.x + this.y * vec2.y;
};

Victor.prototype.cross = function (vec2) {
    return (this.x * vec2.y ) - (this.y * vec2.x );
};

Victor.prototype.rotate = function (angle) {
    var nx = (this.x * Math.cos(angle)) - (this.y * Math.sin(angle));
    var ny = (this.x * Math.sin(angle)) + (this.y * Math.cos(angle));

    this.x = nx;
    this.y = ny;

    return this;
};

/**
 * Square that has its pivot point at its center, and an imaginary pendulum fixed to it so that it swings.
 */
function PendulumSquare(rotatingPart) {
    this.rotatingPart = rotatingPart;

    this.rotationalVel = 0;
    this.rotation = 0;
}

// Pre-allocated objects to avoid object creation each frame
var pendulum = new Victor();
var forceNormal = new Victor();
var forcePerpendicular = new Victor();
var GRAVITY = new Victor(0, 200);

PendulumSquare.prototype.applyForce = function(forceVector, deltaTimeSeconds) {
    // rotation = 0 => pendulum points downwards (hangs still)
    pendulum.x = 0;
    pendulum.y = 1;
    pendulum.rotate(this.rotation);

    forceNormal.copy(pendulum);
    forceNormal.multiplyScalar(pendulum.dot(forceVector));

    forcePerpendicular.copy(forceVector);
    forcePerpendicular.subtract(forceNormal);

    var rotationalAcc = pendulum.cross(forcePerpendicular) / Math.max(10, this.rotatingPart.width() / 10);

    this.rotationalVel += rotationalAcc * deltaTimeSeconds;
    this.rotation += this.rotationalVel * deltaTimeSeconds;
    this.rotationalVel *= 0.97; // Slowly halt the rotation/swigning
    // TODO: Depend on deltaTime

    // To prevent rounding errors when the rotation is very big, make sure to remove whole rotations
    var twoPi = 2 * Math.PI;
    var fullRotations = (this.rotation / twoPi) | 0;
    this.rotation -= fullRotations * twoPi;

    // TODO: Add initial rotation
    var degrees = this.rotation * 180 / Math.PI;
    this.rotatingPart.css({
        transform: 'rotate(' + degrees + 'deg)',
        '-msTransform': 'rotate(' + degrees + 'deg)',
        '-webkitTransform': 'rotate(' + degrees + 'deg)'
    });
}

PendulumSquare.prototype.isStandingStill = function() {
    return Math.abs(this.rotation) < 0.015 && Math.abs(this.rotationalVel) < 0.1;
}

PendulumSquare.prototype.kickstartRotation = function() {
    this.rotationalVel += 15;
}

/**
 * Clever starting and stopping of the animation is taken care of by this class,
 * as well as running the animation itself.
 */
function AnimationController(win) {
    this.win = win;
    this.framesWithoutMovement = 0;
    this.animationRunning = false;
    this.lastTimeStamp = null;
}

AnimationController.prototype.ensureRunning = function() {
    if (!this.animationRunning) {
        this.win.requestAnimationFrame(this.runFrame.bind(this));
        this.animationRunning = true;
        this.framesWithoutMovement = 0;
    }
}

AnimationController.prototype.runFrame = function(timestamp) {
    // Handle special case of first invocation
    if (!this.lastTimeStamp) {
        this.lastTimeStamp = timestamp;
    }

    var deltaTimeMs = timestamp - this.lastTimeStamp;
    this.lastTimeStamp = timestamp;

    // Prevent weird delta times due to e.g. low prio or slow devices
    deltaTimeMs = Math.max(0, deltaTimeMs);
    deltaTimeMs = Math.min(100, deltaTimeMs);

    var animationStandingStill = true;
    rotatingParts.forEach(function(pendulumSquare) {
        pendulumSquare.applyForce(GRAVITY, deltaTimeMs / 1000);

        if (!pendulumSquare.isStandingStill()) {
            animationStandingStill = false;
        }
    });

    if (animationStandingStill) {
        this.framesWithoutMovement++;
    } else {
        this.framesWithoutMovement = 0;
    }

    if (this.framesWithoutMovement < 10) {
        win.requestAnimationFrame(this.runFrame.bind(this));
    } else {
        this.animationRunning = false;
    }
}

// Setup code to keep track of all components that shall rotate
var rotatingParts = [];
$('#logo > img').each(function() {
    rotatingParts.push(new PendulumSquare($(this)));
})

var animationController = new AnimationController(win);

// Trigger animation on click
// TODO: touch/mouse down event for responsiveness
$('#logo').click(function() {
    rotatingParts.forEach(function(pendulumSquare) {
        pendulumSquare.kickstartRotation();
    });
    animationController.ensureRunning();
});

$('.logo-text > span').click(function() {
    var logoText = $(this);
    $({blurAmount: 0}).animate({blurAmount: 100},
        {
            step: function(now){
                logoText.css({
                    'filter': 'blur(' + now + 'px)',
                    '-webkitFilter': 'blur(' + now + 'px)',
                    'opacity': (1 - now/100)
                });
            },
            always: function(){
                window.setTimeout(function(){
                    logoText.css({
                        'filter': '',
                        '-webkitFilter': '',
                        'opacity': 1
                    });
                }, 3000);
            }

    }
    );
})

})(window);
<commit_msg>Spice up fade and blur animation<commit_after>/*
 * Copyright (c) 2015 Martin Nordholts
 * All rights reserved.
 *
 *
 * This file contains some code copied from http://victorjs.org/, which
 * is licensed  as follows:
 *
 * MIT License
 *
 * Copyright (c) 2011 Max Kueng, George Crabtree
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
 (function(win) {
'use strict';

// Part of http://victorjs.org/ library. Licence above.

function Victor (x, y) {
    this.x = x || 0;
    this.y = y || 0;
};

Victor.prototype.subtract = function (vec) {
    this.x -= vec.x;
    this.y -= vec.y;
    return this;
};

Victor.prototype.multiplyScalar = function (scalar) {
    this.x *= scalar;
    this.y *= scalar;
    return this;
};

Victor.prototype.copyX = function (vec) {
    this.x = vec.x;
    return this;
};

Victor.prototype.copyY = function (vec) {
    this.y = vec.y;
    return this;
};

Victor.prototype.copy = function (vec) {
    this.copyX(vec);
    this.copyY(vec);
    return this;
};

Victor.prototype.dot = function (vec2) {
    return this.x * vec2.x + this.y * vec2.y;
};

Victor.prototype.cross = function (vec2) {
    return (this.x * vec2.y ) - (this.y * vec2.x );
};

Victor.prototype.rotate = function (angle) {
    var nx = (this.x * Math.cos(angle)) - (this.y * Math.sin(angle));
    var ny = (this.x * Math.sin(angle)) + (this.y * Math.cos(angle));

    this.x = nx;
    this.y = ny;

    return this;
};

/**
 * Square that has its pivot point at its center, and an imaginary pendulum fixed to it so that it swings.
 */
function PendulumSquare(rotatingPart) {
    this.rotatingPart = rotatingPart;

    this.rotationalVel = 0;
    this.rotation = 0;
}

// Pre-allocated objects to avoid object creation each frame
var pendulum = new Victor();
var forceNormal = new Victor();
var forcePerpendicular = new Victor();
var GRAVITY = new Victor(0, 200);

PendulumSquare.prototype.applyForce = function(forceVector, deltaTimeSeconds) {
    // rotation = 0 => pendulum points downwards (hangs still)
    pendulum.x = 0;
    pendulum.y = 1;
    pendulum.rotate(this.rotation);

    forceNormal.copy(pendulum);
    forceNormal.multiplyScalar(pendulum.dot(forceVector));

    forcePerpendicular.copy(forceVector);
    forcePerpendicular.subtract(forceNormal);

    var rotationalAcc = pendulum.cross(forcePerpendicular) / Math.max(10, this.rotatingPart.width() / 10);

    this.rotationalVel += rotationalAcc * deltaTimeSeconds;
    this.rotation += this.rotationalVel * deltaTimeSeconds;
    this.rotationalVel *= 0.97; // Slowly halt the rotation/swigning
    // TODO: Depend on deltaTime

    // To prevent rounding errors when the rotation is very big, make sure to remove whole rotations
    var twoPi = 2 * Math.PI;
    var fullRotations = (this.rotation / twoPi) | 0;
    this.rotation -= fullRotations * twoPi;

    // TODO: Add initial rotation
    var degrees = this.rotation * 180 / Math.PI;
    this.rotatingPart.css({
        transform: 'rotate(' + degrees + 'deg)',
        '-msTransform': 'rotate(' + degrees + 'deg)',
        '-webkitTransform': 'rotate(' + degrees + 'deg)'
    });
}

PendulumSquare.prototype.isStandingStill = function() {
    return Math.abs(this.rotation) < 0.015 && Math.abs(this.rotationalVel) < 0.1;
}

PendulumSquare.prototype.kickstartRotation = function() {
    this.rotationalVel += 15;
}

/**
 * Clever starting and stopping of the animation is taken care of by this class,
 * as well as running the animation itself.
 */
function AnimationController(win) {
    this.win = win;
    this.framesWithoutMovement = 0;
    this.animationRunning = false;
    this.lastTimeStamp = null;
}

AnimationController.prototype.ensureRunning = function() {
    if (!this.animationRunning) {
        this.win.requestAnimationFrame(this.runFrame.bind(this));
        this.animationRunning = true;
        this.framesWithoutMovement = 0;
    }
}

AnimationController.prototype.runFrame = function(timestamp) {
    // Handle special case of first invocation
    if (!this.lastTimeStamp) {
        this.lastTimeStamp = timestamp;
    }

    var deltaTimeMs = timestamp - this.lastTimeStamp;
    this.lastTimeStamp = timestamp;

    // Prevent weird delta times due to e.g. low prio or slow devices
    deltaTimeMs = Math.max(0, deltaTimeMs);
    deltaTimeMs = Math.min(100, deltaTimeMs);

    var animationStandingStill = true;
    rotatingParts.forEach(function(pendulumSquare) {
        pendulumSquare.applyForce(GRAVITY, deltaTimeMs / 1000);

        if (!pendulumSquare.isStandingStill()) {
            animationStandingStill = false;
        }
    });

    if (animationStandingStill) {
        this.framesWithoutMovement++;
    } else {
        this.framesWithoutMovement = 0;
    }

    if (this.framesWithoutMovement < 10) {
        win.requestAnimationFrame(this.runFrame.bind(this));
    } else {
        this.animationRunning = false;
    }
}

// Setup code to keep track of all components that shall rotate
var rotatingParts = [];
$('#logo > img').each(function() {
    rotatingParts.push(new PendulumSquare($(this)));
})

var animationController = new AnimationController(win);

// Trigger animation on click
// TODO: touch/mouse down event for responsiveness
$('#logo').click(function() {
    rotatingParts.forEach(function(pendulumSquare) {
        pendulumSquare.kickstartRotation();
    });
    animationController.ensureRunning();
});

$('.logo-text > span').click(function() {
    var letter = $(this);
    if (parseFloat(letter.css('opacity')) < 1) {
        // Let ongoing animation finish
        return;
    }

    var animLength = 3000;
    var blurLength = 50;
    $({animationProgress: 0}).animate({animationProgress: animLength}, {
        duration: animLength,
        step: function(now){
            var currentOpacity = 0;
            if (now < blurLength) {
                currentOpacity = (1 - now / blurLength);
            } else if (now > animLength - blurLength) {
                currentOpacity = 1 - (animLength - now) / blurLength;
            }
            var blurRadius = (1 - currentOpacity) * 100;
            var blurFilter = 'blur(' + blurRadius + 'px)';
            letter.css({
                'filter': blurFilter,
                '-webkitFilter': blurFilter,
                'opacity': currentOpacity
            });
        },
        always: function(){
            letter.css({
                'filter': '',
                '-webkitFilter': '',
                'opacity': 1
            });
        }

    });
})

})(window);
<|endoftext|>


<commit_before>/*
 * Copyright 2021, TeamDev. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * This script contains helper functions for switching between Java/Kotlin source code examples.
 *
 * The selected language will be saved in cookies so that the user can navigate between pages.
 *
 * To show tabs use the structure below.
 * Add the `lang` attribute with `Java`, `Kotlin`, `JavaScript`, or any other language value
 * for `div`s with content. If no language is provided, the content will not be displayed.
 *
 * ```
 * <div class="code-tabs">
 *     <div class="code-tab-content" lang="Java">
 *          Any Java content here
 *     </div>
 *     <div class="code-tab-content" lang="Kotlin">
 *          Any Kotlin content here
 *     </div>
 *     <div class="code-tab-content" lang="JavaScript">
 *          Any JavaScript content here
 *     </div>
 * </div>
 * ```
 *
 * In markdown, all tags should be left-aligned. This way, the blocks of code
 * will not be broken:
 * ```
 * <div class="code-tabs">
 * <div class="code-tab-content" lang="Java">
 * Any Java content here
 * </div>
 * <div class="code-tab-content" lang="Kotlin">
 * Any Kotlin content here
 * </div>
 * </div>
 * ```
 *
 * If you don't need to display the tabs, but only need to show a specific paragraph of text
 * or change the title depending on the language, just use this:
 * ```
 * <div class="code-tab-content" lang="Java">
 * # Getting started with Spine in Java
 * </div>
 * <div class="code-tab-content" lang=Kotlin>
 * # Getting started with Spine in Kotlin
 * </div>
 * ```
 *
 * To change only some of the words in a sentence, use the `<span>` tag with the `.inline` class:
 * ```
 * A minimal client-server application in
 * <span class="code-tab-content inline" lang="Java">Java</span>
 * <span class="code-tab-content inline" lang="Kotlin">Kotlin</span>
 * which handles one command to print some text...
 * ```
 */

'use strict';

$(
    function() {
        const cookieCodeLang = 'codeLang';
        const $codeTabs = $('.code-tabs');
        const $codeTabContent = $('.code-tab-content');

        addTabSwitcher();
        addTabContentClass();
        initCodeLangSwitcher();

        /**
         * Adds a tab switcher to the DOM.
         */
        function addTabSwitcher() {
            $codeTabs.each(function() {
                const tabBlock = $(this);
                createTab(tabBlock, createTabContainer(tabBlock));
            });
        }

        /**
         * Creates a container for tabs.
         *
         * @param tabBlock a block that contains tabs.
         * @return {jQuery|HTMLElement} tabContainer a container for tabs.
         */
        function createTabContainer(tabBlock) {
            const tabContainer = $('

');
            tabBlock.prepend(tabContainer);
            return tabContainer;
        }

        /**
         * Creates a tab inside the container for each `code-tab-content` element.
         *
         * Adds the corresponding class to the tab that was provided in the `lang` attribute.
         *
         * @param tabBlock a block that contains tabs.
         * @param tabContainer a container for tabs.
         */
        function createTab(tabBlock, tabContainer) {
            const tabContent = tabBlock.find($codeTabContent);
            tabContent.each(function () {
                const lang = getLang($(this));
                if (lang) {
                    const item = $(`<div class="tab ${lang.langClass}">${lang.langName}</div>`);
                    tabContainer.append(item);
                }
            });
        }

        /**
         * Adds the class to the `code-tab-content` element from the `lang` attribute.
         */
        function addTabContentClass() {
            $codeTabContent.each(function () {
                const lang = getLang($(this));
                if (lang) {
                    $(this).addClass(lang.langClass);
                }
            });
        }

        /**
         * Gets the language value from the `lang` attribute of the element.
         *
         * And generates a valid class name based on the value of the attribute.
         *
         * @param el an element that has `lang` attribute.
         * @return {{langName, langClass}}
         */
        function getLang(el) {
            const lang = el.attr('lang');
            if (typeof lang !== 'undefined' && lang !== false ) {
                const langName = lang;
                let langClass = lang.replace(/\s+/g, '-').toLowerCase();
                langClass = langClass.replace(/[.,+\/#!$%\^&\*;:{}=\_`~()]/g,'-');
                return {langName, langClass}
            }
        }

        /**
         * Inits a code language switcher.
         *
         * By default, sets the primary code language to the `cookie`.
         * On a tab click switches between code languages.
         */
        function initCodeLangSwitcher() {
            const primaryLang = 'java';
            const cookieValue = Cookies.get(cookieCodeLang);

            if (cookieValue == null) {
                setCodeLang(primaryLang);
            } else {
                setCodeLang(cookieValue);
            }

            $('.tab').click(function() {
                const lang = $(this).attr('class').split(' ')[1];
                if (typeof lang !== 'undefined' && lang !== false) {
                    setCodeLang(lang);
                }
            });
        }

        /**
         * Sets the chosen code language to the `cookie` and adds corresponding
         * CSS classes to the selected tab and content element.
         *
         * The CSS file is located at `_sass/modules/_code-tabs.scss`.
         *
         * @param codeLang a selected code language.
         */
        function setCodeLang(codeLang) {
            Cookies.set(cookieCodeLang, codeLang);
            $('.tab').removeClass('selected');
            $('.tab.' + codeLang).addClass('selected');
            $codeTabContent.removeClass('show');
            $('.code-tab-content.' + codeLang).addClass('show');
        }
    }
);
<commit_msg>Update JSDocs<commit_after>/*
 * Copyright 2021, TeamDev. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * This script contains helper functions for switching between any language of source
 * code examples.
 *
 * This script does not need to be modified if a new language needs to be added to the layout.
 * The selected language will be saved in cookies so that the user can navigate between pages.
 *
 * To show tabs use the structure below.
 * Add the `lang` attribute with `Java`, `Kotlin`, `JavaScript`, or any other language value
 * for `div`s with content. If no language is provided, the content will not be displayed.
 *
 * ```
 * <div class="code-tabs">
 *     <div class="code-tab-content" lang="Java">
 *          Any Java content here
 *     </div>
 *     <div class="code-tab-content" lang="Kotlin">
 *          Any Kotlin content here
 *     </div>
 *     <div class="code-tab-content" lang="JavaScript">
 *          Any JavaScript content here
 *     </div>
 * </div>
 * ```
 *
 * In markdown, all tags should be left-aligned. This way, the blocks of code
 * will not be broken:
 * ```
 * <div class="code-tabs">
 * <div class="code-tab-content" lang="Java">
 * Any Java content here
 * </div>
 * <div class="code-tab-content" lang="Kotlin">
 * Any Kotlin content here
 * </div>
 * </div>
 * ```
 *
 * If you don't need to display the tabs, but only need to show a specific paragraph of text
 * or change the title depending on the language, just use this:
 * ```
 * <div class="code-tab-content" lang="Java">
 * # Getting started with Spine in Java
 * </div>
 * <div class="code-tab-content" lang=Kotlin>
 * # Getting started with Spine in Kotlin
 * </div>
 * ```
 *
 * To change only some of the words in a sentence, use the `<span>` tag with the `.inline` class:
 * ```
 * A minimal client-server application in
 * <span class="code-tab-content inline" lang="Java">Java</span>
 * <span class="code-tab-content inline" lang="Kotlin">Kotlin</span>
 * which handles one command to print some text...
 * ```
 */

'use strict';

$(
    function() {
        const cookieCodeLang = 'codeLang';
        const $codeTabs = $('.code-tabs');
        const $codeTabContent = $('.code-tab-content');

        addTabSwitcher();
        addTabContentClass();
        initCodeLangSwitcher();

        /**
         * Adds a tab switcher to the DOM.
         */
        function addTabSwitcher() {
            $codeTabs.each(function() {
                const tabBlock = $(this);
                createTab(tabBlock, createTabContainer(tabBlock));
            });
        }

        /**
         * Creates a container for tabs.
         *
         * @param tabBlock a block that contains tabs.
         * @return {jQuery|HTMLElement} tabContainer a container for tabs.
         */
        function createTabContainer(tabBlock) {
            const tabContainer = $('

');
            tabBlock.prepend(tabContainer);
            return tabContainer;
        }

        /**
         * Creates a tab inside the container for each `code-tab-content` element.
         *
         * Adds the corresponding class to the tab that was provided in the `lang` attribute.
         *
         * @param tabBlock a block that contains tabs.
         * @param tabContainer a container for tabs.
         */
        function createTab(tabBlock, tabContainer) {
            const tabContent = tabBlock.find($codeTabContent);
            tabContent.each(function () {
                const lang = getLang($(this));
                if (lang) {
                    const item = $(`<div class="tab ${lang.langClass}">${lang.langName}</div>`);
                    tabContainer.append(item);
                }
            });
        }

        /**
         * Adds the class to the `code-tab-content` element from the `lang` attribute.
         */
        function addTabContentClass() {
            $codeTabContent.each(function () {
                const lang = getLang($(this));
                if (lang) {
                    $(this).addClass(lang.langClass);
                }
            });
        }

        /**
         * Gets the language value from the `lang` attribute of the element.
         *
         * And generates a valid class name based on the value of the attribute.
         *
         * @param el an element that has `lang` attribute.
         * @return {{langName, langClass}}
         */
        function getLang(el) {
            const lang = el.attr('lang');
            if (typeof lang !== 'undefined' && lang !== false ) {
                const langName = lang;
                let langClass = lang.replace(/\s+/g, '-').toLowerCase();
                langClass = langClass.replace(/[.,+\/#!$%\^&\*;:{}=\_`~()]/g,'-');
                return {langName, langClass}
            }
        }

        /**
         * Inits a code language switcher.
         *
         * By default, sets the primary code language to the `cookie`.
         * On a tab click switches between code languages.
         */
        function initCodeLangSwitcher() {
            const primaryLang = 'java';
            const cookieValue = Cookies.get(cookieCodeLang);

            if (cookieValue == null) {
                setCodeLang(primaryLang);
            } else {
                setCodeLang(cookieValue);
            }

            $('.tab').click(function() {
                const lang = $(this).attr('class').split(' ')[1];
                if (typeof lang !== 'undefined' && lang !== false) {
                    setCodeLang(lang);
                }
            });
        }

        /**
         * Sets the chosen code language to the `cookie` and adds corresponding
         * CSS classes to the selected tab and content element.
         *
         * The CSS file is located at `_sass/modules/_code-tabs.scss`.
         *
         * @param codeLang a selected code language.
         */
        function setCodeLang(codeLang) {
            Cookies.set(cookieCodeLang, codeLang);
            $('.tab').removeClass('selected');
            $('.tab.' + codeLang).addClass('selected');
            $codeTabContent.removeClass('show');
            $('.code-tab-content.' + codeLang).addClass('show');
        }
    }
);
<|endoftext|>


<commit_before>/*
---
name: Delegator
description: Allows for the registration of delegated events on a container.
requires: [More/Element.Delegation, Core/Options, Core/Events, /Event.Mock, /Behavior]
provides: [Delegator]
...
*/
(function(){

	var spaceOrCommaRegex = /\s*,\s*|\s+/g;

	window.Delegator = new Class({

		Implements: [Options, Events, Behavior.PassMethods],

		options: {
			// breakOnErrors: false,
			onError: Behavior.getLog('error'),
			onWarn: Behavior.getLog('warn')
		},

		initialize: function(options){
			this.setOptions(options);
			this._bound = {
				eventHandler: this._eventHandler.bind(this)
			};
			Delegator._instances.push(this);
			Object.each(Delegator._triggers, function(trigger){
				this._eventTypes.combine(trigger.types);
			}, this);
			this.API = new Class({ Extends: BehaviorAPI });
			this.passMethods({
				addEvent: this.addEvent.bind(this),
				removeEvent: this.removeEvent.bind(this),
				addEvents: this.addEvents.bind(this),
				removeEvents: this.removeEvents.bind(this),
				fireEvent: this.fireEvent.bind(this),
				attach: this.attach.bind(this),
				trigger: this.trigger.bind(this),
				error: function(){ this.fireEvent('error', arguments); }.bind(this),
				fail: function(){
					var msg = Array.join(arguments, ' ');
					throw new Error(msg);
				},
				warn: function(){
					this.fireEvent('warn', arguments);
				}.bind(this)
			});
		},

		attach: function(target, _method){
			_method = _method || 'addEvent';
			target = document.id(target);
			if ((_method == 'addEvent' && this._attachedTo.contains(target)) ||
			    (_method == 'removeEvent') && !this._attachedTo.contains(target)) return this;
			this._eventTypes.each(function(event){
				target[_method](event + ':relay([data-trigger])', this._bound.eventHandler);
			}, this);
			this._attachedTo.push(target);
			return this;
		},

		detach: function(target){
			if (target){
				this.attach(target, 'removeEvent');
				return this;
			} else {
				this._attachedTo.each(this.detach, this);
			}
		},

		trigger: function(name, element, event){
			if (!event || typeOf(event) == "string") event = new Event.Mock(element, event);
			var trigger = this._getTrigger(name);
			if (trigger && trigger.types.contains(event.type)) {
				if (this.options.breakOnErrors){
					this._trigger(trigger, element, event);
				} else {
					try {
						this._trigger(trigger, element, event);
					} catch(e) {
						this.fireEvent('error', ['Could not apply the trigger', name, e]);
					}
				}
			} else {
				this.fireEvent('error', 'Could not find a trigger with the name ' + name + ' for event: ' + event.type);
			}
			return this;
		},

		/******************
		 * PRIVATE METHODS
		 ******************/

		_getTrigger: function(name){
			return this._triggers[name] || Delegator._triggers[name];
		},

		_trigger: function(trigger, element, event){
			var api = new this.API(element, trigger.name);
			if (trigger.requireAs){
				api.requireAs(trigger.requireAs);
			} else if (trigger.require){
				api.require.apply(api, Array.from(trigger.require));
			} if (trigger.defaults){
				api.setDefault(trigger.defaults);
			}
			trigger.handler(event, element, api);
		},

		_eventHandler: function(event, target){
			var triggers = target.getTriggers();
			if (triggers.contains('EventStop')) event.stop();
			if (triggers.contains('PreventDefault')) event.preventDefault();
			triggers.each(function(trigger){
				this.trigger(trigger, target, event);
			}, this);
		},

		_onRegister: function(eventTypes){
			eventTypes.each(function(eventType){
				if (!this._eventTypes.contains(eventType)){
					this._attachedTo.each(function(element){
						element.addEvent(eventType + ':relay([data-trigger])', this._bound.eventHandler);
					}, this);
				}
				this._eventTypes.include(eventType);
			}, this);
		},

		_attachedTo: [],
		_eventTypes: [],
		_triggers: {}

	});

	Delegator._triggers = {};
	Delegator._instances = [];
	Delegator._onRegister = function(eventType){
		this._instances.each(function(instance){
			instance._onRegister(eventType);
		});
	};

	Delegator.register = function(eventTypes, name, handler, overwrite /** or eventType, obj, overwrite */){
		eventTypes = Array.from(eventTypes);
		if (typeOf(name) == "object"){
			var obj = name;
			for (name in obj){
				this.register.apply(this, [eventTypes, name, obj[name], handler]);
			}
			return this;
		}
		if (!this._triggers[name] || overwrite){
			if (typeOf(handler) == "function"){
				handler = {
					handler: handler
				};
			}
			handler.types = eventTypes;
			handler.name = name;
			this._triggers[name] = handler;
			this._onRegister(eventTypes);
		} else {
			throw new Error('Could add the trigger "' + name  +'" as a previous trigger by that same name exists.');
		}
		return this;
	};

	Delegator.implement('register', Delegator.register);

	Element.implement({

		addTrigger: function(name){
			return this.setData('trigger', this.getTriggers().include(name).join(' '));
		},

		removeTrigger: function(name){
			return this.setData('trigger', this.getTriggers().erase(name).join(' '));
		},

		getTriggers: function(){
			var triggers = this.getData('trigger');
			if (!triggers) return [];
			return triggers.trim().split(spaceOrCommaRegex);
		},

		hasTrigger: function(name){
			return this.getTriggers().contains(name);
		}

	});

})();<commit_msg>Delegator triggers now fire events; EventStop > Stop<commit_after>/*
---
name: Delegator
description: Allows for the registration of delegated events on a container.
requires: [More/Element.Delegation, Core/Options, Core/Events, /Event.Mock, /Behavior]
provides: [Delegator]
...
*/
(function(){

	var spaceOrCommaRegex = /\s*,\s*|\s+/g;

	window.Delegator = new Class({

		Implements: [Options, Events, Behavior.PassMethods],

		options: {
			// breakOnErrors: false,
			onError: Behavior.getLog('error'),
			onWarn: Behavior.getLog('warn')
		},

		initialize: function(options){
			this.setOptions(options);
			this._bound = {
				eventHandler: this._eventHandler.bind(this)
			};
			Delegator._instances.push(this);
			Object.each(Delegator._triggers, function(trigger){
				this._eventTypes.combine(trigger.types);
			}, this);
			this.API = new Class({ Extends: BehaviorAPI });
			this.passMethods({
				addEvent: this.addEvent.bind(this),
				removeEvent: this.removeEvent.bind(this),
				addEvents: this.addEvents.bind(this),
				removeEvents: this.removeEvents.bind(this),
				fireEvent: this.fireEvent.bind(this),
				attach: this.attach.bind(this),
				trigger: this.trigger.bind(this),
				error: function(){ this.fireEvent('error', arguments); }.bind(this),
				fail: function(){
					var msg = Array.join(arguments, ' ');
					throw new Error(msg);
				},
				warn: function(){
					this.fireEvent('warn', arguments);
				}.bind(this)
			});
		},

		attach: function(target, _method){
			_method = _method || 'addEvent';
			target = document.id(target);
			if ((_method == 'addEvent' && this._attachedTo.contains(target)) ||
			    (_method == 'removeEvent') && !this._attachedTo.contains(target)) return this;
			this._eventTypes.each(function(event){
				target[_method](event + ':relay([data-trigger])', this._bound.eventHandler);
			}, this);
			this._attachedTo.push(target);
			return this;
		},

		detach: function(target){
			if (target){
				this.attach(target, 'removeEvent');
				return this;
			} else {
				this._attachedTo.each(this.detach, this);
			}
		},

		trigger: function(name, element, event){
			if (!event || typeOf(event) == "string") event = new Event.Mock(element, event);
			var trigger = this._getTrigger(name);
			if (trigger && trigger.types.contains(event.type)) {
				if (this.options.breakOnErrors){
					this._trigger(trigger, element, event);
				} else {
					try {
						this._trigger(trigger, element, event);
					} catch(e) {
						this.fireEvent('error', ['Could not apply the trigger', name, e]);
					}
				}
			} else {
				this.fireEvent('error', 'Could not find a trigger with the name ' + name + ' for event: ' + event.type);
			}
			return this;
		},

		/******************
		 * PRIVATE METHODS
		 ******************/

		_getTrigger: function(name){
			return this._triggers[name] || Delegator._triggers[name];
		},

		_trigger: function(trigger, element, event){
			var api = new this.API(element, trigger.name);
			if (trigger.requireAs){
				api.requireAs(trigger.requireAs);
			} else if (trigger.require){
				api.require.apply(api, Array.from(trigger.require));
			} if (trigger.defaults){
				api.setDefault(trigger.defaults);
			}
			trigger.handler.apply(this, [event, element, api]);
			this.fireEvent('trigger', [trigger, element, event]);
		},

		_eventHandler: function(event, target){
			var triggers = target.getTriggers();
			if (triggers.contains('Stop')) event.stop();
			if (triggers.contains('PreventDefault')) event.preventDefault();
			triggers.each(function(trigger){
				if (trigger != "Stop" && trigger != "PreventDefault") this.trigger(trigger, target, event);
			}, this);
		},

		_onRegister: function(eventTypes){
			eventTypes.each(function(eventType){
				if (!this._eventTypes.contains(eventType)){
					this._attachedTo.each(function(element){
						element.addEvent(eventType + ':relay([data-trigger])', this._bound.eventHandler);
					}, this);
				}
				this._eventTypes.include(eventType);
			}, this);
		},

		_attachedTo: [],
		_eventTypes: [],
		_triggers: {}

	});

	Delegator._triggers = {};
	Delegator._instances = [];
	Delegator._onRegister = function(eventType){
		this._instances.each(function(instance){
			instance._onRegister(eventType);
		});
	};

	Delegator.register = function(eventTypes, name, handler, overwrite /** or eventType, obj, overwrite */){
		eventTypes = Array.from(eventTypes);
		if (typeOf(name) == "object"){
			var obj = name;
			for (name in obj){
				this.register.apply(this, [eventTypes, name, obj[name], handler]);
			}
			return this;
		}
		if (!this._triggers[name] || overwrite){
			if (typeOf(handler) == "function"){
				handler = {
					handler: handler
				};
			}
			handler.types = eventTypes;
			handler.name = name;
			this._triggers[name] = handler;
			this._onRegister(eventTypes);
		} else {
			throw new Error('Could add the trigger "' + name  +'" as a previous trigger by that same name exists.');
		}
		return this;
	};

	Delegator.implement('register', Delegator.register);

	Element.implement({

		addTrigger: function(name){
			return this.setData('trigger', this.getTriggers().include(name).join(' '));
		},

		removeTrigger: function(name){
			return this.setData('trigger', this.getTriggers().erase(name).join(' '));
		},

		getTriggers: function(){
			var triggers = this.getData('trigger');
			if (!triggers) return [];
			return triggers.trim().split(spaceOrCommaRegex);
		},

		hasTrigger: function(name){
			return this.getTriggers().contains(name);
		}

	});

})();<|endoftext|>