<commit_before>/*
 * Copyright (c) 2014 Modulus
 *
 * 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.
 *
 */

var error = module.exports;

error.handlePromptError = function(err, cb) {
  //Check if canceled by the user (CTRL+C)
  if(err.message && err.message === 'canceled') {
    console.log(''); //Extra space.
    return cb('Canceled by user.');
  }

  modulus.io.error(err);
  return cb(err);
};

error.handleApiError = function(err, command, cb) {
  var e = error.getError(err, command);

  if(e.level < 2) {
    cb(e.message);
    return '';
  } else {
    return e.message;
  }
};

error.getError = function(err, command) {
  var id = 'DEFAULT';
  var msg = '';

  if(err.code) {
    id = err.code;
  } else if(err.errors && err.errors.length > 0) {
    id = err.errors[0].id;
    msg = err.errors[0].message;
  }

  for(var e in error.responseCodes) {
    if(id === e) {
      // If there is no message set,
      // use the message from congress
      if(error.responseCodes[e].message === null) {
        var ret = error.responseCodes[e];
        ret.message = msg;

        return ret;
      }

      return error.responseCodes[e];
    }
  }

  if(typeof command === 'string') {
    for(var c in error.commandCodes) {
      if(command === c) {
        return error.commandCodes[c];
      }
    }
  }

  return error.responseCodes.DEFAULT;
};

/*--- Error Codes ---*/
// Level 0 - Something very bad happened, panic.
// Level 1 - Processing error, do not continue.
// Level 2 - Format/Input error, try again.
error.responseCodes = {
  DEFAULT : {
    id : 'DEFAULT',
    level : 1,
    message : 'There was an error processing your request.'
  },
  ECONNREFUSED : {
    id : 'ECONNREFUSED',
    level : 1,
    message : 'Could not connect to Modulus.'
  },
  INVALID_AUTH : {
    id : 'INVALID_AUTH',
    level : 1,
    message : 'Your session has expired. Please log in to continue.'
  },
  USERNAME_ALREADY_EXISTS : {
    id : 'USERNAME_ALREADY_EXISTS',
    level : 2,
    message : null
  },
  EMAIL_ALREADY_EXISTS : {
    id : 'EMAIL_ALREADY_EXISTS',
    level : 2,
    message : null
  },
  BETA_KEY_NOT_FOUND : {
    id : 'BETA_KEY_NOT_FOUND',
    level : 2,
    message : null
  },
  BETA_KEY_ALEADY_USED : {
    id : 'BETA_KEY_ALEADY_USED',
    level : 2,
    message : null
  },
  PROJECT_LIMIT_REACHED : {
    id : 'PROJECT_LIMIT_REACHED',
    level : 1,
    message : null
  },
  NO_CAPACITY : {
    id : 'NO_CAPACITY',
    level : 2,
    message : 'Not enough capacity for new project. New capacity is being added now. Please attempt the request again in a few minutes.'
  },
  PROJECT_ZIP_TOO_LARGE: {
    id : 'PROJECT_ZIP_TOO_LARGE',
    level : 1,
    message : 'Your application must be less than 1gb in size.'
  },
  NO_MATCHING_NAME: {
    id : 'NO_MATCHING_NAME',
    level: 1,
    message: 'No project found that matches specified name.'
  },
  NO_MATCHING_DB_NAME: {
    id : 'NO_MATCHING_DB_NAME',
    level: 1,
    message: 'No database found that matches specified name.'
  },
  OAUTH_TOKEN_NOT_FOUND: {
    id : 'OAUTH_TOKEN_NOT_FOUND',
    level : 1,
    message : 'Please link your account with GitHub using the web portal to use GitHub authentication.'
  },
  SINGLE_SIGN_ON_USER_NOT_FOUND: {
    id : 'SINGLE_SIGN_ON_USER_NOT_FOUND',
    level : 1,
    message : 'GitHub account not found. Please link your account with GitHub using the web portal to use GitHub authentication.'
  },
  INVALID_ENV_VARIABLE_VALUE: {
    id: 'INVALID_ENV_VARIABLE_VALUE',
    level: 2,
    message: 'Environment variable values cannot contain single quotes.'
  },
  INVALID_ENV_VARIABLE_NAME: {
    id: 'INVALID_ENV_VARIABLE_NAME',
    level: 2,
    message: 'Variable names cannot start with numbers, can only contain alpha-numeric characters and underscores, and cannot contain quotes.'
  },
  INVALID_PROJECT_TYPE: {
    id: 'INVALID_PROJECT_TYPE',
    level: 1,
    message: 'The project type you have provided was not recognized.'
  },
  API_KEY_NOT_FOUND: {
    id: 'API_KEY_NOT_FOUND',
    level: 1,
    message: 'Token not found. Please ensure it was entered correctly and try again.'
  },
  INVALID_JSON: {
    id: 'INVALID_JSON',
    level: 1,
    message: 'Invalid JSON content.'
  },
  INVALID_VALUE: {
    id: 'INVALID_VALUE',
    level: 1,
    message: 'Failed to parse environment variable value.'
  },
  INVALID_FILE: {
    id: 'INVALID_FILE',
    level: 1,
    message: 'The specified file is missing or invalid.'
  },
  INVALID_FLAGS: {
    id: 'INVALID_FLAGS',
    level: 1,
    message: 'You must be verified to perform that action.'
  }
};

error.commandCodes = {
  LOGIN : {
    id : 'LOGIN',
    level : 2,
    message : 'Username or Password incorrect.\nFor Github users, use the --github option.'
  },
  CREATE_MONGO : {
    id : 'CREATE_MONGO',
    level : 1,
    message : 'MongoDB database could not be created.'
  },
  GET_DATABASES : {
    id : 'GET_DATABASES',
    level : 1,
    message : 'Could not retreive databases for user.'
  }
};<commit_msg>Updated error message to include more detail.<commit_after>/*
 * Copyright (c) 2014 Modulus
 *
 * 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.
 *
 */

var error = module.exports;

error.handlePromptError = function(err, cb) {
  //Check if canceled by the user (CTRL+C)
  if(err.message && err.message === 'canceled') {
    console.log(''); //Extra space.
    return cb('Canceled by user.');
  }

  modulus.io.error(err);
  return cb(err);
};

error.handleApiError = function(err, command, cb) {
  var e = error.getError(err, command);

  if(e.level < 2) {
    cb(e.message);
    return '';
  } else {
    return e.message;
  }
};

error.getError = function(err, command) {
  var id = 'DEFAULT';
  var msg = '';

  if(err.code) {
    id = err.code;
  } else if(err.errors && err.errors.length > 0) {
    id = err.errors[0].id;
    msg = err.errors[0].message;
  }

  for(var e in error.responseCodes) {
    if(id === e) {
      // If there is no message set,
      // use the message from congress
      if(error.responseCodes[e].message === null) {
        var ret = error.responseCodes[e];
        ret.message = msg;

        return ret;
      }

      return error.responseCodes[e];
    }
  }

  if(typeof command === 'string') {
    for(var c in error.commandCodes) {
      if(command === c) {
        return error.commandCodes[c];
      }
    }
  }

  return error.responseCodes.DEFAULT;
};

/*--- Error Codes ---*/
// Level 0 - Something very bad happened, panic.
// Level 1 - Processing error, do not continue.
// Level 2 - Format/Input error, try again.
error.responseCodes = {
  DEFAULT : {
    id : 'DEFAULT',
    level : 1,
    message : 'There was an error processing your request.'
  },
  ECONNREFUSED : {
    id : 'ECONNREFUSED',
    level : 1,
    message : 'Could not connect to Modulus.'
  },
  INVALID_AUTH : {
    id : 'INVALID_AUTH',
    level : 1,
    message : 'Your session has expired. Please log in to continue.'
  },
  USERNAME_ALREADY_EXISTS : {
    id : 'USERNAME_ALREADY_EXISTS',
    level : 2,
    message : null
  },
  EMAIL_ALREADY_EXISTS : {
    id : 'EMAIL_ALREADY_EXISTS',
    level : 2,
    message : null
  },
  BETA_KEY_NOT_FOUND : {
    id : 'BETA_KEY_NOT_FOUND',
    level : 2,
    message : null
  },
  BETA_KEY_ALEADY_USED : {
    id : 'BETA_KEY_ALEADY_USED',
    level : 2,
    message : null
  },
  PROJECT_LIMIT_REACHED : {
    id : 'PROJECT_LIMIT_REACHED',
    level : 1,
    message : null
  },
  NO_CAPACITY : {
    id : 'NO_CAPACITY',
    level : 2,
    message : 'Not enough capacity for new project. New capacity is being added now. Please attempt the request again in a few minutes.'
  },
  PROJECT_ZIP_TOO_LARGE: {
    id : 'PROJECT_ZIP_TOO_LARGE',
    level : 1,
    message : 'Your application must be less than 1gb in size.'
  },
  NO_MATCHING_NAME: {
    id : 'NO_MATCHING_NAME',
    level: 1,
    message: 'No project found that matches specified name.'
  },
  NO_MATCHING_DB_NAME: {
    id : 'NO_MATCHING_DB_NAME',
    level: 1,
    message: 'No database found that matches specified name.'
  },
  OAUTH_TOKEN_NOT_FOUND: {
    id : 'OAUTH_TOKEN_NOT_FOUND',
    level : 1,
    message : 'Please link your account with GitHub using the web portal to use GitHub authentication.'
  },
  SINGLE_SIGN_ON_USER_NOT_FOUND: {
    id : 'SINGLE_SIGN_ON_USER_NOT_FOUND',
    level : 1,
    message : 'GitHub account not found. Please link your account with GitHub using the web portal to use GitHub authentication.'
  },
  INVALID_ENV_VARIABLE_VALUE: {
    id: 'INVALID_ENV_VARIABLE_VALUE',
    level: 2,
    message: 'Environment variable values cannot contain single quotes.'
  },
  INVALID_ENV_VARIABLE_NAME: {
    id: 'INVALID_ENV_VARIABLE_NAME',
    level: 2,
    message: 'Variable names cannot start with numbers, can only contain alpha-numeric characters and underscores, and cannot contain quotes.'
  },
  INVALID_PROJECT_TYPE: {
    id: 'INVALID_PROJECT_TYPE',
    level: 1,
    message: 'The project type you have provided was not recognized.'
  },
  API_KEY_NOT_FOUND: {
    id: 'API_KEY_NOT_FOUND',
    level: 1,
    message: 'Token not found. Please ensure it was entered correctly and try again.'
  },
  INVALID_JSON: {
    id: 'INVALID_JSON',
    level: 1,
    message: 'Invalid JSON content.'
  },
  INVALID_VALUE: {
    id: 'INVALID_VALUE',
    level: 1,
    message: 'Failed to parse environment variable value.'
  },
  INVALID_FILE: {
    id: 'INVALID_FILE',
    level: 1,
    message: 'The specified file is missing or invalid.'
  },
  INVALID_FLAGS: {
    id: 'INVALID_FLAGS',
    level: 1,
    message: 'You must be verified to perform this action. Visit https://modulus.io/verify to verify your account.'
  }
};

error.commandCodes = {
  LOGIN : {
    id : 'LOGIN',
    level : 2,
    message : 'Username or Password incorrect.\nFor Github users, use the --github option.'
  },
  CREATE_MONGO : {
    id : 'CREATE_MONGO',
    level : 1,
    message : 'MongoDB database could not be created.'
  },
  GET_DATABASES : {
    id : 'GET_DATABASES',
    level : 1,
    message : 'Could not retreive databases for user.'
  }
};<|endoftext|>


<commit_before>/**
* Editable Popover
* ---------------------
* requires bootstrap-popover.js
*/
(function ($) {
    "use strict";

    //extend methods
    $.extend($.fn.editableContainer.Popup.prototype, {
        containerName: 'popover',
        //for compatibility with bootstrap <= 2.2.1 (content inserted into <p> instead of directly .popover-content)
        innerCss: $.fn.popover && $($.fn.popover.defaults.template).find('p').length ? '.popover-content p' : '.popover-content',

        initContainer: function(){
            $.extend(this.containerOptions, {
                trigger: 'manual',
                selector: false,
                content: ' ',
                template: $.fn.popover.defaults.template
            });

            //as template property is used in inputs, hide it from popover
            var t;
            if(this.$element.data('template')) {
               t = this.$element.data('template');
               this.$element.removeData('template');
            }

            this.call(this.containerOptions);

            if(t) {
               //restore data('template')
               this.$element.data('template', t);
            }
        },

        /* show */
        innerShow: function () {
            this.call('show');
        },

        /* hide */
        innerHide: function () {
            this.call('hide');
        },

        /* destroy */
        innerDestroy: function() {
            this.call('destroy');
        },

        setContainerOption: function(key, value) {
            this.container().options[key] = value;
        },

        /**
        * move popover to new position. This function mainly copied from bootstrap-popover.
        */
        /*jshint laxcomma: true*/
        setPosition: function () {

            (function() {
                var $tip = this.tip()
                , inside
                , pos
                , actualWidth
                , actualHeight
                , placement
                , tp;

                placement = typeof this.options.placement === 'function' ?
                this.options.placement.call(this, $tip[0], this.$element[0]) :
                this.options.placement;

                inside = /in/.test(placement);

                $tip
              //  .detach()
              //vitalets: remove any placement class because otherwise they dont influence on re-positioning of visible popover
                .removeClass('top right bottom left')
                .css({ top: 0, left: 0, display: 'block' });
              //  .insertAfter(this.$element);

                pos = this.getPosition(inside);

                actualWidth = $tip[0].offsetWidth;
                actualHeight = $tip[0].offsetHeight;

                switch (inside ? placement.split(' ')[1] : placement) {
                    case 'bottom':
                        tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2};
                        break;
                    case 'top':
                        tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2};
                        break;
                    case 'left':
                        tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth};
                        break;
                    case 'right':
                        tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width};
                        break;
                }

                $tip
                .offset(tp)
                .addClass(placement)
                .addClass('in');

            }).call(this.container());
          /*jshint laxcomma: false*/
        }
    });

}(window.jQuery));<commit_msg>Fix 'bootstrap popover falls off page if editable is too close to window edge'<commit_after>/**
* Editable Popover
* ---------------------
* requires bootstrap-popover.js
*/
(function ($) {
    "use strict";

    //extend methods
    $.extend($.fn.editableContainer.Popup.prototype, {
        containerName: 'popover',
        //for compatibility with bootstrap <= 2.2.1 (content inserted into <p> instead of directly .popover-content)
        innerCss: $.fn.popover && $($.fn.popover.defaults.template).find('p').length ? '.popover-content p' : '.popover-content',

        initContainer: function(){
            $.extend(this.containerOptions, {
                trigger: 'manual',
                selector: false,
                content: ' ',
                template: $.fn.popover.defaults.template
            });

            //as template property is used in inputs, hide it from popover
            var t;
            if(this.$element.data('template')) {
               t = this.$element.data('template');
               this.$element.removeData('template');
            }

            this.call(this.containerOptions);

            if(t) {
               //restore data('template')
               this.$element.data('template', t);
            }
        },

        /* show */
        innerShow: function () {
            this.call('show');
        },

        /* hide */
        innerHide: function () {
            this.call('hide');
        },

        /* destroy */
        innerDestroy: function() {
            this.call('destroy');
        },

        setContainerOption: function(key, value) {
            this.container().options[key] = value;
        },

        /**
        * move popover to new position. This function mainly copied from bootstrap-popover.
        */
        /*jshint laxcomma: true*/
        setPosition: function () {

            (function() {
                var $tip = this.tip()
                , inside
                , pos
                , actualWidth
                , actualHeight
                , placement
                , tp
                , tpt
                , tpb
                , tpl
                , tpr;

                placement = typeof this.options.placement === 'function' ?
                this.options.placement.call(this, $tip[0], this.$element[0]) :
                this.options.placement;

                inside = /in/.test(placement);

                $tip
              //  .detach()
              //vitalets: remove any placement class because otherwise they dont influence on re-positioning of visible popover
                .removeClass('top right bottom left')
                .css({ top: 0, left: 0, display: 'block' });
              //  .insertAfter(this.$element);

                pos = this.getPosition(inside);

                actualWidth = $tip[0].offsetWidth;
                actualHeight = $tip[0].offsetHeight;

                placement = inside ? placement.split(' ')[1] : placement;

    			tpb = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
				tpt = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
				tpl = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
				tpr = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}

				switch (placement) {
					case 'bottom':
						if ((tpb.top + actualHeight) > ($(window).scrollTop() + $(window).height())) {
							if (tpt.top > $(window).scrollTop()) {
								placement = 'top'
							} else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
								placement = 'right'
							} else if (tpl.left > $(window).scrollLeft()) {
								placement = 'left'
							} else {
								placement = 'right'
							}
						}
						break
					case 'top':
						if (tpt.top < $(window).scrollTop()) {
							if ((tpb.top + actualHeight) < ($(window).scrollTop() + $(window).height())) {
								placement = 'bottom'
							} else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
								placement = 'right'
							} else if (tpl.left > $(window).scrollLeft()) {
								placement = 'left'
							} else {
								placement = 'right'
							}
						}
						break
					case 'left':
						if (tpl.left < $(window).scrollLeft()) {
							if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
								placement = 'right'
							} else if (tpt.top > $(window).scrollTop()) {
								placement = 'top'
							} else if (tpt.top > $(window).scrollTop()) {
								placement = 'bottom'
							} else {
								placement = 'right'
							}
						}
						break
					case 'right':
						if ((tpr.left + actualWidth) > ($(window).scrollLeft() + $(window).width())) {
							if (tpl.left > $(window).scrollLeft()) {
								placement = 'left'
							} else if (tpt.top > $(window).scrollTop()) {
								placement = 'top'
							} else if (tpt.top > $(window).scrollTop()) {
								placement = 'bottom'
							}
						}
						break
				}

				switch (placement) {
					case 'bottom':
						tp = tpb;
						break
					case 'top':
						tp = tpt;
						break
					case 'left':
						tp = tpl;
						break
					case 'right':
						tp = tpr;
						break
				}

                $tip
                .offset(tp)
                .addClass(placement)
                .addClass('in');

            }).call(this.container());
          /*jshint laxcomma: false*/
        }
    });

}(window.jQuery));
<|endoftext|>


<commit_before>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This is a fork/based on Small Tabs, written by ロシア,
 * https://addons.mozilla.org/en-US/firefox/addon/small-tabs/
 */

"use strict";

var EXPORTED_SYMBOLS = [ "ThinTabs" ];

Components.utils.import("resource://gre/modules/Services.jsm");

Components.utils.import("chrome://thintabs/content/javascript/CSSBuilder.js");
Components.utils.import("chrome://thintabs/content/javascript/DynamicStyleSheets.js");
Components.utils.import("chrome://thintabs/content/javascript/Preferences.js");

var ThinTabs = {
	destroy : function() {
		Preferences.destroy();
		DynamicStyleSheets.unregisterAll();
	},

	init : function() {
		DynamicStyleSheets.init();
		DynamicStyleSheets.registerPath("main", "resource://thintabs/content/css/main.css");

		Preferences.init("extensions.org.bonsaimind.thintabs.");
		this.initPreferences();
	},

	initPreferences : function() {
		Preferences.registerBool("close.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder(".tab-close-button:not([pinned])").hide();
				DynamicStyleSheets.register(name, css.toCSS());
			} else {
				DynamicStyleSheets.unregister(name);
			}
		});
		Preferences.registerInt("close.padding.top", -1, function(name, value) {
			var css = new CSSBuilder(".tab-close-button").autoPadding("top", value);
			DynamicStyleSheets.register(name, css.toCSS());
		});

		Preferences.registerBool("icon.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder(".tab-icon-image:not([pinned])").hide();
				DynamicStyleSheets.register(name, css.toCSS());
			} else {
				DynamicStyleSheets.unregister(name);
			}
		});
		Preferences.registerInt("icon.padding.top", -1, function(name, value) {
			var css = new CSSBuilder(".tab-icon-image").autoPadding("top", value);
			DynamicStyleSheets.register(name, css.toCSS());
		});

		Preferences.registerInt("tabs.height", 19, function(name, value) {
			var css = new CSSBuilder(".tab-background-start[selected=true]::after");
			css = css.addSelector(".tab-background-start[selected=true]::before");
			css = css.addSelector(".tab-background-start");
			css = css.addSelector(".tab-background-end");
			css = css.addSelector(".tab-background-end[selected=true]::after");
			css = css.addSelector(".tab-background-end[selected=true]::before");
			css = css.addSelector(".tabbrowser-tabs");
			css = css.addSelector("#tabs-toolbar"); // Thunderbird
			css = css.addSelector("#tabbar-toolbar"); // Thunderbird
			css = css.height(value);
			css = css.minHeight(value);
			DynamicStyleSheets.register(name, css.toCSS());
		});
		Preferences.registerBool("tabs.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#TabsToolbar");
				css = css.addSelector("#tabs-toolbar"); // Thunderbird
				css = css.hide();
				DynamicStyleSheets.register(name, css.toCSS());
			} else {
				DynamicStyleSheets.unregister(name);
			}
		});
		Preferences.registerInt("tabs.padding.end", 7, function(name, value) {
			var css = new CSSBuilder(".tab-content:not([pinned])").autoMozPadding("end", value);
			DynamicStyleSheets.register(name, css.toCSS());
		});
		Preferences.registerInt("tabs.padding.start", 1, function(name, value) {
			var css = new CSSBuilder(".tab-content:not([pinned])").autoMozPadding("start", value);
			DynamicStyleSheets.register(name, css.toCSS());
		});
		var tabsWidthFunction = function(name, value) {
			if (Preferences.getBool("tabs.width.override")) {
				var width = Preferences.getInt("tabs.width")
				var css = new CSSBuilder(".tabbrowser-tab");
				// TODO .tabmail-tab does not accept the forced width.
				css = css.addSelector(".tabmail-tab"); // Thunderbird
				css = css.forceWidth(width);
				DynamicStyleSheets.register("tabs.width", css.toCSS());
			} else {
				DynamicStyleSheets.unregister("tabs.width");
			}
		}
		Preferences.registerInt("tabs.width", 200, tabsWidthFunction);
		Preferences.registerBool("tabs.width.override", false, tabsWidthFunction);

		var fontFamilyFunction = function(name, value) {
			if (Preferences.getBool("text.font.family.override")) {
				var css = new CSSBuilder(".tab-text.tab-label:not([pinned])");
				css = css.fontFamily(Preferences.getChar("text.font.family"));
				DynamicStyleSheets.register("text.font.family", css.toCSS());
			} else {
				DynamicStyleSheets.unregister("text.font.family");
			}
		};
		Preferences.registerChar("text.font.family", "monospace", fontFamilyFunction);
		Preferences.registerBool("text.font.family.override", false, fontFamilyFunction);

		var fontSizeFunction = function(name, value) {
			if (Preferences.getBool("text.font.size.override")) {
				var css = new CSSBuilder(".tab-text.tab-label:not([pinned])");
				css = css.fontSize(Preferences.getInt("text.font.size"));
				DynamicStyleSheets.register("text.font.size", css.toCSS());
			} else {
				DynamicStyleSheets.unregister("text.font.size");
			}
		};
		Preferences.registerInt("text.font.size", 8, fontSizeFunction);
		Preferences.registerBool("text.font.size.override", false, fontSizeFunction);

		Preferences.registerBool("text.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder(".tab-text").hide();
				DynamicStyleSheets.register(name, css.toCSS());
			} else {
				DynamicStyleSheets.unregister(name);
			}
		});

		Preferences.registerInt("text.padding.top", 1, function(name, value) {
			var css = new CSSBuilder(".tab-text.tab-label:not([pinned])").autoPadding("top", value);
			DynamicStyleSheets.register(name, css.toCSS());
		});

		Preferences.registerBool("throbber.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder(".tab-throbber:not([pinned])").hide();
				DynamicStyleSheets.register(name, css.toCSS());
			} else {
				DynamicStyleSheets.unregister(name);
			}
		});
	}
};
<commit_msg>Started working on TabMixPlus support...nearly working.<commit_after>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This is a fork/based on Small Tabs, written by ロシア,
 * https://addons.mozilla.org/en-US/firefox/addon/small-tabs/
 */

"use strict";

var EXPORTED_SYMBOLS = [ "ThinTabs" ];

Components.utils.import("resource://gre/modules/Services.jsm");

Components.utils.import("chrome://thintabs/content/javascript/CSSBuilder.js");
Components.utils.import("chrome://thintabs/content/javascript/DynamicStyleSheets.js");
Components.utils.import("chrome://thintabs/content/javascript/Preferences.js");

var ThinTabs = {
	destroy : function() {
		Preferences.destroy();
		DynamicStyleSheets.unregisterAll();
	},

	init : function() {
		DynamicStyleSheets.init();
		DynamicStyleSheets.registerPath("main", "resource://thintabs/content/css/main.css");

		Preferences.init("extensions.org.bonsaimind.thintabs.");
		this.initPreferences();
	},

	initPreferences : function() {
		Preferences.registerBool("close.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder(".tab-close-button:not([pinned])").hide();
				DynamicStyleSheets.register(name, css.toCSS());
			} else {
				DynamicStyleSheets.unregister(name);
			}
		});
		Preferences.registerInt("close.padding.top", -1, function(name, value) {
			var css = new CSSBuilder(".tab-close-button").autoPadding("top", value);
			DynamicStyleSheets.register(name, css.toCSS());
		});

		Preferences.registerBool("icon.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder(".tab-icon-image:not([pinned])").hide();
				DynamicStyleSheets.register(name, css.toCSS());
			} else {
				DynamicStyleSheets.unregister(name);
			}
		});
		Preferences.registerInt("icon.padding.top", -1, function(name, value) {
			var css = new CSSBuilder(".tab-icon-image").autoPadding("top", value);
			DynamicStyleSheets.register(name, css.toCSS());
		});

		Preferences.registerInt("tabs.height", 19, function(name, value) {
			var css = new CSSBuilder(".tab-background-start[selected=true]::after");
			css = css.addSelector(".tab-background-start[selected=true]::before");
			css = css.addSelector(".tab-background-start");
			css = css.addSelector(".tab-background-end");
			css = css.addSelector(".tab-background-end[selected=true]::after");
			css = css.addSelector(".tab-background-end[selected=true]::before");
			//css = css.addSelector(".tabbrowser-tabs");
			//css = css.addSelector("#tabs-toolbar"); // Thunderbird
			//css = css.addSelector("#tabbar-toolbar"); // Thunderbird
			css = css.addSelector(".tabbrowser-tab");
			css = css.addSelector(".tabs-newtab-button");
			css = css.height(value);
			css = css.minHeight(value);
			DynamicStyleSheets.register(name, css.toCSS());

			var css2 = new CSSBuilder("#tabmixScrollBox");
			css2 = css2.forceHeight(value * 2); //Preferences.getInt("extensions.tabmix.tabBarMaxRow"));
			DynamicStyleSheets.register("test", css2.toCSS());

			var css3 = new CSSBuilder("#tabmixScrollBox > *");
			css3 = css3.addSelector("#alltabs-button");
			css3 = css3.addSelector("#new-tab-button");
			css3 = css3.addSelector("#tabmix-tabs-closebutton");
			css3 = css3.forceHeight(value);
			DynamicStyleSheets.register("test2", css3.toCSS());
		});
		Preferences.registerBool("tabs.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#TabsToolbar");
				css = css.addSelector("#tabs-toolbar"); // Thunderbird
				css = css.hide();
				DynamicStyleSheets.register(name, css.toCSS());
			} else {
				DynamicStyleSheets.unregister(name);
			}
		});
		Preferences.registerInt("tabs.padding.end", 7, function(name, value) {
			var css = new CSSBuilder(".tab-content:not([pinned])").autoMozPadding("end", value);
			DynamicStyleSheets.register(name, css.toCSS());
		});
		Preferences.registerInt("tabs.padding.start", 1, function(name, value) {
			var css = new CSSBuilder(".tab-content:not([pinned])").autoMozPadding("start", value);
			DynamicStyleSheets.register(name, css.toCSS());
		});
		var tabsWidthFunction = function(name, value) {
			if (Preferences.getBool("tabs.width.override")) {
				var width = Preferences.getInt("tabs.width")
				var css = new CSSBuilder(".tabbrowser-tab");
				// TODO .tabmail-tab does not accept the forced width.
				css = css.addSelector(".tabmail-tab"); // Thunderbird
				css = css.forceWidth(width);
				DynamicStyleSheets.register("tabs.width", css.toCSS());
			} else {
				DynamicStyleSheets.unregister("tabs.width");
			}
		}
		Preferences.registerInt("tabs.width", 200, tabsWidthFunction);
		Preferences.registerBool("tabs.width.override", false, tabsWidthFunction);

		var fontFamilyFunction = function(name, value) {
			if (Preferences.getBool("text.font.family.override")) {
				var css = new CSSBuilder(".tab-text.tab-label:not([pinned])");
				css = css.fontFamily(Preferences.getChar("text.font.family"));
				DynamicStyleSheets.register("text.font.family", css.toCSS());
			} else {
				DynamicStyleSheets.unregister("text.font.family");
			}
		};
		Preferences.registerChar("text.font.family", "monospace", fontFamilyFunction);
		Preferences.registerBool("text.font.family.override", false, fontFamilyFunction);

		var fontSizeFunction = function(name, value) {
			if (Preferences.getBool("text.font.size.override")) {
				var css = new CSSBuilder(".tab-text.tab-label:not([pinned])");
				css = css.fontSize(Preferences.getInt("text.font.size"));
				DynamicStyleSheets.register("text.font.size", css.toCSS());
			} else {
				DynamicStyleSheets.unregister("text.font.size");
			}
		};
		Preferences.registerInt("text.font.size", 8, fontSizeFunction);
		Preferences.registerBool("text.font.size.override", false, fontSizeFunction);

		Preferences.registerBool("text.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder(".tab-text").hide();
				DynamicStyleSheets.register(name, css.toCSS());
			} else {
				DynamicStyleSheets.unregister(name);
			}
		});

		Preferences.registerInt("text.padding.top", 1, function(name, value) {
			var css = new CSSBuilder(".tab-text.tab-label:not([pinned])").autoPadding("top", value);
			DynamicStyleSheets.register(name, css.toCSS());
		});

		Preferences.registerBool("throbber.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder(".tab-throbber:not([pinned])").hide();
				DynamicStyleSheets.register(name, css.toCSS());
			} else {
				DynamicStyleSheets.unregister(name);
			}
		});
	}
};
<|endoftext|>


<commit_before>(function() {

	"use strict";

	var root = this,
		Chart = root.Chart,
		helpers = Chart.helpers;

	Chart.defaults.line = {
		showLines: true,

		hover: {
			mode: "label"
		},

		scales: {
			xAxes: [{
				type: "category",
				id: 'x-axis-0'
			}],
			yAxes: [{
				type: "linear",
				id: 'y-axis-0'
			}],
		},
	};

	Chart.controllers.line = Chart.DatasetController.extend({
		addElements: function() {

			this.getDataset().metaData = this.getDataset().metaData || [];

			this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({
				_chart: this.chart.chart,
				_datasetIndex: this.index,
				_points: this.getDataset().metaData,
			});

			helpers.each(this.getDataset().data, function(value, index) {
				this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
					_chart: this.chart.chart,
					_datasetIndex: this.index,
					_index: index,
				});
			}, this);
		},
		addElementAndReset: function(index) {
			this.getDataset().metaData = this.getDataset().metaData || [];
			var point = new Chart.elements.Point({
				_chart: this.chart.chart,
				_datasetIndex: this.index,
				_index: index,
			});

			// Reset the point
			this.updateElement(point, index, true);

			// Add to the points array
			this.getDataset().metaData.splice(index, 0, point);

			// Make sure bezier control points are updated
			if (this.chart.options.showLines)
				this.updateBezierControlPoints();
		},

		update: function update(reset) {
			var line = this.getDataset().metaDataset;
			var points = this.getDataset().metaData;

			var yScale = this.getScaleForId(this.getDataset().yAxisID);
			var xScale = this.getScaleForId(this.getDataset().xAxisID);
			var scaleBase;

			if (yScale.min < 0 && yScale.max < 0) {
				scaleBase = yScale.getPixelForValue(yScale.max);
			} else if (yScale.min > 0 && yScale.max > 0) {
				scaleBase = yScale.getPixelForValue(yScale.min);
			} else {
				scaleBase = yScale.getPixelForValue(0);
			}

			// Update Line
			if (this.chart.options.showLines) {
				// Utility
				line._scale = yScale;
				line._datasetIndex = this.index;
				// Data
				line._children = points;
				// Model
				line._model = {
					// Appearance
					tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
					backgroundColor: line.custom && line.custom.backgroundColor ? line.custom.backgroundColor : (this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor),
					borderWidth: line.custom && line.custom.borderWidth ? line.custom.borderWidth : (this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth),
					borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor),
					borderCapStyle: line.custom && line.custom.borderCapStyle ? line.custom.borderCapStyle : (this.getDataset().borderCapStyle || this.chart.options.elements.line.borderCapStyle),
					borderDash: line.custom && line.custom.borderDash ? line.custom.borderDash : (this.getDataset().borderDash || this.chart.options.elements.line.borderDash),
					borderDashOffset: line.custom && line.custom.borderDashOffset ? line.custom.borderDashOffset : (this.getDataset().borderDashOffset || this.chart.options.elements.line.borderDashOffset),
					borderJoinStyle: line.custom && line.custom.borderJoinStyle ? line.custom.borderJoinStyle : (this.getDataset().borderJoinStyle || this.chart.options.elements.line.borderJoinStyle),
					fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill),
					// Scale
					scaleTop: yScale.top,
					scaleBottom: yScale.bottom,
					scaleZero: scaleBase
				};
				line.pivot();
			}

			// Update Points
			helpers.each(points, function(point, index) {
				this.updateElement(point, index, reset);
			}, this);

			if (this.chart.options.showLines)
				this.updateBezierControlPoints();
		},

		getPointBackgroundColor: function(point, index) {
			var backgroundColor = this.chart.options.elements.point.backgroundColor;
			var dataset = this.getDataset();

			if (point.custom && point.custom.backgroundColor) {
				backgroundColor = point.custom.backgroundColor;
			} else if (dataset.pointBackgroundColor) {
				backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
			} else if (dataset.backgroundColor) {
				backgroundColor = dataset.backgroundColor;
			}

			return backgroundColor;
		},
		getPointBorderColor: function(point, index) {
			var borderColor = this.chart.options.elements.point.borderColor;
			var dataset = this.getDataset();

			if (point.custom && point.custom.borderColor) {
				borderColor = point.custom.borderColor;
			} else if (dataset.pointBorderColor) {
				borderColor = helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, borderColor);
			} else if (dataset.borderColor) {
				borderColor = dataset.borderColor;
			}

			return borderColor;
		},
		getPointBorderWidth: function(point, index) {
			var borderWidth = this.chart.options.elements.point.borderWidth;
			var dataset = this.getDataset();

			if (point.custom && point.custom.borderWidth !== undefined) {
				borderWidth = point.custom.borderWidth;
			} else if (dataset.pointBorderWidth !== undefined) {
				borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
			} else if (dataset.borderWidth !== undefined) {
				borderWidth = dataset.borderWidth;
			}

			return borderWidth;
		},

		updateElement: function(point, index, reset) {
			var yScale = this.getScaleForId(this.getDataset().yAxisID);
			var xScale = this.getScaleForId(this.getDataset().xAxisID);
			var scaleBase;

			if (yScale.min < 0 && yScale.max < 0) {
				scaleBase = yScale.getPixelForValue(yScale.max);
			} else if (yScale.min > 0 && yScale.max > 0) {
				scaleBase = yScale.getPixelForValue(yScale.min);
			} else {
				scaleBase = yScale.getPixelForValue(0);
			}

			// Utility
			point._chart = this.chart.chart;
			point._xScale = xScale;
			point._yScale = yScale;
			point._datasetIndex = this.index;
			point._index = index;

			// Desired view properties
			point._model = {
				x: xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo),
				y: reset ? scaleBase : this.calculatePointY(this.getDataset().data[index], index, this.index, this.chart.isCombo),
				// Appearance
				tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
				radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius),
				backgroundColor: this.getPointBackgroundColor(point, index),
				borderColor: this.getPointBorderColor(point, index),
				borderWidth: this.getPointBorderWidth(point, index),
				// Tooltip
				hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius)
			};

			point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
		},

		calculatePointY: function(value, index, datasetIndex, isCombo) {

			var xScale = this.getScaleForId(this.getDataset().xAxisID);
			var yScale = this.getScaleForId(this.getDataset().yAxisID);

			if (yScale.options.stacked) {

				var sumPos = 0,
					sumNeg = 0;

				for (var i = this.chart.data.datasets.length - 1; i > datasetIndex; i--) {
					var ds = this.chart.data.datasets[i];
					if (ds.type === 'line' && helpers.isDatasetVisible(ds)) {
						if (ds.data[index] < 0) {
							sumNeg += ds.data[index] || 0;
						} else {
							sumPos += ds.data[index] || 0;
						}
					}
				}

				if (value < 0) {
					return yScale.getPixelForValue(sumNeg + value);
				} else {
					return yScale.getPixelForValue(sumPos + value);
				}

				return yScale.getPixelForValue(value);
			}

			return yScale.getPixelForValue(value);
		},

		updateBezierControlPoints: function() {
			// Update bezier control points
			helpers.each(this.getDataset().metaData, function(point, index) {
				var controlPoints = helpers.splineCurve(
					helpers.previousItem(this.getDataset().metaData, index)._model,
					point._model,
					helpers.nextItem(this.getDataset().metaData, index)._model,
					point._model.tension
				);

				// Prevent the bezier going outside of the bounds of the graph
				point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left);
				point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top);

				point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left);
				point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top);

				// Now pivot the point for animation
				point.pivot();
			}, this);
		},

		draw: function(ease) {
			var easingDecimal = ease || 1;

			// Transition Point Locations
			helpers.each(this.getDataset().metaData, function(point) {
				point.transition(easingDecimal);
			});

			// Transition and Draw the line
			if (this.chart.options.showLines)
				this.getDataset().metaDataset.transition(easingDecimal).draw();

			// Draw the points
			helpers.each(this.getDataset().metaData, function(point) {
				point.draw();
			});
		},

		setHoverStyle: function(point) {
			// Point
			var dataset = this.chart.data.datasets[point._datasetIndex];
			var index = point._index;

			point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
			point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
			point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString());
			point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, point._model.borderWidth);
		},

		removeHoverStyle: function(point) {
			var dataset = this.chart.data.datasets[point._datasetIndex];
			var index = point._index;

			point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius);
			point._model.backgroundColor = this.getPointBackgroundColor(point, index);
			point._model.borderColor = this.getPointBorderColor(point, index);
			point._model.borderWidth = this.getPointBorderWidth(point, index);
		}
	});
}).call(this);
<commit_msg>Do not calculate control points if we want straight lines<commit_after>(function() {

	"use strict";

	var root = this,
		Chart = root.Chart,
		helpers = Chart.helpers;

	Chart.defaults.line = {
		showLines: true,

		hover: {
			mode: "label"
		},

		scales: {
			xAxes: [{
				type: "category",
				id: 'x-axis-0'
			}],
			yAxes: [{
				type: "linear",
				id: 'y-axis-0'
			}],
		},
	};

	Chart.controllers.line = Chart.DatasetController.extend({
		addElements: function() {

			this.getDataset().metaData = this.getDataset().metaData || [];

			this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({
				_chart: this.chart.chart,
				_datasetIndex: this.index,
				_points: this.getDataset().metaData,
			});

			helpers.each(this.getDataset().data, function(value, index) {
				this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
					_chart: this.chart.chart,
					_datasetIndex: this.index,
					_index: index,
				});
			}, this);
		},
		addElementAndReset: function(index) {
			this.getDataset().metaData = this.getDataset().metaData || [];
			var point = new Chart.elements.Point({
				_chart: this.chart.chart,
				_datasetIndex: this.index,
				_index: index,
			});

			// Reset the point
			this.updateElement(point, index, true);

			// Add to the points array
			this.getDataset().metaData.splice(index, 0, point);

			// Make sure bezier control points are updated
			if (this.chart.options.showLines && this.chart.options.elements.line.tension !== 0)
				this.updateBezierControlPoints();
		},

		update: function update(reset) {
			var line = this.getDataset().metaDataset;
			var points = this.getDataset().metaData;

			var yScale = this.getScaleForId(this.getDataset().yAxisID);
			var xScale = this.getScaleForId(this.getDataset().xAxisID);
			var scaleBase;

			if (yScale.min < 0 && yScale.max < 0) {
				scaleBase = yScale.getPixelForValue(yScale.max);
			} else if (yScale.min > 0 && yScale.max > 0) {
				scaleBase = yScale.getPixelForValue(yScale.min);
			} else {
				scaleBase = yScale.getPixelForValue(0);
			}

			// Update Line
			if (this.chart.options.showLines) {
				// Utility
				line._scale = yScale;
				line._datasetIndex = this.index;
				// Data
				line._children = points;
				// Model
				line._model = {
					// Appearance
					tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
					backgroundColor: line.custom && line.custom.backgroundColor ? line.custom.backgroundColor : (this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor),
					borderWidth: line.custom && line.custom.borderWidth ? line.custom.borderWidth : (this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth),
					borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor),
					borderCapStyle: line.custom && line.custom.borderCapStyle ? line.custom.borderCapStyle : (this.getDataset().borderCapStyle || this.chart.options.elements.line.borderCapStyle),
					borderDash: line.custom && line.custom.borderDash ? line.custom.borderDash : (this.getDataset().borderDash || this.chart.options.elements.line.borderDash),
					borderDashOffset: line.custom && line.custom.borderDashOffset ? line.custom.borderDashOffset : (this.getDataset().borderDashOffset || this.chart.options.elements.line.borderDashOffset),
					borderJoinStyle: line.custom && line.custom.borderJoinStyle ? line.custom.borderJoinStyle : (this.getDataset().borderJoinStyle || this.chart.options.elements.line.borderJoinStyle),
					fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill),
					// Scale
					scaleTop: yScale.top,
					scaleBottom: yScale.bottom,
					scaleZero: scaleBase
				};
				line.pivot();
			}

			// Update Points
			helpers.each(points, function(point, index) {
				this.updateElement(point, index, reset);
			}, this);

			if (this.chart.options.showLines && this.chart.options.elements.line.tension !== 0)
				this.updateBezierControlPoints();
		},

		getPointBackgroundColor: function(point, index) {
			var backgroundColor = this.chart.options.elements.point.backgroundColor;
			var dataset = this.getDataset();

			if (point.custom && point.custom.backgroundColor) {
				backgroundColor = point.custom.backgroundColor;
			} else if (dataset.pointBackgroundColor) {
				backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
			} else if (dataset.backgroundColor) {
				backgroundColor = dataset.backgroundColor;
			}

			return backgroundColor;
		},
		getPointBorderColor: function(point, index) {
			var borderColor = this.chart.options.elements.point.borderColor;
			var dataset = this.getDataset();

			if (point.custom && point.custom.borderColor) {
				borderColor = point.custom.borderColor;
			} else if (dataset.pointBorderColor) {
				borderColor = helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, borderColor);
			} else if (dataset.borderColor) {
				borderColor = dataset.borderColor;
			}

			return borderColor;
		},
		getPointBorderWidth: function(point, index) {
			var borderWidth = this.chart.options.elements.point.borderWidth;
			var dataset = this.getDataset();

			if (point.custom && point.custom.borderWidth !== undefined) {
				borderWidth = point.custom.borderWidth;
			} else if (dataset.pointBorderWidth !== undefined) {
				borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
			} else if (dataset.borderWidth !== undefined) {
				borderWidth = dataset.borderWidth;
			}

			return borderWidth;
		},

		updateElement: function(point, index, reset) {
			var yScale = this.getScaleForId(this.getDataset().yAxisID);
			var xScale = this.getScaleForId(this.getDataset().xAxisID);
			var scaleBase;

			if (yScale.min < 0 && yScale.max < 0) {
				scaleBase = yScale.getPixelForValue(yScale.max);
			} else if (yScale.min > 0 && yScale.max > 0) {
				scaleBase = yScale.getPixelForValue(yScale.min);
			} else {
				scaleBase = yScale.getPixelForValue(0);
			}

			// Utility
			point._chart = this.chart.chart;
			point._xScale = xScale;
			point._yScale = yScale;
			point._datasetIndex = this.index;
			point._index = index;

			// Desired view properties
			point._model = {
				x: xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo),
				y: reset ? scaleBase : this.calculatePointY(this.getDataset().data[index], index, this.index, this.chart.isCombo),
				// Appearance
				tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
				radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius),
				backgroundColor: this.getPointBackgroundColor(point, index),
				borderColor: this.getPointBorderColor(point, index),
				borderWidth: this.getPointBorderWidth(point, index),
				// Tooltip
				hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius)
			};

			point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
		},

		calculatePointY: function(value, index, datasetIndex, isCombo) {

			var xScale = this.getScaleForId(this.getDataset().xAxisID);
			var yScale = this.getScaleForId(this.getDataset().yAxisID);

			if (yScale.options.stacked) {

				var sumPos = 0,
					sumNeg = 0;

				for (var i = this.chart.data.datasets.length - 1; i > datasetIndex; i--) {
					var ds = this.chart.data.datasets[i];
					if (ds.type === 'line' && helpers.isDatasetVisible(ds)) {
						if (ds.data[index] < 0) {
							sumNeg += ds.data[index] || 0;
						} else {
							sumPos += ds.data[index] || 0;
						}
					}
				}

				if (value < 0) {
					return yScale.getPixelForValue(sumNeg + value);
				} else {
					return yScale.getPixelForValue(sumPos + value);
				}

				return yScale.getPixelForValue(value);
			}

			return yScale.getPixelForValue(value);
		},

		updateBezierControlPoints: function() {
			// Update bezier control points
			helpers.each(this.getDataset().metaData, function(point, index) {
				var controlPoints = helpers.splineCurve(
					helpers.previousItem(this.getDataset().metaData, index)._model,
					point._model,
					helpers.nextItem(this.getDataset().metaData, index)._model,
					point._model.tension
				);

				// Prevent the bezier going outside of the bounds of the graph
				point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left);
				point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top);

				point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left);
				point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top);

				// Now pivot the point for animation
				point.pivot();
			}, this);
		},

		draw: function(ease) {
			var easingDecimal = ease || 1;

			// Transition Point Locations
			helpers.each(this.getDataset().metaData, function(point) {
				point.transition(easingDecimal);
			});

			// Transition and Draw the line
			if (this.chart.options.showLines)
				this.getDataset().metaDataset.transition(easingDecimal).draw();

			// Draw the points
			helpers.each(this.getDataset().metaData, function(point) {
				point.draw();
			});
		},

		setHoverStyle: function(point) {
			// Point
			var dataset = this.chart.data.datasets[point._datasetIndex];
			var index = point._index;

			point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
			point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
			point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString());
			point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, point._model.borderWidth);
		},

		removeHoverStyle: function(point) {
			var dataset = this.chart.data.datasets[point._datasetIndex];
			var index = point._index;

			point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius);
			point._model.backgroundColor = this.getPointBackgroundColor(point, index);
			point._model.borderColor = this.getPointBorderColor(point, index);
			point._model.borderWidth = this.getPointBorderWidth(point, index);
		}
	});
}).call(this);
<|endoftext|>


<commit_before>/**
 This Source Code is licensed under the MIT license. If a copy of the
 MIT-license was not distributed with this file, You can obtain one at:
 http://opensource.org/licenses/mit-license.html.

 @author: Tom Clement (tjclement)
 @license MIT
 @copyright Bizboard, 2015

 */

import {DataSource}         from '../../core/DataSource';
import ObjectHelper         from '../../utils/objectHelper';
import {Context}            from 'arva-context/Context';
import EventEmitter         from 'eventemitter3';

export default
class PrioritisedArray extends Array {

    /* Extending Array does not work fluently yet. The length property always returns 0,
     * regardless of how many entries are in the array. We'll override the length prop to determine
     * the amount of enumerable properties in our PrioritisedArray instead of using the built-in length property.
     */
    get length() {
        return Object.keys(this).length
    }

    set length(value) {
    }

    /**
     *
     * @param {Function} dataType
     * @param {DataSource} dataSource
     * @param {Snapshot} dataSnapshot
     */
    constructor(dataType, dataSource = null, dataSnapshot = null) {
        super();
        /**** Callbacks ****/
        this._valueChangedCallback = null;

        /**** Private properties ****/
        this._dataType = dataType;
        this._dataSource = dataSource;
        this._isBeingReordered = false; /* Flag to determine when we're reordering so we don't listen to move updates */
        this._eventEmitter = new EventEmitter();

        /* Bind all local methods to the current object instance, so we can refer to "this"
         * in the methods as expected, even when they're called from event handlers.        */
        ObjectHelper.bindAllMethods(this, this);

        /* Hide all private properties (starting with '_') and methods from enumeration,
         * so when you do for( in ), only actual data properties show up. */
        ObjectHelper.hideMethodsAndPrivatePropertiesFromObject(this);

        /* Hide the priority field from enumeration, so we don't save it to the dataSource. */
        ObjectHelper.hidePropertyFromObject(Object.getPrototypeOf(this), 'length');

        /* If no dataSource is given, create own one with guessed path */
        if(!dataSource) {
            let modelName = Object.getPrototypeOf(this).constructor.name;
            let path = modelName; //+ 's';

            // retrieve dataSource from the DI context
            dataSource = Context.getContext().get(DataSource);

            dataSource = dataSource.child(path);
            this._dataSource = dataSource;
        }

        /* If a snapshot is present use it, otherwise generate one by subscribing to the dataSource one time. */
        if (dataSnapshot) {
            this._buildFromSnapshot(dataSnapshot);
        } else {
            this._buildFromDataSource(dataSource);
        }
    }

    /**
     * Subscribes to events emitted by this PrioritisedArray.
     * @param {String} event 'value', 'child_changed', 'child_moved', 'child_removed'
     * @param {Function} fn Function to call when event is emitted.
     * @param {Object} context Object to bind the given callback function to.
     * @returns {*}
     */
    on(event, fn, context) {
        return this._eventEmitter.on(event, fn, context);
    }

    /**
     * Removes subscription to events emitted by this PrioritisedArray. If no fn or context is given, all handlers for
     * the given event are removed. If no parameters are given at all, all event types will have their handlers removed.
     * @param {String} event 'value', 'child_changed', 'child_moved', 'child_removed'
     * @param {Function} fn Function to call when event is emitted.
     * @param {Object} context Object to bind the given callback function to.
     * @returns {*}
     */
    off(event, fn, context) {

        if(event && (fn || context)) {
            this._eventEmitter.removeListener(event, fn, context);
        } else {
            this._eventEmitter.removeAllListeners(event);
        }
    }

    /**
     * Adds a model instance to the rear of the PrioritisedArray
     * @param {Model} model Subclass of Model
     * @returns {*} Same model as the one originally passed as parameter
     */
    add(model) {
        if (model instanceof this._dataType) {
            if (this._findIndexById(model.id) < 0) {
                model.priority = this.length;
                this.push(model);

                if (!model._inheritable) {
                    model.on('value', (modelData) => {
                        this._onChildChanged(modelData);
                    });
                }

            } else {
                /* TODO: change to throw exception */
                console.log('Tried to append an object with the same ID as one already present.');
            }
        }
        /* Let's try to parse the object using property reflection */
        else if(model instanceof Object) {
            // retrieve dataSource from the DI context
            var options = { dataSource: this._dataSource};
            let newModel = new this._dataType(null, model, options);
            this.add(newModel);
        }
        else {
            /* TODO: change to throw exception */
            console.log('Tried to append an object that is not the same type as the one this PrioritisedArray was created with.');
        }

        /* Return model so we can do this: let newModel = PrioArray.add(new Model()); newModel.someProperty = true; */
        return model;
    }

    /**
     * Inserts a model instance at the given position of the PrioritisedArray, and recalculates the priority (position)
     * of all models after the inserted position.
     * @param {Model} model Subclass of Model
     * @param {Number} position Zero-based index where to put the new model instance.
     */
    insertAt(model, position) {
        if (model instanceof this._dataType) {
            this.splice(position, 0, model);
            this._recalculatePriorities(position);
        }
        else {
            /* TODO: change to throw exception */
            console.log('Tried to append an object that is not the same type as the PrioritisedArray was created with.');
        }

        /* Return model so we can do this: let newModel = PrioArray.add(new Model()); newModel.someProperty = true; */
        return model;
    }

    /**
     * Moves a model instance from one position to another.
     * @param {Number} fromPosition Zero-based index of original position
     * @param {Number} toPosition Zero-based index of target position
     */
    move(fromPosition, toPosition) {
        let model = this[fromPosition];
        this.splice(fromPosition, 1);
        this.splice(toPosition, 0, model);
        this._recalculatePriorities();
    }

    /**
     * Removes the model instance at the given position. Does not remove the model from the datasource, to do that
     * call model.remove() directly, or PrioArray[index].remove().
     * @param position
     */
    remove(position) {
        this.splice(position, 1);
    }

    /**
     * Assigns models' priorities based on their position in the PrioritisedArray.
     * @param {Number} start Index to start calculation from, so we don't process unnecessary models.
     * @private
     */
    _recalculatePriorities(start = 0) {
        this._isBeingReordered = true;
        for (let i = start; i < this.length; i++) {
            this[i].priority = i;
        }
        this._isBeingReordered = false;
    }

    /**
     * Interprets all childs of a given snapshot as instances of the given data type for this PrioritisedArray,
     * and attempts to instantiate new model instances based on these sub-snapshots. It adds them to the
     * PrioritisedArray, which also assigns their priority based on their inserted position.
     * @param {Snapshot} dataSnapshot
     * @private
     */
    _buildFromSnapshot(dataSnapshot) {
        dataSnapshot.forEach(
            /** @param {Snapshot} child **/
            function(child){
                /* Create a new instance of the given data type and prefill it with the snapshot data. */
                let options = {dataSnapshot: child};

                /* whenever the ref() is a datasource, we can bind that source to the model.
                 * whenever it's not a datasource, we assume the model should instantiate a new
                  * datasource to bind the model */

                 if (child.ref() instanceof DataSource)
                    options.dataSource = child.ref();
                 else {
                     var rootPath = child.ref().root().toString();
                     options.path = child.ref().toString().replace(rootPath,'/');
                 }

                let newModel = new this._dataType(child.key(), child.val(), options);
                this.add(newModel);

            }.bind(this));

        if (dataSnapshot.ref() instanceof DataSource &&
            dataSnapshot.ref().inheritable)
            this._eventEmitter.emit('value', this);

        //this._registerCallbacks(this._dataSource);
    }

    /**
     * Clones a dataSource (to not disturb any existing callbacks defined on the original) and uses it
     * to get a dataSnapshot which is used in _buildSnapshot to build our array.
     * @param dataSource
     * @private
     */
    _buildFromDataSource(dataSource) {

        let path = dataSource.path();
        let DataSource = Object.getPrototypeOf(dataSource).constructor;
        let newSource = new DataSource(path);
        newSource.setValueChangedCallback((dataSnapshot) => {
            newSource.removeValueChangedCallback();
            this._buildFromSnapshot(dataSnapshot);
            this._registerCallbacks(newSource);
        });

    }

    _registerCallbacks(dataSource) {
        dataSource.setChildAddedCallback(this._onChildAdded);
        dataSource.setChildMovedCallback(this._onChildMoved);
        if (dataSource.inheritable)
            dataSource.setChildChangedCallback(this._onChildChanged);
        dataSource.setChildRemovedCallback(this._onChildRemoved);
    }

    /**
     * Called by dataSource when a new child is added.
     * @param {Snapshot} snapshot
     * @private
     */
    _onChildAdded(snapshot) {
        let id = snapshot.key();
        let model = this.add(new this._dataType(id, null, {dataSnapshot: snapshot}));

        this._eventEmitter.emit('child_added', model);
        this._eventEmitter.emit('value', this);
    }

    /**
     *
     */
    _onChildChanged(snapshot) {
        let id = snapshot.key();
        let itemIndex = this._findIndexById(id);
        let changedModel = new this._dataType(id, null, {dataSnapshot: snapshot, dataSource: snapshot.ref() });

        if (!(JSON.stringify(this[itemIndex])===JSON.stringify(changedModel))) {
            this[itemIndex] = changedModel;

            this._eventEmitter.emit('child_changed', changedModel);
            this._eventEmitter.emit('value', this);
        }
    }

    /**
     * Called by dataSource when a child is moved, which changes its priority.
     * @param {Snapshot} snapshot
     * @private
     */
    _onChildMoved(snapshot, upper) {
        /* Ignore priority updates whilst we're reordering to avoid floods */
        if (!this._isBeingReordered) {

            let id = snapshot.key();
            let previousPosition = this._findIndexById(id);
            let tempModel = this[previousPosition];
            this.remove(previousPosition);

            let newPosition = this._findIndexById(upper)+1;
            this.insertAt(tempModel, newPosition);

            this._recalculatePriorities();

            let model = this[newPosition];

            this._eventEmitter.emit('child_moved', model, previousPosition);
            this._eventEmitter.emit('value', this);
        }
    }

    /**
     * Called by dataSource when a child is removed.
     * @param {Snapshot} oldSnapshot
     * @private
     */
    _onChildRemoved(oldSnapshot) {
        /* TODO: figure out if we can use the snapshot's priority as our array index reliably, to avoid big loops. */
        let id = oldSnapshot.key();
        let position = this._findIndexById(id);
        let model = this[position];

        if (position !== -1) {
            this.remove(position, false);

            this._eventEmitter.emit('child_removed', model);
            this._eventEmitter.emit('value', this);
        }
    }

    /**
     * Searches for the index in the PrioritisedArray of a model that has an id equal to the given id.
     * @param id Id field of the model we're looking for
     * @returns {Number} Zero-based index if found, -1 otherwise
     * @private
     */
    _findIndexById(id) {
        for (let i = 0; i < this.length; i++) {
            if (this[i].id == id) {
                return (i);
            }
        }
        return -1;
    }

}<commit_msg>implemented once ready AND move child.<commit_after>/**
 This Source Code is licensed under the MIT license. If a copy of the
 MIT-license was not distributed with this file, You can obtain one at:
 http://opensource.org/licenses/mit-license.html.

 @author: Tom Clement (tjclement)
 @license MIT
 @copyright Bizboard, 2015

 */

import {DataSource}         from '../../core/DataSource';
import ObjectHelper         from '../../utils/objectHelper';
import {Context}            from 'arva-context/Context';
import EventEmitter         from 'eventemitter3';

export default
class PrioritisedArray extends Array {

    /* Extending Array does not work fluently yet. The length property always returns 0,
     * regardless of how many entries are in the array. We'll override the length prop to determine
     * the amount of enumerable properties in our PrioritisedArray instead of using the built-in length property.
     */
    get length() {
        return Object.keys(this).length
    }

    set length(value) {
    }

    /**
     *
     * @param {Function} dataType
     * @param {DataSource} dataSource
     * @param {Snapshot} dataSnapshot
     */
    constructor(dataType, dataSource = null, dataSnapshot = null) {
        super();
        /**** Callbacks ****/
        this._valueChangedCallback = null;

        /**** Private properties ****/
        this._dataType = dataType;
        this._dataSource = dataSource;
        this._isBeingReordered = false; /* Flag to determine when we're reordering so we don't listen to move updates */
        this._eventEmitter = new EventEmitter();

        /* Bind all local methods to the current object instance, so we can refer to "this"
         * in the methods as expected, even when they're called from event handlers.        */
        ObjectHelper.bindAllMethods(this, this);

        /* Hide all private properties (starting with '_') and methods from enumeration,
         * so when you do for( in ), only actual data properties show up. */
        ObjectHelper.hideMethodsAndPrivatePropertiesFromObject(this);

        /* Hide the priority field from enumeration, so we don't save it to the dataSource. */
        ObjectHelper.hidePropertyFromObject(Object.getPrototypeOf(this), 'length');

        /* If no dataSource is given, create own one with guessed path */
        if(!dataSource) {
            let modelName = Object.getPrototypeOf(this).constructor.name;
            let path = modelName; //+ 's';

            // retrieve dataSource from the DI context
            dataSource = Context.getContext().get(DataSource);

            dataSource = dataSource.child(path);
            this._dataSource = dataSource;
        }

        /* If a snapshot is present use it, otherwise generate one by subscribing to the dataSource one time. */
        if (dataSnapshot) {
            this._buildFromSnapshot(dataSnapshot);
        } else {
            this._buildFromDataSource(dataSource);
        }
    }

    /**
     *
     * @param event
     * @param fn
     * @param context
     * @returns {*}
     */
    once(event, fn, context = this) {
        return this.on(event, function(){
            fn.call(context, arguments);
            this.off(event, fn, context);
        }, this);
    }

    /**
     * Subscribes to events emitted by this PrioritisedArray.
     * @param {String} event 'value', 'child_changed', 'child_moved', 'child_removed'
     * @param {Function} fn Function to call when event is emitted.
     * @param {Object} context Object to bind the given callback function to.
     * @returns {*}
     */
    on(event, fn, context) {
        /* If we're already ready, fire immediately */
        if(event === 'ready' && this._dataSource && this._dataSource.ready) { fn.call(context, this); }

        return this._eventEmitter.on(event, fn, context);
    }

    /**
     * Removes subscription to events emitted by this PrioritisedArray. If no fn or context is given, all handlers for
     * the given event are removed. If no parameters are given at all, all event types will have their handlers removed.
     * @param {String} event 'value', 'child_changed', 'child_moved', 'child_removed'
     * @param {Function} fn Function to call when event is emitted.
     * @param {Object} context Object to bind the given callback function to.
     * @returns {*}
     */
    off(event, fn, context) {

        if(event && (fn || context)) {
            this._eventEmitter.removeListener(event, fn, context);
        } else {
            this._eventEmitter.removeAllListeners(event);
        }
    }

    /**
     * Adds a model instance to the rear of the PrioritisedArray
     * @param {Model} model Subclass of Model
     * @returns {*} Same model as the one originally passed as parameter
     */
    add(model) {
        if (model instanceof this._dataType) {
            if (this._findIndexById(model.id) < 0) {
                model.priority = this.length;
                this.push(model);

                if (!model._inheritable) {
                    model.on('value', (modelData) => {
                        this._onChildChanged(modelData);
                    });
                }

            } else {
                /* TODO: change to throw exception */
                console.log('Tried to append an object with the same ID as one already present.');
            }
        }
        /* Let's try to parse the object using property reflection */
        else if(model instanceof Object) {
            // retrieve dataSource from the DI context
            var options = { dataSource: this._dataSource};
            let newModel = new this._dataType(null, model, options);
            this.add(newModel);
        }
        else {
            /* TODO: change to throw exception */
            console.log('Tried to append an object that is not the same type as the one this PrioritisedArray was created with.');
        }

        /* Return model so we can do this: let newModel = PrioArray.add(new Model()); newModel.someProperty = true; */
        return model;
    }

    /**
     * Inserts a model instance at the given position of the PrioritisedArray, and recalculates the priority (position)
     * of all models after the inserted position.
     * @param {Model} model Subclass of Model
     * @param {Number} position Zero-based index where to put the new model instance.
     */
    insertAt(model, position) {
        if (model instanceof this._dataType) {
            this.splice(position, 0, model);
            this._recalculatePriorities(position);
        }
        else {
            /* TODO: change to throw exception */
            console.log('Tried to append an object that is not the same type as the PrioritisedArray was created with.');
        }

        /* Return model so we can do this: let newModel = PrioArray.add(new Model()); newModel.someProperty = true; */
        return model;
    }

    /**
     * Moves a model instance from one position to another.
     * @param {Number} fromPosition Zero-based index of original position
     * @param {Number} toPosition Zero-based index of target position
     */
    move(fromPosition, toPosition) {
        let model = this[fromPosition];
        this.splice(fromPosition, 1);
        this.splice(toPosition, 0, model);
        this._recalculatePriorities();
    }

    /**
     * Removes the model instance at the given position. Does not remove the model from the datasource, to do that
     * call model.remove() directly, or PrioArray[index].remove().
     * @param position
     */
    remove(position) {
        this.splice(position, 1);
    }

    /**
     * Assigns models' priorities based on their position in the PrioritisedArray.
     * @param {Number} start Index to start calculation from, so we don't process unnecessary models.
     * @private
     */
    _recalculatePriorities(start = 0) {
        this._isBeingReordered = true;
        for (let i = start; i < this.length; i++) {
            this[i].priority = i;
        }
        this._isBeingReordered = false;
    }

    /**
     * Interprets all childs of a given snapshot as instances of the given data type for this PrioritisedArray,
     * and attempts to instantiate new model instances based on these sub-snapshots. It adds them to the
     * PrioritisedArray, which also assigns their priority based on their inserted position.
     * @param {Snapshot} dataSnapshot
     * @private
     */
    _buildFromSnapshot(dataSnapshot) {

        let numChildren = dataSnapshot.numChildren(), currentChild = 1;

        /* If there is no data at this point yet, fire a ready event */
        if (numChildren === 0) {
            this._dataSource.ready = true;
            this.emit('ready');
        }

        dataSnapshot.forEach(
            /** @param {Snapshot} child **/
            function(child){
                /* Create a new instance of the given data type and prefill it with the snapshot data. */
                let options = {dataSnapshot: child};

                /* whenever the ref() is a datasource, we can bind that source to the model.
                 * whenever it's not a datasource, we assume the model should instantiate a new
                  * datasource to bind the model */

                 if (child.ref() instanceof DataSource)
                    options.dataSource = child.ref();
                 else {
                     var rootPath = child.ref().root().toString();
                     options.path = child.ref().toString().replace(rootPath,'/');
                 }

                let newModel = new this._dataType(child.key(), child.val(), options);
                this.add(newModel);

                /* If this is the last child, fire a ready event */
                if(currentChild++ == numChildren){
                    this._dataSource.ready = true;
                    this.emit('ready');
                }

            }.bind(this));

        if (dataSnapshot.ref() instanceof DataSource &&
            dataSnapshot.ref().inheritable)
            this._eventEmitter.emit('value', this);

        //this._registerCallbacks(this._dataSource);
    }

    /**
     * Clones a dataSource (to not disturb any existing callbacks defined on the original) and uses it
     * to get a dataSnapshot which is used in _buildSnapshot to build our array.
     * @param dataSource
     * @private
     */
    _buildFromDataSource(dataSource) {

        let path = dataSource.path();
        let DataSource = Object.getPrototypeOf(dataSource).constructor;
        let newSource = new DataSource(path);
        newSource.setValueChangedCallback((dataSnapshot) => {
            newSource.removeValueChangedCallback();
            this._buildFromSnapshot(dataSnapshot);
            this._registerCallbacks(newSource);
        });

    }

    _registerCallbacks(dataSource) {
        dataSource.setChildAddedCallback(this._onChildAdded);
        dataSource.setChildMovedCallback(this._onChildMoved);
        if (dataSource.inheritable)
            dataSource.setChildChangedCallback(this._onChildChanged);
        dataSource.setChildRemovedCallback(this._onChildRemoved);
    }

    /**
     * Called by dataSource when a new child is added.
     * @param {Snapshot} snapshot
     * @private
     */
    _onChildAdded(snapshot) {
        let id = snapshot.key();
        let model = this.add(new this._dataType(id, null, {dataSnapshot: snapshot}));

        this._eventEmitter.emit('child_added', model);
        this._eventEmitter.emit('value', this);
    }

    /**
     *
     */
    _onChildChanged(snapshot) {
        let id = snapshot.key();
        let itemIndex = this._findIndexById(id);
        let changedModel = new this._dataType(id, null, {dataSnapshot: snapshot, dataSource: snapshot.ref() });

        if (!(JSON.stringify(this[itemIndex])===JSON.stringify(changedModel))) {
            this[itemIndex] = changedModel;

            this._eventEmitter.emit('child_changed', changedModel);
            this._eventEmitter.emit('value', this);
        }
    }

    /**
     * Called by dataSource when a child is moved, which changes its priority.
     * @param {Snapshot} snapshot
     * @private
     */
    _onChildMoved(snapshot, prevSiblingId) {
        /* Ignore priority updates whilst we're reordering to avoid floods */
        if (!this._isBeingReordered) {

            let id = snapshot.key();
            let previousPosition = this._findIndexById(id);
            let tempModel = this[previousPosition];
            this.remove(previousPosition);

            let newPosition = this._findIndexById(prevSiblingId)+1;
            this.insertAt(tempModel, newPosition);

            this._recalculatePriorities();

            let model = this[newPosition];

            this._eventEmitter.emit('child_moved', model, previousPosition);
            this._eventEmitter.emit('value', this);
        }
    }

    /**
     * Called by dataSource when a child is removed.
     * @param {Snapshot} oldSnapshot
     * @private
     */
    _onChildRemoved(oldSnapshot) {
        /* TODO: figure out if we can use the snapshot's priority as our array index reliably, to avoid big loops. */
        let id = oldSnapshot.key();
        let position = this._findIndexById(id);
        let model = this[position];

        if (position !== -1) {
            this.remove(position, false);

            this._eventEmitter.emit('child_removed', model);
            this._eventEmitter.emit('value', this);
        }
    }

    /**
     * Searches for the index in the PrioritisedArray of a model that has an id equal to the given id.
     * @param id Id field of the model we're looking for
     * @returns {Number} Zero-based index if found, -1 otherwise
     * @private
     */
    _findIndexById(id) {
        for (let i = 0; i < this.length; i++) {
            if (this[i].id == id) {
                return (i);
            }
        }
        return -1;
    }

}<|endoftext|>


<commit_before>var appWithPublic = path.join(__dirname, 'app-with-public');
var appWithPrivate = path.join(__dirname, 'app-with-private');

// These tests make some assumptions about the structure of stars: that there
// are client and server programs inside programs/.

console.log("Bundle app with public/ directory");
assert.doesNotThrow(function () {
  var lib = new library.Library({
    localPackageDirs: [ path.join(files.getCurrentToolsDir(), 'packages') ]
  });
  var tmpOutputDir = tmpDir();
  var result = bundler.bundle(appWithPublic, tmpOutputDir, {
    nodeModulesMode: 'skip',
    library: lib,
    releaseStamp: 'none'
  });
  var clientManifest = JSON.parse(
    fs.readFileSync(
      path.join(tmpOutputDir, "programs", "client", "program.json")
    )
  );
  var staticDir = path.join(tmpOutputDir, "programs",
                            "client", clientManifest.static);
  var testTxtPath = path.join(staticDir, "test.txt");
  var nestedTxtPath = path.join(staticDir, "nested", "nested.txt");
  assert.strictEqual(result.errors, false, result.errors && result.errors[0]);
  assert(fs.existsSync(testTxtPath));
  assert(fs.existsSync(nestedTxtPath));
  assert.strictEqual(fs.readFileSync(testTxtPath, "utf8").trim(),
                     "Test");
  assert.strictEqual(fs.readFileSync(nestedTxtPath, "utf8").trim(),
                     "Nested");
});

console.log("Bundle app with private/ directory and package asset");
assert.doesNotThrow(function () {
  var lib = new library.Library({
    localPackageDirs: [ path.join(files.getCurrentToolsDir(), 'packages'),
                      path.join(appWithPrivate, "packages") ]
  });
  var tmpOutputDir = tmpDir();
  var result = bundler.bundle(appWithPrivate, tmpOutputDir, {
    nodeModulesMode: 'skip',
    library: lib,
    releaseStamp: 'none'
  });
  var serverManifest = JSON.parse(
    fs.readFileSync(
      path.join(tmpOutputDir, "programs", "server",
                "program.json")
    )
  );
  var staticDir;
  var packageTxtPath;
  _.each(serverManifest.load, function (item) {
    console.log(item.path);
    if (item.path === "/packages/test-package.js") {
      packageTxtPath = path.join(tmpOutputDir,
                                 "programs", "server",
                                 item.staticDirectory, "test-package.txt");
    }
    if (item.path === "/app/test.js") {
      staticDir = path.join(tmpOutputDir,
                            "programs", "server",
                            item.staticDirectory);
    }
  });
  // Check that the files are where the manifest says they are
  var testTxtPath = path.join(staticDir, "test.txt");
  var nestedTxtPath = path.join(staticDir, "nested", "test.txt");
  assert.strictEqual(result.errors, false, result.errors && result.errors[0]);
  assert(fs.existsSync(testTxtPath));
  assert(fs.existsSync(nestedTxtPath));
  assert(fs.existsSync(packageTxtPath));
  assert.strictEqual(fs.readFileSync(testTxtPath, "utf8").trim(), "Test");
  assert.strictEqual(fs.readFileSync(nestedTxtPath, "utf8").trim(), "Nested");
  assert.strictEqual(fs.readFileSync(packageTxtPath, "utf8").trim(), "Package");

  // Run the app to check that Assets.getText/Binary do the right things.
  var cp = require('child_process');
  var meteor = path.join(__dirname, "..", "..", "meteor"); // XXX is this allowed?
  var fut = new Future();
  var proc = cp.spawn(meteor, ["--once"], {
    cwd: path.join(__dirname, "app-with-private")
  });
  proc.on("exit", function (code) {
    fut.return(code);
  });
  assert.strictEqual(fut.wait(), 0);
});
<commit_msg>Remove extra log from assets test<commit_after>var appWithPublic = path.join(__dirname, 'app-with-public');
var appWithPrivate = path.join(__dirname, 'app-with-private');

// These tests make some assumptions about the structure of stars: that there
// are client and server programs inside programs/.

console.log("Bundle app with public/ directory");
assert.doesNotThrow(function () {
  var lib = new library.Library({
    localPackageDirs: [ path.join(files.getCurrentToolsDir(), 'packages') ]
  });
  var tmpOutputDir = tmpDir();
  var result = bundler.bundle(appWithPublic, tmpOutputDir, {
    nodeModulesMode: 'skip',
    library: lib,
    releaseStamp: 'none'
  });
  var clientManifest = JSON.parse(
    fs.readFileSync(
      path.join(tmpOutputDir, "programs", "client", "program.json")
    )
  );
  var staticDir = path.join(tmpOutputDir, "programs",
                            "client", clientManifest.static);
  var testTxtPath = path.join(staticDir, "test.txt");
  var nestedTxtPath = path.join(staticDir, "nested", "nested.txt");
  assert.strictEqual(result.errors, false, result.errors && result.errors[0]);
  assert(fs.existsSync(testTxtPath));
  assert(fs.existsSync(nestedTxtPath));
  assert.strictEqual(fs.readFileSync(testTxtPath, "utf8").trim(),
                     "Test");
  assert.strictEqual(fs.readFileSync(nestedTxtPath, "utf8").trim(),
                     "Nested");
});

console.log("Bundle app with private/ directory and package asset");
assert.doesNotThrow(function () {
  var lib = new library.Library({
    localPackageDirs: [ path.join(files.getCurrentToolsDir(), 'packages'),
                      path.join(appWithPrivate, "packages") ]
  });
  var tmpOutputDir = tmpDir();
  var result = bundler.bundle(appWithPrivate, tmpOutputDir, {
    nodeModulesMode: 'skip',
    library: lib,
    releaseStamp: 'none'
  });
  var serverManifest = JSON.parse(
    fs.readFileSync(
      path.join(tmpOutputDir, "programs", "server",
                "program.json")
    )
  );
  var staticDir;
  var packageTxtPath;
  _.each(serverManifest.load, function (item) {
    if (item.path === "/packages/test-package.js") {
      packageTxtPath = path.join(tmpOutputDir,
                                 "programs", "server",
                                 item.staticDirectory, "test-package.txt");
    }
    if (item.path === "/app/test.js") {
      staticDir = path.join(tmpOutputDir,
                            "programs", "server",
                            item.staticDirectory);
    }
  });
  // Check that the files are where the manifest says they are
  var testTxtPath = path.join(staticDir, "test.txt");
  var nestedTxtPath = path.join(staticDir, "nested", "test.txt");
  assert.strictEqual(result.errors, false, result.errors && result.errors[0]);
  assert(fs.existsSync(testTxtPath));
  assert(fs.existsSync(nestedTxtPath));
  assert(fs.existsSync(packageTxtPath));
  assert.strictEqual(fs.readFileSync(testTxtPath, "utf8").trim(), "Test");
  assert.strictEqual(fs.readFileSync(nestedTxtPath, "utf8").trim(), "Nested");
  assert.strictEqual(fs.readFileSync(packageTxtPath, "utf8").trim(), "Package");

  // Run the app to check that Assets.getText/Binary do the right things.
  var cp = require('child_process');
  var meteor = path.join(__dirname, "..", "..", "meteor"); // XXX is this allowed?
  var fut = new Future();
  var proc = cp.spawn(meteor, ["--once"], {
    cwd: path.join(__dirname, "app-with-private")
  });
  proc.on("exit", function (code) {
    fut.return(code);
  });
  assert.strictEqual(fut.wait(), 0);
});
<|endoftext|>


<commit_before>// Requires
var Q = require('q');
var _ = require('underscore');

var path = require('path');

var utils = require('../utils');

function ProjectRunner(events, workspace, shells, run_ports, project_detect, urlPattern) {
    // Id for port allocation and stuff
    this.id = 'project';

    this.events = events;
    this.workspace = workspace;

    this.shells = shells;

    this.run_ports = run_ports;
    this.project_detect = project_detect;

    this.urlPattern = urlPattern;

    _.bindAll(this);
}

ProjectRunner.prototype.scriptPath = function(projectType) {
    return path.resolve(__dirname, 'scripts', projectType + '.sh');
};

ProjectRunner.prototype.shellId = function(projectType, port) {
    return [this.id, projectType, port].join('-');
};

ProjectRunner.prototype.portId = function(projectType) {
    return [this.id, projectType].join('-');
};

ProjectRunner.prototype.runScript = function(projectType, port) {
    var self = this;

    // Id of our shell
    var shellId = this.shellId(projectType, port);

    // Command to run after the script itself
    // this keeps the shell open
    var exitCMD = "read -p  $'####\\n# Press \\e[00;31mENTER\\e[00m to close this shell ...\\n####\\n'";

    // Spawn the new shell
    var shell = this.shells.createShellCommand(
        shellId, [
            // Script itself
            this.scriptPath(projectType),

            // Path to project folder
            this.workspace.root,

            // Port allocated/claimed
            port
        ], {
        cwd: this.workspace.root,
        env: _.defaults({
            PORT: port,
            HTTP_PORT: port
        }, process.env)
    });

    // Id of our harbor port (to release)
    var portId = self.portId(projectType);

    // Free port on shell exit
    self.shells.shells[shellId].ps.once('exit', function() {
        self.run_ports.release(portId);
    });

    return {
        shellId: shellId,
        type: projectType,
        port: port,
        url: this.urlPattern.replace("%d", port)
    };
};

ProjectRunner.prototype.detect = function() {
    return this.project_detect.detect(this.workspace.root);
};

ProjectRunner.prototype.isRunning = function(portId) {
    return _.has(this.run_ports.ports, portId);
};

ProjectRunner.prototype.run = function() {
    var self = this;

    // Check if project has a specific type
    return this.detect()
    .then(function(projectType) {
        if(!projectType) {
            return Q.reject(new Error("The project has no supported type"));
        }
        return projectType;
    })
    .then(function(projectType) {
        var portId = self.portId(projectType);

        if(self.isRunning(portId)) {
            return Q.reject(new Error("Project is already running"));
        }

        // Now that we have the type, claim a port
        // then run the script
        return Q.all([
            projectType,

            // Get a new port
            self.run_ports.claim(portId)
        ])
        .spread(function(projectType, port) {
            // Run the script in a new shell and return
            // a object describing the run
            return self.runScript(projectType, port);
        });
    });
};

// Exports
exports.ProjectRunner = ProjectRunner;
<commit_msg>Remove the now useless exitCMD from cb.run.project<commit_after>// Requires
var Q = require('q');
var _ = require('underscore');

var path = require('path');

var utils = require('../utils');

function ProjectRunner(events, workspace, shells, run_ports, project_detect, urlPattern) {
    // Id for port allocation and stuff
    this.id = 'project';

    this.events = events;
    this.workspace = workspace;

    this.shells = shells;

    this.run_ports = run_ports;
    this.project_detect = project_detect;

    this.urlPattern = urlPattern;

    _.bindAll(this);
}

ProjectRunner.prototype.scriptPath = function(projectType) {
    return path.resolve(__dirname, 'scripts', projectType + '.sh');
};

ProjectRunner.prototype.shellId = function(projectType, port) {
    return [this.id, projectType, port].join('-');
};

ProjectRunner.prototype.portId = function(projectType) {
    return [this.id, projectType].join('-');
};

ProjectRunner.prototype.runScript = function(projectType, port) {
    var self = this;

    // Id of our shell
    var shellId = this.shellId(projectType, port);

    // Spawn the new shell
    var shell = this.shells.createShellCommand(
        shellId, [
            // Script itself
            this.scriptPath(projectType),

            // Path to project folder
            this.workspace.root,

            // Port allocated/claimed
            port
        ], {
        cwd: this.workspace.root,
        env: _.defaults({
            PORT: port,
            HTTP_PORT: port
        }, process.env)
    });

    // Id of our harbor port (to release)
    var portId = self.portId(projectType);

    // Free port on shell exit
    self.shells.shells[shellId].ps.once('exit', function() {
        self.run_ports.release(portId);
    });

    return {
        shellId: shellId,
        type: projectType,
        port: port,
        url: this.urlPattern.replace("%d", port)
    };
};

ProjectRunner.prototype.detect = function() {
    return this.project_detect.detect(this.workspace.root);
};

ProjectRunner.prototype.isRunning = function(portId) {
    return _.has(this.run_ports.ports, portId);
};

ProjectRunner.prototype.run = function() {
    var self = this;

    // Check if project has a specific type
    return this.detect()
    .then(function(projectType) {
        if(!projectType) {
            return Q.reject(new Error("The project has no supported type"));
        }
        return projectType;
    })
    .then(function(projectType) {
        var portId = self.portId(projectType);

        if(self.isRunning(portId)) {
            return Q.reject(new Error("Project is already running"));
        }

        // Now that we have the type, claim a port
        // then run the script
        return Q.all([
            projectType,

            // Get a new port
            self.run_ports.claim(portId)
        ])
        .spread(function(projectType, port) {
            // Run the script in a new shell and return
            // a object describing the run
            return self.runScript(projectType, port);
        });
    });
};

// Exports
exports.ProjectRunner = ProjectRunner;
<|endoftext|>