<commit_before>/*    _____                    _____
     /\    \                  /\    \
    /::\    \                /::\    \
    \:::\    f\              /::::\    \
     \:::\    \            /::::::\    \
      \:::\    \          /:::/\:::\    \
       \:::\    \        /:::/  \:::\    \
       /::::\    \      /:::/    \:::\    \
      /::::::\    \    /:::/    / \:::\    \
     /:::/\:::\    \  /:::/    /   \:::\    \
    /:::/  \:::\____\/:::/____/     \:::\____\
   /:::/    \::/    /\:::\    \      \::/    /
  /:::/    / \/____/  \:::\    \      \/____/
 /:::/    /            \:::\    \
/:::/    /              \:::\    \
\::/    /                \:::\    \
 \/____/                  \:::\    \
                           \:::\    \
                            \:::\____\
                             \::/    /
                              \/____/
                             TYPE/CODE
                         From 2010 till ∞     */

(function(window, $) {

	var NI = window.NI,
	    console = NI.app.getConsole(true);

	var regex = {
		email: /^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i,

		url: /^((ht|f)tps?:\/\/)?[a-z0-9-\.]+\.[a-z]{2,4}\/?([^\s<>\%"\,\{\}\\|\\\^\[\]`]+)?$/,

		alphanumeric:  /^\s*[a-z\d\.]+([a-z\d\.]*\.|\s*\-\s*[a-z\d\.]+)?(\s+[a-z\d\.]+(\.|\s*\-\s*[a-z\d\.]+)?)*\s*$/i,

		number: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/
	};

	function regTest(s, key) {
		return regex[key].test(s);
	}

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

	var is = {

		alphaNumeric: function(s) {
			return regTest(s, "alphanumeric");
		},

		email: function(s) {
			return regTest(s, "email");
		},

		URL: function(s) {
			return regTest(s, "url");
		},

		num: function(s) {
			return regTest(s, "number");
		},

		numNonNegative: function(s) {
			return isNum(s) && Number(s) >= 0;
		},

		numPositive: function(s) {
			return isNum(s) && Number(s) > 0;
		},

		date: function(s) {
			var day, month, year, buf, date;
			if (s) {
				buf = s.split("/");
				if (buf.length == 3) {
					month = buf[0].replace(/^0+/,'');
					day = buf[1].replace(/^0+/,'');
					year = buf[2].replace(/^0+/,'');
					try {
						date = new Date();
						month = (month*1) - 1;
						date.setFullYear(year, month, day);
						if (date.getMonth() == month &&
						    date.getDate() == day &&
						    date.getFullYear() == year) {
								return true;
							}
					} catch(err) {
						// ignore
					}
				}
				return false;
			}
			return true;
		}
	};

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

	function ValidationToken() {
		this.clean();
	}
	ValidationToken.prototype = {
		errors: function() {
			return this.errs.length ?
				this.errs : false;
		},
		addError: function(str) {
			this.errs.push(str);
			return this;
		},
		clean: function() {
			this.errs = [];
			return this;
		}
	};

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

	var events = {
		fieldValidationPass: function(e, d) {
			$(e.target).removeClass("state-invalid").addClass("state-valid");
			if (e.data.manager.o.$mother instanceof $) {
				e.data.manager.o.$mother.trigger("validationPass", d);
			}
			return false;
		},
		fieldValidationFail: function(e, d) {
			$(e.target).removeClass("state-valid").addClass("state-invalid");
			if (e.data.manager.o.$mother instanceof $) {
				e.data.manager.o.$mother.trigger("validationFail", d);
			}
			return false;
		},
		fieldValidationReset: function(e, d) {
			$(e.target).removeClass("state-valid state-invalid");
			return false;
		},
		fieldChange: function(e, d) {
			validate($(e.target));
		},
		fieldKeyup: function(e, d) {
			validate($(e.target));
		},
		managedFieldPass: function(e, d) {
			if (e.data.manager.isValid()) {
				$(e.target).removeClass("state-invalid").addClass("state-valid");
			}
			return false;
		},
		managedFieldFail: function(e, d) {
			$(e.target).removeClass("state-valid").addClass("state-invalid");
			return false;
		}
	};

	function validate(field) {
		var value,
		    validators,
		    token,
		    errors,
		    type;

		switch (field[0].nodeName.toLowerCase()) {
			case "input":
				switch (field.attr("type")) {
					case "radio":
					case "checkbox":
						type = "bool";
						if (field.attr("checked")) {
							value = "true";
						} else {
							value = "false";
						}
						break;
					default:
						value = field.val();
						break;
				}
				break;
			case "textarea":
				value = field.val();
				break;
			case "select":
				type = "select";
				value = field.find("option:selected").attr("value");
				break;
			default:
				value = false;
				break;
		}
		NI.ex.checkStr(value);

		validators = field.data("validators");
		token = field.data("vtoken");

		if (!validators || !token) {
			throw new Error("Field not prepared for validation");
		}
		token.clean();

		$.each(validators, function(j, v) {
			var t, validation;
			t = typeof v;
			if (t === "string") {
				v = v.toLowerCase();
				if ($.isFunction(ValidationManager.validators[v])) {
					validation = ValidationManager.validators[v](value);
					if (validation && validation.errors && validation.errors.length) {
						$.each(validation.errors, function(k, err) {
							token.addError(err);
						});
					}
				} else if (v.indexOf("maxlen") === 0) {
					(function(max) {
						if (value.length > max) {
							token.addError("This cannot exceed "+ max +" characters");
						}
					}(v.split("=")[1]));
				} else if (v.indexOf("minlen") === 0) {
					(function(min) {
						if (value.length < min) {
							token.addError("This must be at least "+ min +" characters");
						}
					}(v.split("=")[1]));
				} else {
					switch (v) {
						case "required":
							(function() {
								var err;
								err = "This is a required field";
								if (type === "bool") {
									if (value === "false") {
										token.addError(err);
									}
								} else if (type === "select") {
									if (value === "-1") {
										token.addError(err);
									}
								} else {
									if (NI.ex.isEmpty(value)) {
										token.addError(err);
									}
								}
							}());
							break;
						case "alphanumeric":
							if (!is.alphaNumeric(value)) {
								token.addError("This is not a valid alpha-numeric value");
							}
							break;
						case "email":
							if (!is.email(value)) {
								token.addError("This is not a valid Email address");
							}
							break;
						case "url":
							if (!is.URL(value)) {
								token.addError("This is not a valid URL");
							}
							break;
						case "number":
							if (!is.num(value)) {
								token.addError("This is not a valid number");
							}
							break;
						case "date":
							if (!is.date(value)) {
								token.addError("This is not a valid date");
							}
							break;
						default:
							break;
					}
				}
			} else if (t === "function") {
				validation = v(value);
				if (validation.errors && validation.errors.length) {
					$.each(validation.errors, function(k, err) {
						token.addError(err);
					});
				}
			}
		});

		errors = token.errors();
		if (errors) {
			field.trigger("validationFail", { errors: errors });
			return { valid: false, errors: errors };
		}
		field.trigger("validationPass");
		return { valid: true };
	}

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

	function ValidationManager(options) {
		var me = this;

		this.o = $.extend({
			$mother: null,
			spec: [],
			watchKeypress: false
		}, options);

		this.fields = [];
		$.each(this.o.spec, function(i, item) {
			var field;
			field = typeof item.element === "string" ? $(item.element) : item.element;
			if (!field.length || !item.validators) {
				return true;
			}

			field.data("validators", item.validators);
			field.data("vtoken", new ValidationToken());

			field.bind("validationPass", { manager:me }, events.fieldValidationPass)
			     .bind("validationFail", { manager:me }, events.fieldValidationFail)
			     .bind("validationReset", { manager:me }, events.fieldValidationReset)
			     .bind("change", { manager:me }, events.fieldChange);
			if (me.o.watchKeypress) {
				field.bind("keyup", { manager:me }, events.fieldKeyup);
			}

			me.fields.push(field);
		});

		if (this.o.$mother instanceof $) {
			this.o.$mother.bind("validationPass", { manager:this }, events.managedFieldPass)
			  .bind("validationFail", { manager:this }, events.managedFieldFail);
		}
	}
	ValidationManager.prototype = {
		validate: function() {
			var errorCount = 0;

			$.each(this.fields, function(i, field) {
				var validation = validate(field);
				if (!validation.valid) {
					errorCount += validation.errors.length;
				}
			});

			return errorCount === 0;
		},
		isValid: function() {
			var i;
			for (i = 0; i < this.fields.length; i += 1) {
				if (this.fields[i].data("vtoken").errors()) {
					return false;
				}
			}
			return true;
		},
		reset: function() {
			$.each(this.fields, function(i, field) {
				field.data("vtoken").clean();
				field.trigger("validationReset");
			});
			if (this.o.$mother instanceof $) {
				this.$c.removeClass("state-valid state-invalid");
			}
			this.invalidFieldCount = 0;
			return this;
		}
	};

	var RESERVED_KEYS =
		["required",
		"alphanumeric",
		"email",
		"url",
		"number",
		"date"];

	ValidationManager.validators = {};

	ValidationManager.registerValidator = function(key, fn) {
		NI.ex.checkStr(key);
		NI.ex.checkFn(fn);
		key = key.toLowerCase();
		if (RESERVED_KEYS.indexOf(key) === -1) {
			ValidationManager.validators[key] = fn;
		} else {
			console.warn("Cannot override the built-in validator: "+ key);
		}
	};

	ValidationManager.makeRangeValidator = function(low, high, note) {
		note = note || "This must be between "+ low +" and "+ high;
		return function(value) {
			value = Number(value);
			if (value < low || value > high) {
				return { valid: false, errors: [note] };
			}
			return { valid: true };
		};
	};

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

	NI.ValidationManager = ValidationManager;
	NI.is = $.extend(NI.is || {}, is);

}(this, this.jQuery));<commit_msg>more validation manager stuff<commit_after>/*    _____                    _____
     /\    \                  /\    \
    /::\    \                /::\    \
    \:::\    f\              /::::\    \
     \:::\    \            /::::::\    \
      \:::\    \          /:::/\:::\    \
       \:::\    \        /:::/  \:::\    \
       /::::\    \      /:::/    \:::\    \
      /::::::\    \    /:::/    / \:::\    \
     /:::/\:::\    \  /:::/    /   \:::\    \
    /:::/  \:::\____\/:::/____/     \:::\____\
   /:::/    \::/    /\:::\    \      \::/    /
  /:::/    / \/____/  \:::\    \      \/____/
 /:::/    /            \:::\    \
/:::/    /              \:::\    \
\::/    /                \:::\    \
 \/____/                  \:::\    \
                           \:::\    \
                            \:::\____\
                             \::/    /
                              \/____/
                             TYPE/CODE
                         From 2010 till ∞     */

(function(window, $) {

	var NI = window.NI,
	    console = NI.app.getConsole(true);

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// regular expressions

	var regex = {
		email: /^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i,

		url: /^((ht|f)tps?:\/\/)?[a-z0-9-\.]+\.[a-z]{2,4}\/?([^\s<>\%"\,\{\}\\|\\\^\[\]`]+)?$/,

		alphanumeric:  /^\s*[a-z\d\.]+([a-z\d\.]*\.|\s*\-\s*[a-z\d\.]+)?(\s+[a-z\d\.]+(\.|\s*\-\s*[a-z\d\.]+)?)*\s*$/i,

		number: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/
	};

	function regTest(s, key) {
		return regex[key].test(s);
	}

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// tests

	var is = {

		empty: function(s) {
			return NI.ex.isEmpty(s);
		},

		alphaNumeric: function(s) {
			return regTest(s, "alphanumeric");
		},

		email: function(s) {
			return regTest(s, "email");
		},

		url: function(s) {
			return regTest(s, "url");
		},

		num: function(s) {
			return regTest(s, "number");
		},

		numNonNegative: function(s) {
			return isNum(s) && Number(s) >= 0;
		},

		numPositive: function(s) {
			return isNum(s) && Number(s) > 0;
		},

		date: function(s) {
			var day, month, year, buf, date;
			if (s) {
				buf = s.split("/");
				if (buf.length == 3) {
					month = buf[0].replace(/^0+/,'');
					day = buf[1].replace(/^0+/,'');
					year = buf[2].replace(/^0+/,'');
					if (!(year.length == 2 || year.length == 4)) {
						return false;
					}
					try {
						date = new Date();
						month = (month*1) - 1;
						date.setFullYear(year, month, day);
						if (date.getMonth() == month &&
						    date.getDate() == day &&
						    date.getFullYear() == year) {
								return true;
							}
					} catch(err) {
						// ignore
					}
				}
				return false;
			}
			return true;
		}
	};

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// built-in validators

	var built_ins = {

		required: "This is a required field",

		alphanumeric: "This is not a valid alpha-numeeric value",

		email: "This is not a valid Email address",

		url: "This is not a valid URL",

		number: "This is not a valid number",

		date: "This is not a valid date"
	};

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// field validation

	function ValidationToken() {
		this.clean();
	}
	ValidationToken.prototype = {
		errors: function() {
			return this.errs.length ?
				this.errs : false;
		},
		addError: function(str) {
			this.errs.push(str);
			return this;
		},
		clean: function() {
			this.errs = [];
			return this;
		}
	};

	var events = {
		fieldValidationPass: function(e, d) {
			$(e.target).removeClass("state-invalid").addClass("state-valid");
			if (e.data.manager.o.$mother instanceof $) {
				e.data.manager.o.$mother.trigger("validationPass", d);
			}
			return false;
		},
		fieldValidationFail: function(e, d) {
			$(e.target).removeClass("state-valid").addClass("state-invalid");
			if (e.data.manager.o.$mother instanceof $) {
				e.data.manager.o.$mother.trigger("validationFail", d);
			}
			return false;
		},
		fieldValidationReset: function(e, d) {
			$(e.target).removeClass("state-valid state-invalid");
			return false;
		},
		fieldChange: function(e, d) {
			validate($(e.target));
		},
		fieldKeyup: function(e, d) {
			validate($(e.target));
		},
		managedFieldPass: function(e, d) {
			if (e.data.manager.isValid()) {
				$(e.target).removeClass("state-invalid").addClass("state-valid");
			}
			return false;
		},
		managedFieldFail: function(e, d) {
			$(e.target).removeClass("state-valid").addClass("state-invalid");
			return false;
		}
	};

	function validate(field) {
		var value,
		    validators,
		    token,
		    errors,
		    type;

		switch (field[0].nodeName.toLowerCase()) {
			case "input":
				switch (field.attr("type")) {
					case "radio":
					case "checkbox":
						type = "bool";
						if (field.attr("checked")) {
							value = "true";
						} else {
							value = "false";
						}
						break;
					default:
						value = field.val();
						break;
				}
				break;
			case "textarea":
				value = field.val();
				break;
			case "select":
				type = "select";
				value = field.find("option:selected").attr("value");
				break;
			default:
				value = false;
				break;
		}
		NI.ex.checkStr(value);

		validators = field.data("validators");
		token = field.data("vtoken");

		if (!validators || !token) {
			throw new Error("Field not prepared for validation");
		}
		token.clean();

		$.each(validators, function(j, v) {
			var t, validation;
			t = typeof v;
			if (t === "string") {
				v = v.toLowerCase();
				if ($.isFunction(ValidationManager.validators[v])) {
					validation = ValidationManager.validators[v](value);
					if (validation && validation.errors && validation.errors.length) {
						$.each(validation.errors, function(k, err) {
							token.addError(err);
						});
					}
				} else if (v.indexOf("maxlen") === 0) {
					(function(max) {
						if (value.length > max) {
							token.addError("This cannot exceed "+ max +" characters");
						}
					}(v.split("=")[1]));
				} else if (v.indexOf("minlen") === 0) {
					(function(min) {
						if (value.length < min) {
							token.addError("This must be at least "+ min +" characters");
						}
					}(v.split("=")[1]));
				} else {
					switch (v) {
						case "required":
							(function() {
								var err;
								err = built_ins["required"];
								if (type === "bool") {
									if (value === "false") {
										token.addError(err);
									}
								} else if (type === "select") {
									if (value === "-1") {
										token.addError(err);
									}
								} else {
									if (NI.ex.isEmpty(value)) {
										token.addError(err);
									}
								}
							}());
							break;
						case "alphanumeric":
							if (!is.alphaNumeric(value)) {
								token.addError(built_ins["alphanumeric"]);
							}
							break;
						case "email":
							if (!is.email(value)) {
								token.addError(built_ins["email"]);
							}
							break;
						case "url":
							if (!is.url(value)) {
								token.addError(built_ins["url"]);
							}
							break;
						case "number":
							if (!is.num(value)) {
								token.addError(built_ins["number"]);
							}
							break;
						case "date":
							if (!is.date(value)) {
								token.addError(built_ins["date"]);
							}
							break;
						default:
							break;
					}
				}
			} else if (t === "function") {
				validation = v(value);
				if (validation.errors && validation.errors.length) {
					$.each(validation.errors, function(k, err) {
						token.addError(err);
					});
				}
			}
		});

		errors = token.errors();
		if (errors) {
			field.trigger("validationFail", { errors: errors });
			return { valid: false, errors: errors };
		}
		field.trigger("validationPass");
		return { valid: true };
	}

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// manager

	function ValidationManager(options) {
		var me = this;

		this.o = $.extend({
			$mother: null,
			spec: [],
			watchKeypress: false
		}, options);

		this.fields = [];
		$.each(this.o.spec, function(i, item) {
			var field;
			field = typeof item.element === "string" ? $(item.element) : item.element;
			if (!field.length || !item.validators) {
				return true;
			}

			field.data("validators", item.validators);
			field.data("vtoken", new ValidationToken());

			field.bind("validationPass", { manager:me }, events.fieldValidationPass)
			     .bind("validationFail", { manager:me }, events.fieldValidationFail)
			     .bind("validationReset", { manager:me }, events.fieldValidationReset)
			     .bind("change", { manager:me }, events.fieldChange);
			if (me.o.watchKeypress) {
				field.bind("keyup", { manager:me }, events.fieldKeyup);
			}

			me.fields.push(field);
		});

		if (this.o.$mother instanceof $) {
			this.o.$mother.bind("validationPass", { manager:this }, events.managedFieldPass)
			  .bind("validationFail", { manager:this }, events.managedFieldFail);
		}
	}
	ValidationManager.prototype = {
		validate: function() {
			var errorCount = 0;

			$.each(this.fields, function(i, field) {
				var validation = validate(field);
				if (!validation.valid) {
					errorCount += validation.errors.length;
				}
			});

			return errorCount === 0;
		},
		isValid: function() {
			var i;
			for (i = 0; i < this.fields.length; i += 1) {
				if (this.fields[i].data("vtoken").errors()) {
					return false;
				}
			}
			return true;
		},
		reset: function() {
			$.each(this.fields, function(i, field) {
				field.data("vtoken").clean();
				field.trigger("validationReset");
			});
			if (this.o.$mother instanceof $) {
				this.$c.removeClass("state-valid state-invalid");
			}
			this.invalidFieldCount = 0;
			return this;
		}
	};

	ValidationManager.validators = {};

	ValidationManager.registerValidator = function(key, fn) {
		NI.ex.checkStr(key);
		NI.ex.checkFn(fn);
		key = key.toLowerCase();
		if (built_ins[key]) {
			console.warn("Cannot override the built-in validator: "+ key);
			return;
		}
		ValidationManager.validators[key] = fn;
	};

	ValidationManager.registerErrorMessage = function(key, note) {
		NI.ex.checkStr(key);
		NI.ex.checkStr(note);
		if (built_ins[key]) {
			built_ins[key] = note;
		} else {

		}
	};

	ValidationManager.makeRangeValidator = function(low, high, note) {
		note = note || "This must be between "+ low +" and "+ high;
		return function(value) {
			value = Number(value);
			if (value < low || value > high) {
				return { valid: false, errors: [note] };
			}
			return { valid: true };
		};
	};

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

	NI.ValidationManager = ValidationManager;
	NI.is = $.extend(NI.is || {}, is);

}(this, this.jQuery));<|endoftext|>


<commit_before>var crypto = require('crypto')
var fs = require('fs')
var path = require('path')

var minimatch = require('minimatch')

var Resolve = null

function PackageTranspilationRegistry () {
  this.configByPackagePath = {}
  this.specByFilePath = {}
  this.transpilerPaths = {}
}

Object.assign(PackageTranspilationRegistry.prototype, {
  addTranspilerConfigForPath: function (packagePath, config) {
    packagePath = fs.realpathSync(packagePath)
    this.configByPackagePath[packagePath] = {
      specs: config,
      path: packagePath
    }
  },

  removeTranspilerConfigForPath: function (packagePath) {
    packagePath = fs.realpathSync(packagePath)
    delete this.configByPackagePath[packagePath]
  },

  // Wraps the transpiler in an object with the same interface
  // that falls back to the original transpiler implementation if and
  // only if a package hasn't registered its desire to transpile its own source.
  wrapTranspiler: function (transpiler) {
    var self = this
    return {
      getCachePath: function (sourceCode, filePath) {
        var spec = self.getPackageTranspilerSpecForFilePath(filePath)
        if (spec) {
          return self.getCachePath(sourceCode, filePath, spec)
        }

        return transpiler.getCachePath(sourceCode, filePath)
      },

      compile: function (sourceCode, filePath) {
        var spec = self.getPackageTranspilerSpecForFilePath(filePath)
        if (spec) {
          return self.transpileWithPackageTranspiler(sourceCode, filePath, spec)
        }

        return transpiler.compile(sourceCode, filePath)
      },

      shouldCompile: function (sourceCode, filePath) {
        if (self.transpilerPaths[filePath]) {
          return false
        }
        var spec = self.getPackageTranspilerSpecForFilePath(filePath)
        if (spec) {
          return true
        }

        return transpiler.shouldCompile(sourceCode, filePath)
      }
    }
  },

  getPackageTranspilerSpecForFilePath: function (filePath) {
    if (this.specByFilePath[filePath] !== undefined) return this.specByFilePath[filePath]

    var config = null
    var spec = null
    var thisPath = filePath
    var lastPath = null
    // Iterate parents from the file path to the root, checking at each level
    // to see if a package manages transpilation for that directory.
    // This means searching for a config for `/path/to/file/here.js` only
    // only iterates four times, even if there are hundreds of configs registered.
    while (thisPath !== lastPath) { // until we reach the root
      if (config = this.configByPackagePath[thisPath]) { // eslint-disable-line no-cond-assign
        for (var i = 0; i < config.specs.length; i++) {
          spec = config.specs[i]
          if (minimatch(filePath, path.join(config.path, spec.glob))) {
            spec._config = config
            this.specByFilePath[filePath] = spec
            return spec
          }
        }
      }

      lastPath = thisPath
      thisPath = path.resolve(thisPath, '..')
    }

    this.specByFilePath[filePath] = null
    return null
  },

  getCachePath: function (sourceCode, filePath, spec) {
    var transpilerPath = path.join(spec._config.path, spec.transpiler)
    var transpilerSource = spec._transpilerSource || fs.readFileSync(transpilerPath, 'utf8')
    spec._transpilerSource = transpilerSource
    return path.join(
      'package-transpile',
      crypto
        .createHash('sha1')
        .update(JSON.stringify(spec.options || {}))
        .update(transpilerSource, 'utf8')
        .update(sourceCode, 'utf8')
        .digest('hex')
    )
  },

  transpileWithPackageTranspiler: function (sourceCode, filePath, spec) {
    Resolve = Resolve || require('resolve')
    var transpilerPath = Resolve.sync(spec.transpiler, {
      basedir: spec._config.path,
      extensions: Object.keys(require.extensions)
    })

    if (transpilerPath) {
      this.transpilerPaths[transpilerPath] = true
      var transpiler = require(transpilerPath)
      var result = transpiler.compile(sourceCode, filePath, spec.options || {})
      if (result === undefined) {
        return sourceCode
      } else {
        return result
      }
    } else {
      var err = new Error("Could not resolve transpiler '" + spec.transpiler + "' from '" + spec._config.path + "'")
      console.error(err)
    }
  }
})

module.exports = PackageTranspilationRegistry
<commit_msg>Include user data in cache calculation<commit_after>var crypto = require('crypto')
var fs = require('fs')
var path = require('path')

var minimatch = require('minimatch')

var Resolve = null

function PackageTranspilationRegistry () {
  this.configByPackagePath = {}
  this.specByFilePath = {}
  this.transpilerPaths = {}
}

Object.assign(PackageTranspilationRegistry.prototype, {
  addTranspilerConfigForPath: function (packagePath, config) {
    packagePath = fs.realpathSync(packagePath)
    this.configByPackagePath[packagePath] = {
      specs: config,
      path: packagePath
    }
  },

  removeTranspilerConfigForPath: function (packagePath) {
    packagePath = fs.realpathSync(packagePath)
    delete this.configByPackagePath[packagePath]
  },

  // Wraps the transpiler in an object with the same interface
  // that falls back to the original transpiler implementation if and
  // only if a package hasn't registered its desire to transpile its own source.
  wrapTranspiler: function (transpiler) {
    var self = this
    return {
      getCachePath: function (sourceCode, filePath) {
        var spec = self.getPackageTranspilerSpecForFilePath(filePath)
        if (spec) {
          return self.getCachePath(sourceCode, filePath, spec)
        }

        return transpiler.getCachePath(sourceCode, filePath)
      },

      compile: function (sourceCode, filePath) {
        var spec = self.getPackageTranspilerSpecForFilePath(filePath)
        if (spec) {
          return self.transpileWithPackageTranspiler(sourceCode, filePath, spec)
        }

        return transpiler.compile(sourceCode, filePath)
      },

      shouldCompile: function (sourceCode, filePath) {
        if (self.transpilerPaths[filePath]) {
          return false
        }
        var spec = self.getPackageTranspilerSpecForFilePath(filePath)
        if (spec) {
          return true
        }

        return transpiler.shouldCompile(sourceCode, filePath)
      }
    }
  },

  getPackageTranspilerSpecForFilePath: function (filePath) {
    if (this.specByFilePath[filePath] !== undefined) return this.specByFilePath[filePath]

    var config = null
    var spec = null
    var thisPath = filePath
    var lastPath = null
    // Iterate parents from the file path to the root, checking at each level
    // to see if a package manages transpilation for that directory.
    // This means searching for a config for `/path/to/file/here.js` only
    // only iterates four times, even if there are hundreds of configs registered.
    while (thisPath !== lastPath) { // until we reach the root
      if (config = this.configByPackagePath[thisPath]) { // eslint-disable-line no-cond-assign
        for (var i = 0; i < config.specs.length; i++) {
          spec = config.specs[i]
          if (minimatch(filePath, path.join(config.path, spec.glob))) {
            spec._config = config
            this.specByFilePath[filePath] = spec
            return spec
          }
        }
      }

      lastPath = thisPath
      thisPath = path.resolve(thisPath, '..')
    }

    this.specByFilePath[filePath] = null
    return null
  },

  getCachePath: function (sourceCode, filePath, spec) {
    var transpilerPath = this.getTranspilerPath(spec)
    var transpilerSource = spec._transpilerSource || fs.readFileSync(transpilerPath, 'utf8')
    spec._transpilerSource = transpilerSource
    var transpiler = this.getTranspiler(spec)

    var hash = crypto
      .createHash('sha1')
      .update(JSON.stringify(spec.options || {}))
      .update(transpilerSource, 'utf8')
      .update(sourceCode, 'utf8')

    var additionalCacheData
    if (transpiler && transpiler.getCacheKeyData) {
      additionalCacheData = transpiler.getCacheKeyData(sourceCode, filePath, spec.options)
      hash.update(additionalCacheData, 'utf8')
    }

    return path.join('package-transpile', hash.digest('hex'))
  },

  transpileWithPackageTranspiler: function (sourceCode, filePath, spec) {
    var transpiler = this.getTranspiler(spec)

    if (transpiler) {
      var result = transpiler.compile(sourceCode, filePath, spec.options || {})
      if (result === undefined) {
        return sourceCode
      } else {
        return result
      }
    } else {
      var err = new Error("Could not resolve transpiler '" + spec.transpiler + "' from '" + spec._config.path + "'")
      console.error(err)
    }
  },

  getTranspilerPath: function (spec) {
    Resolve = Resolve || require('resolve')
    return Resolve.sync(spec.transpiler, {
      basedir: spec._config.path,
      extensions: Object.keys(require.extensions)
    })
  },

  getTranspiler: function (spec) {
    var transpilerPath = this.getTranspilerPath(spec)
    if (transpilerPath) {
      var transpiler = require(transpilerPath)
      this.transpilerPaths[transpilerPath] = true
      return transpiler
    }
  }
})

module.exports = PackageTranspilationRegistry
<|endoftext|>


<commit_before>// Dependencies
import React, {PropTypes} from 'react';
import builder from 'focus-core/component/builder';

// Components
const DefaultSearchBar = require('../../../search/search-bar').component;
const Results = require('../common/component/results').component;
import DefaultGroupComponent from './group';

// Mixins
const referenceBehaviour = require('../../../common/form/mixin/reference-behaviour');
const storeBehaviour = require('../../../common/mixin/store-behaviour');

// Actions
import actionBuilder from 'focus-core/search/action-builder';

// Stores
import {quickSearchStore} from 'focus-core/search/built-in-store';

/**
* General search mixin.
* Contains a search bar, and a results list.
* @type {Object}
*/
const QuickSearchComponent = {
    /**
    * Component's mixins
    * @type {Array}
    */
    mixins: [referenceBehaviour, storeBehaviour],
    /**
    * Tag name.
    */
    displayName: 'QuickSearch',
    /**
    * Reference names to be fetched by the reference behaviour
    * @type {Array}
    */
    referenceNames: ['scopes'],
    /**
    * Get the default props
    * @return {object} the default props
    */
    getDefaultProps() {
        return {
            action: undefined,
            groupComponent: DefaultGroupComponent,
            groupMaxRows: undefined,
            lineComponentMapper: undefined,
            lineOperationList: undefined,
            onLineClick: undefined,
            scopeFacetKey: 'FCT_SCOPE',
            scopeSelectionHandler: this._scopeSelectionHandler,
            scrollParentSelector: undefined,
            SearchBar: DefaultSearchBar,
            service: undefined,
            store: quickSearchStore
        };
    },
    /**
    * Prop validation
    * @type {Object}
    */
    propTypes: {
        action: PropTypes.object,
        groupComponent: PropTypes.func,
        groupMaxRows: PropTypes.number,
        lineComponentMapper: PropTypes.func,
        onLineClick: PropTypes.func,
        scopeFacetKey: PropTypes.string,
        scopeSelectionHandler: PropTypes.func,
        service: PropTypes.object,
        store: PropTypes.object,
        showAllHandler: PropTypes.func
    },
    /**
    * Register the store listeners
    */
    componentWillMount() {
        const {action, service, store} = this.props;
        this._action = action || actionBuilder({
            service: service,
            identifier: store.identifier,
            getSearchOptions: () => {return store.getValue.call(store); } // Binding the store in the function call
        });
        this._loadReference();

        store.on('quick-search-criterias:change', this._triggerSearch);
        // store.addQueryChangeListener(this._triggerSearch);
        // store.addScopeChangeListener(this._triggerSearch);
        store.addResultsChangeListener(this._onResultsChange);
    },
    /**
    * Unregister the store listeners
    */
    componentWillUnmount() {
        const {store} = this.props;
        store.removeListener('quick-search-criterias:change', this._triggerSearch);
        // store.removeQueryChangeListener(this._triggerSearch);
        // store.removeScopeChangeListener(this._triggerSearch);
        store.removeResultsChangeListener(this._onResultsChange);
    },
    /**
    * Trigger search
    */
    _triggerSearch() {
        this._action.search();
    },
    /**
    * Results change handler
    */
    _onResultsChange() {
        const {store} = this.props;
        const resultsMap = store.getResults();
        const facets = store.getFacets();
        const totalCount = store.getTotalCount();
        this.setState({resultsMap, facets, totalCount});
    },
    /**
    * Action on line click.
    * @param {object} item  the item clicked
    */
    _lineClickHandler(item) {
        if (this.props.onLineClick) {
            this.props.onLineClick(item);
        }
    },
    /**
    * redner the SearchBar
    * @returns {HTML} the rendered component
    */
    _renderSearchBar() {
        const {placeholder, SearchBar, store} = this.props;
        const {isLoading, reference: {scopes}} = this.state;
        return (
            <SearchBar
                action={this._action}
                data-focus='search-bar'
                loading={isLoading}
                placeholder={placeholder}
                ref='searchBar'
                scopes={scopes}
                store={store}
            />
        );
    },
    /**
    * redner the results
    * @returns {HTML} the rendered component
    */
    _renderResults() {
        // Adding 'action' in this.props destructuring here prevent the fact that '...otherProps' consider the props 'action' in otherProps.
        // It didn't give 'this._action' to the 'action' props without doing it
        const {action, groupComponent, groupMaxRows, lineComponentMapper, lineOperationList, scrollParentSelector, scopeFacetKey, store, ...otherProps} = this.props;
        const {facets, resultsMap, totalCount} = this.state;
        return (
            <Results
                action={this._action}
                groupComponent={groupComponent}
                groupingKey={scopeFacetKey}
                initialRowsCount={groupMaxRows}
                isSelection={false}
                lineClickHandler={this._lineClickHandler}
                lineComponentMapper={lineComponentMapper}
                lineOperationList={lineOperationList}
                resultsFacets={facets}
                resultsMap={resultsMap}
                scrollParentSelector={scrollParentSelector}
                store={store}
                totalCount={totalCount}
                {...otherProps}
            />
        );
    },
    /**
    * Render the component
    * @return {HTML} the rendered component
    */
    render() {

        return (

                    {this._renderSearchBar()}

                    {this._renderResults()}

        );
    }
};

module.exports = builder(QuickSearchComponent);
<commit_msg>Add the props "scopesConfig" to the quick-search component.<commit_after>// Dependencies
import React, {PropTypes} from 'react';
import builder from 'focus-core/component/builder';

// Components
const DefaultSearchBar = require('../../../search/search-bar').component;
const Results = require('../common/component/results').component;
import DefaultGroupComponent from './group';

// Mixins
const referenceBehaviour = require('../../../common/form/mixin/reference-behaviour');
const storeBehaviour = require('../../../common/mixin/store-behaviour');

// Actions
import actionBuilder from 'focus-core/search/action-builder';

// Stores
import {quickSearchStore} from 'focus-core/search/built-in-store';

/**
* General search mixin.
* Contains a search bar, and a results list.
* @type {Object}
*/
const QuickSearchComponent = {
    /**
    * Component's mixins
    * @type {Array}
    */
    mixins: [referenceBehaviour, storeBehaviour],
    /**
    * Tag name.
    */
    displayName: 'QuickSearch',
    /**
    * Reference names to be fetched by the reference behaviour
    * @type {Array}
    */
    referenceNames: ['scopes'],
    /**
    * Get the default props
    * @return {object} the default props
    */
    getDefaultProps() {
        return {
            action: undefined,
            groupComponent: DefaultGroupComponent,
            groupMaxRows: undefined,
            lineComponentMapper: undefined,
            lineOperationList: undefined,
            onLineClick: undefined,
            scopesConfig: {},
            scopeFacetKey: 'FCT_SCOPE',
            scopeSelectionHandler: this._scopeSelectionHandler,
            scrollParentSelector: undefined,
            SearchBar: DefaultSearchBar,
            service: undefined,
            store: quickSearchStore
        };
    },
    /**
    * Prop validation
    * @type {Object}
    */
    propTypes: {
        action: PropTypes.object,
        groupComponent: PropTypes.func,
        groupMaxRows: PropTypes.number,
        lineComponentMapper: PropTypes.func,
        onLineClick: PropTypes.func,
        scopesConfig: PropTypes.object,
        scopeFacetKey: PropTypes.string,
        scopeSelectionHandler: PropTypes.func,
        service: PropTypes.object,
        store: PropTypes.object,
        showAllHandler: PropTypes.func
    },
    /**
    * Register the store listeners
    */
    componentWillMount() {
        const {action, service, store} = this.props;
        this._action = action || actionBuilder({
            service: service,
            identifier: store.identifier,
            getSearchOptions: () => {return store.getValue.call(store); } // Binding the store in the function call
        });
        this._loadReference();

        store.on('quick-search-criterias:change', this._triggerSearch);
        // store.addQueryChangeListener(this._triggerSearch);
        // store.addScopeChangeListener(this._triggerSearch);
        store.addResultsChangeListener(this._onResultsChange);
    },
    /**
    * Unregister the store listeners
    */
    componentWillUnmount() {
        const {store} = this.props;
        store.removeListener('quick-search-criterias:change', this._triggerSearch);
        // store.removeQueryChangeListener(this._triggerSearch);
        // store.removeScopeChangeListener(this._triggerSearch);
        store.removeResultsChangeListener(this._onResultsChange);
    },
    /**
    * Trigger search
    */
    _triggerSearch() {
        this._action.search();
    },
    /**
    * Results change handler
    */
    _onResultsChange() {
        const {store} = this.props;
        const resultsMap = store.getResults();
        const facets = store.getFacets();
        const totalCount = store.getTotalCount();
        this.setState({resultsMap, facets, totalCount});
    },
    /**
    * Action on line click.
    * @param {object} item  the item clicked
    */
    _lineClickHandler(item) {
        if (this.props.onLineClick) {
            this.props.onLineClick(item);
        }
    },
    /**
    * redner the SearchBar
    * @returns {HTML} the rendered component
    */
    _renderSearchBar() {
        const {placeholder, SearchBar, store} = this.props;
        const {isLoading, reference: {scopes}} = this.state;
        return (
            <SearchBar
                action={this._action}
                data-focus='search-bar'
                loading={isLoading}
                placeholder={placeholder}
                ref='searchBar'
                scopes={scopes}
                store={store}
            />
        );
    },
    /**
    * redner the results
    * @returns {HTML} the rendered component
    */
    _renderResults() {
        // Adding 'action' in this.props destructuring here prevent the fact that '...otherProps' consider the props 'action' in otherProps.
        // It didn't give 'this._action' to the 'action' props without doing it
        const {action, groupComponent, groupMaxRows, lineComponentMapper, lineOperationList, scrollParentSelector, scopeFacetKey, store, scopesConfig, ...otherProps} = this.props;
        const {facets, resultsMap, totalCount} = this.state;
        return (
            <Results
                action={this._action}
                groupComponent={groupComponent}
                groupingKey={scopeFacetKey}
                initialRowsCount={groupMaxRows}
                isSelection={false}
                lineClickHandler={this._lineClickHandler}
                lineComponentMapper={lineComponentMapper}
                lineOperationList={lineOperationList}
                resultsFacets={facets}
                resultsMap={resultsMap}
                scopesConfig={scopesConfig}
                scrollParentSelector={scrollParentSelector}
                store={store}
                totalCount={totalCount}
                {...otherProps}
            />
        );
    },
    /**
    * Render the component
    * @return {HTML} the rendered component
    */
    render() {

        return (

                    {this._renderSearchBar()}

                    {this._renderResults()}

        );
    }
};

module.exports = builder(QuickSearchComponent);
<|endoftext|>


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

import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { connect } from 'react-redux';

import { DataTablePageModal } from '../widgets/modals';
import { PageButton, SearchBar, DataTablePageView, ToggleBar } from '../widgets';
import { DataTable, DataTableHeaderRow, DataTableRow } from '../widgets/DataTable';
import { BottomConfirmModal } from '../widgets/bottomModals';

import { UIDatabase } from '../database';
import Settings from '../settings/MobileAppSettings';
import { MODAL_KEYS, getAllPrograms } from '../utilities';
import { ROUTES } from '../navigation/constants';
import { useNavigationFocus, useSyncListener } from '../hooks';
import { createSupplierRequisition, gotoSupplierRequisition } from '../navigation/actions';
import { getItemLayout, PageActions, getPageDispatchers } from './dataTableUtilities';

import globalStyles from '../globalStyles';
import { buttonStrings, modalStrings, generalStrings } from '../localization';

/**
 * Renders a mSupply mobile page with a list of supplier requisitions.
 *
 * State:
 * Uses a reducer to manage state with `backingData` being a realm results
 * of items to display. `data` is a plain JS array of realm objects. data is
 * hydrated from `backingData` to display in the interface.
 * i.e: When filtering, data is populated from filtered items of `backingData`.
 *
 * dataState is a simple map of objects corresponding to a row being displayed,
 * holding the state of a given row. Each object has the shape :
 * { isSelected, isFocused },
 */
export const SupplierRequisitions = ({
  currentUser,
  dispatch,
  navigation,
  data,
  dataState,
  sortKey,
  isAscending,
  modalKey,
  hasSelection,
  searchTerm,
  columns,
  showFinalised,
  keyExtractor,
  refreshData,
  onFilterData,
  onDeselectAll,
  onDeleteRecords,
  onCloseModal,
  toggleFinalised,
  onCheck,
  onUncheck,
  onSortColumn,
  onNewRequisition,
}) => {
  // Custom hook to refresh data on this page when becoming the head of the stack again.
  useNavigationFocus(navigation, refreshData);
  useSyncListener(refreshData, 'Requisition');

  const onPressRow = useCallback(rowData => dispatch(gotoSupplierRequisition(rowData)), []);

  const onCreateRequisition = otherStoreName => {
    onCloseModal();
    dispatch(createSupplierRequisition({ otherStoreName, currentUser }));
  };

  const onCreateProgramRequisition = requisitionParameters => {
    onCloseModal();
    dispatch(createSupplierRequisition({ ...requisitionParameters, currentUser }));
  };

  const getCallback = useCallback((colKey, propName) => {
    switch (colKey) {
      case 'remove':
        if (propName === 'onCheck') return onCheck;
        return onUncheck;
      default:
        return null;
    }
  }, []);

  const getModalOnSelect = () => {
    switch (modalKey) {
      case MODAL_KEYS.SELECT_INTERNAL_SUPPLIER:
        return onCreateRequisition;
      case MODAL_KEYS.PROGRAM_REQUISITION:
        return onCreateProgramRequisition;
      default:
        return null;
    }
  };

  const renderRow = useCallback(
    listItem => {
      const { item, index } = listItem;
      const rowKey = keyExtractor(item);
      return (
        <DataTableRow
          rowData={data[index]}
          rowState={dataState.get(rowKey)}
          rowKey={rowKey}
          columns={columns}
          getCallback={getCallback}
          onPress={onPressRow}
          rowIndex={index}
        />
      );
    },
    [data, dataState]
  );

  const renderHeader = useCallback(
    () => (
      <DataTableHeaderRow
        columns={columns}
        onPress={onSortColumn}
        isAscending={isAscending}
        sortKey={sortKey}
      />
    ),
    [sortKey, isAscending]
  );

  const toggles = useMemo(
    () => [
      { text: buttonStrings.current, onPress: toggleFinalised, isOn: !showFinalised },
      { text: buttonStrings.past, onPress: toggleFinalised, isOn: showFinalised },
    ],
    [showFinalised]
  );

  const {
    pageTopSectionContainer,
    pageTopLeftSectionContainer,
    pageTopRightSectionContainer,
  } = globalStyles;
  return (
    <DataTablePageView>
      <View style={pageTopSectionContainer}>
        <View style={pageTopLeftSectionContainer}>
          <ToggleBar toggles={toggles} />
          <SearchBar
            onChangeText={onFilterData}
            value={searchTerm}
            placeholder={`${generalStrings.search_by} ${generalStrings.requisition_number}`}
          />
        </View>
        <View style={pageTopRightSectionContainer}>
          <PageButton text={buttonStrings.new_requisition} onPress={onNewRequisition} />
        </View>
      </View>
      <DataTable
        data={data}
        extraData={dataState}
        renderRow={renderRow}
        renderHeader={renderHeader}
        keyExtractor={keyExtractor}
        getItemLayout={getItemLayout}
      />
      <BottomConfirmModal
        isOpen={hasSelection}
        questionText={modalStrings.remove_these_items}
        onCancel={onDeselectAll}
        onConfirm={onDeleteRecords}
        confirmText={modalStrings.remove}
      />
      <DataTablePageModal
        isOpen={!!modalKey}
        modalKey={modalKey}
        onClose={onCloseModal}
        onSelect={getModalOnSelect()}
        dispatch={dispatch}
      />
    </DataTablePageView>
  );
};

const mapDispatchToProps = (dispatch, ownProps) => {
  const usingPrograms = getAllPrograms(Settings, UIDatabase).length > 0;
  const newRequisitionModalKey = usingPrograms
    ? MODAL_KEYS.PROGRAM_REQUISITION
    : MODAL_KEYS.SELECT_INTERNAL_SUPPLIER;

  return {
    ...getPageDispatchers(dispatch, ownProps, 'Requisition', ROUTES.SUPPLIER_REQUISITIONS),
    onFilterData: value =>
      dispatch(PageActions.filterDataWithFinalisedToggle(value, ROUTES.SUPPLIER_REQUISITIONS)),
    refreshData: () =>
      dispatch(PageActions.refreshDataWithFinalisedToggle(ROUTES.SUPPLIER_REQUISITIONS)),
    onNewRequisition: () =>
      dispatch(PageActions.openModal(newRequisitionModalKey, ROUTES.SUPPLIER_REQUISITIONS)),
  };
};

const mapStateToProps = state => {
  const { pages } = state;
  const { supplierRequisitions } = pages;
  return supplierRequisitions;
};

export const SupplierRequisitionsPage = connect(
  mapStateToProps,
  mapDispatchToProps
)(SupplierRequisitions);

SupplierRequisitions.defaultProps = {
  showFinalised: false,
};

SupplierRequisitions.propTypes = {
  dispatch: PropTypes.func.isRequired,
  navigation: PropTypes.object.isRequired,
  data: PropTypes.array.isRequired,
  dataState: PropTypes.object.isRequired,
  sortKey: PropTypes.string.isRequired,
  isAscending: PropTypes.bool.isRequired,
  searchTerm: PropTypes.string.isRequired,
  columns: PropTypes.array.isRequired,
  keyExtractor: PropTypes.func.isRequired,
  showFinalised: PropTypes.bool,
  modalKey: PropTypes.string.isRequired,
  hasSelection: PropTypes.bool.isRequired,
  currentUser: PropTypes.object.isRequired,
  refreshData: PropTypes.func.isRequired,
  onFilterData: PropTypes.func.isRequired,
  onDeselectAll: PropTypes.func.isRequired,
  onDeleteRecords: PropTypes.func.isRequired,
  onCloseModal: PropTypes.func.isRequired,
  toggleFinalised: PropTypes.func.isRequired,
  onCheck: PropTypes.func.isRequired,
  onUncheck: PropTypes.func.isRequired,
  onSortColumn: PropTypes.func.isRequired,
  onNewRequisition: PropTypes.func.isRequired,
};
<commit_msg>Add update SupplierRequisitionsPage to use selectCurrentUser<commit_after>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { connect } from 'react-redux';

import { DataTablePageModal } from '../widgets/modals';
import { PageButton, SearchBar, DataTablePageView, ToggleBar } from '../widgets';
import { DataTable, DataTableHeaderRow, DataTableRow } from '../widgets/DataTable';
import { BottomConfirmModal } from '../widgets/bottomModals';

import { UIDatabase } from '../database';
import Settings from '../settings/MobileAppSettings';
import { MODAL_KEYS, getAllPrograms } from '../utilities';
import { ROUTES } from '../navigation/constants';
import { useNavigationFocus, useSyncListener } from '../hooks';
import { createSupplierRequisition, gotoSupplierRequisition } from '../navigation/actions';
import { getItemLayout, PageActions, getPageDispatchers } from './dataTableUtilities';
import { selectCurrentUser } from '../selectors/user';

import globalStyles from '../globalStyles';
import { buttonStrings, modalStrings, generalStrings } from '../localization';

/**
 * Renders a mSupply mobile page with a list of supplier requisitions.
 *
 * State:
 * Uses a reducer to manage state with `backingData` being a realm results
 * of items to display. `data` is a plain JS array of realm objects. data is
 * hydrated from `backingData` to display in the interface.
 * i.e: When filtering, data is populated from filtered items of `backingData`.
 *
 * dataState is a simple map of objects corresponding to a row being displayed,
 * holding the state of a given row. Each object has the shape :
 * { isSelected, isFocused },
 */
export const SupplierRequisitions = ({
  currentUser,
  dispatch,
  navigation,
  data,
  dataState,
  sortKey,
  isAscending,
  modalKey,
  hasSelection,
  searchTerm,
  columns,
  showFinalised,
  keyExtractor,
  refreshData,
  onFilterData,
  onDeselectAll,
  onDeleteRecords,
  onCloseModal,
  toggleFinalised,
  onCheck,
  onUncheck,
  onSortColumn,
  onNewRequisition,
}) => {
  // Custom hook to refresh data on this page when becoming the head of the stack again.
  useNavigationFocus(navigation, refreshData);
  useSyncListener(refreshData, 'Requisition');

  const onPressRow = useCallback(rowData => dispatch(gotoSupplierRequisition(rowData)), []);

  const onCreateRequisition = otherStoreName => {
    onCloseModal();
    dispatch(createSupplierRequisition({ otherStoreName, currentUser }));
  };

  const onCreateProgramRequisition = requisitionParameters => {
    onCloseModal();
    dispatch(createSupplierRequisition({ ...requisitionParameters, currentUser }));
  };

  const getCallback = useCallback((colKey, propName) => {
    switch (colKey) {
      case 'remove':
        if (propName === 'onCheck') return onCheck;
        return onUncheck;
      default:
        return null;
    }
  }, []);

  const getModalOnSelect = () => {
    switch (modalKey) {
      case MODAL_KEYS.SELECT_INTERNAL_SUPPLIER:
        return onCreateRequisition;
      case MODAL_KEYS.PROGRAM_REQUISITION:
        return onCreateProgramRequisition;
      default:
        return null;
    }
  };

  const renderRow = useCallback(
    listItem => {
      const { item, index } = listItem;
      const rowKey = keyExtractor(item);
      return (
        <DataTableRow
          rowData={data[index]}
          rowState={dataState.get(rowKey)}
          rowKey={rowKey}
          columns={columns}
          getCallback={getCallback}
          onPress={onPressRow}
          rowIndex={index}
        />
      );
    },
    [data, dataState]
  );

  const renderHeader = useCallback(
    () => (
      <DataTableHeaderRow
        columns={columns}
        onPress={onSortColumn}
        isAscending={isAscending}
        sortKey={sortKey}
      />
    ),
    [sortKey, isAscending]
  );

  const toggles = useMemo(
    () => [
      { text: buttonStrings.current, onPress: toggleFinalised, isOn: !showFinalised },
      { text: buttonStrings.past, onPress: toggleFinalised, isOn: showFinalised },
    ],
    [showFinalised]
  );

  const {
    pageTopSectionContainer,
    pageTopLeftSectionContainer,
    pageTopRightSectionContainer,
  } = globalStyles;
  return (
    <DataTablePageView>
      <View style={pageTopSectionContainer}>
        <View style={pageTopLeftSectionContainer}>
          <ToggleBar toggles={toggles} />
          <SearchBar
            onChangeText={onFilterData}
            value={searchTerm}
            placeholder={`${generalStrings.search_by} ${generalStrings.requisition_number}`}
          />
        </View>
        <View style={pageTopRightSectionContainer}>
          <PageButton text={buttonStrings.new_requisition} onPress={onNewRequisition} />
        </View>
      </View>
      <DataTable
        data={data}
        extraData={dataState}
        renderRow={renderRow}
        renderHeader={renderHeader}
        keyExtractor={keyExtractor}
        getItemLayout={getItemLayout}
      />
      <BottomConfirmModal
        isOpen={hasSelection}
        questionText={modalStrings.remove_these_items}
        onCancel={onDeselectAll}
        onConfirm={onDeleteRecords}
        confirmText={modalStrings.remove}
      />
      <DataTablePageModal
        isOpen={!!modalKey}
        modalKey={modalKey}
        onClose={onCloseModal}
        onSelect={getModalOnSelect()}
        dispatch={dispatch}
      />
    </DataTablePageView>
  );
};

const mapDispatchToProps = (dispatch, ownProps) => {
  const usingPrograms = getAllPrograms(Settings, UIDatabase).length > 0;
  const newRequisitionModalKey = usingPrograms
    ? MODAL_KEYS.PROGRAM_REQUISITION
    : MODAL_KEYS.SELECT_INTERNAL_SUPPLIER;

  return {
    ...getPageDispatchers(dispatch, ownProps, 'Requisition', ROUTES.SUPPLIER_REQUISITIONS),
    onFilterData: value =>
      dispatch(PageActions.filterDataWithFinalisedToggle(value, ROUTES.SUPPLIER_REQUISITIONS)),
    refreshData: () =>
      dispatch(PageActions.refreshDataWithFinalisedToggle(ROUTES.SUPPLIER_REQUISITIONS)),
    onNewRequisition: () =>
      dispatch(PageActions.openModal(newRequisitionModalKey, ROUTES.SUPPLIER_REQUISITIONS)),
  };
};

const mapStateToProps = state => {
  const { pages } = state;
  const { supplierRequisitions } = pages;
  const currentUser = selectCurrentUser(state);

  return { ...supplierRequisitions, currentUser };
};

export const SupplierRequisitionsPage = connect(
  mapStateToProps,
  mapDispatchToProps
)(SupplierRequisitions);

SupplierRequisitions.defaultProps = {
  showFinalised: false,
};

SupplierRequisitions.propTypes = {
  dispatch: PropTypes.func.isRequired,
  navigation: PropTypes.object.isRequired,
  data: PropTypes.array.isRequired,
  dataState: PropTypes.object.isRequired,
  sortKey: PropTypes.string.isRequired,
  isAscending: PropTypes.bool.isRequired,
  searchTerm: PropTypes.string.isRequired,
  columns: PropTypes.array.isRequired,
  keyExtractor: PropTypes.func.isRequired,
  showFinalised: PropTypes.bool,
  modalKey: PropTypes.string.isRequired,
  hasSelection: PropTypes.bool.isRequired,
  currentUser: PropTypes.object.isRequired,
  refreshData: PropTypes.func.isRequired,
  onFilterData: PropTypes.func.isRequired,
  onDeselectAll: PropTypes.func.isRequired,
  onDeleteRecords: PropTypes.func.isRequired,
  onCloseModal: PropTypes.func.isRequired,
  toggleFinalised: PropTypes.func.isRequired,
  onCheck: PropTypes.func.isRequired,
  onUncheck: PropTypes.func.isRequired,
  onSortColumn: PropTypes.func.isRequired,
  onNewRequisition: PropTypes.func.isRequired,
};
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import { SUPPLIER_CREDIT_ACTIONS } from '../actions/SupplierCreditActions';
import { UIDatabase } from '../database/index';
import { parsePositiveInteger } from '../utilities';

const mapBatchToObject = batches =>
  batches.map(batch => ({
    returnAmount: 0,
    id: batch.id,
    totalQuantity: batch.totalQuantity,
    itemBatch: batch,
    otherPartyName: batch.otherPartyName,
    batch: batch.batch,
    costPrice: batch.costPrice,
  }));

const initialState = () => ({
  batches: [],
  open: false,
  sortKey: 'otherPartyName',
  isAscending: true,
  item: null,
  title: '',
});

export const SupplierCreditReducer = (state = initialState(), action) => {
  const { type } = action;

  switch (type) {
    case SUPPLIER_CREDIT_ACTIONS.EDIT_RETURN_AMOUNT: {
      const { batches } = state;
      const { payload } = action;
      const { batchId, returnAmount } = payload;

      const rowToEditIndex = batches.findIndex(({ id }) => id === batchId);
      const newRow = {
        ...batches[rowToEditIndex],
        returnAmount: parsePositiveInteger(returnAmount),
      };
      batches[rowToEditIndex] = newRow;

      return { ...state, batches: [...batches] };
    }

    case SUPPLIER_CREDIT_ACTIONS.CREATE_FROM_ITEM: {
      const { payload } = action;
      const { itemId } = payload;

      const batches = UIDatabase.objects('ItemBatch').filtered(
        'item.id == $0 && numberOfPacks > 0',
        itemId
      );

      return {
        ...state,
        open: true,
        batches: mapBatchToObject(batches),
        item: UIDatabase.get('Item', itemId),
        title: `Available Credits for  ${UIDatabase.get('Item', itemId).name}`,
        type: 'supplierCreditFromItem',
      };
    }

    case SUPPLIER_CREDIT_ACTIONS.CREATE_FROM_INVOICE: {
      const { payload } = action;
      const { invoice } = payload;

      const { serialNumber } = invoice;
      const transactionBatches = invoice.getTransactionBatches(UIDatabase);
      const itemBatches = transactionBatches.map(({ itemBatch }) => itemBatch);

      return {
        ...state,
        open: true,
        batches: mapBatchToObject(itemBatches),
        item: UIDatabase.objects('Item')[0],
        title: `Supplier Credit for Supplier Invoice ${serialNumber}`,
        type: 'supplierCreditFromInvoice',
      };
    }

    case SUPPLIER_CREDIT_ACTIONS.SORT: {
      const { isAscending, sortKey } = state;
      const { payload } = action;
      const { sortKey: newSortKey } = payload;

      return {
        ...state,
        sortKey: newSortKey,
        isAscending: sortKey === newSortKey ? !isAscending : true,
      };
    }

    case SUPPLIER_CREDIT_ACTIONS.CLOSE: {
      return initialState();
    }

    default:
      return state;
  }
};
<commit_msg>Add category adjustments to reducer<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import { SUPPLIER_CREDIT_ACTIONS } from '../actions/SupplierCreditActions';
import { UIDatabase } from '../database';
import { parsePositiveInteger } from '../utilities';

const mapBatchToObject = batches =>
  batches.map(batch => ({
    returnAmount: 0,
    id: batch.id,
    totalQuantity: batch.totalQuantity,
    itemBatch: batch,
    otherPartyName: batch.otherPartyName,
    batch: batch.batch,
    costPrice: batch.costPrice,
  }));

const initialState = () => ({
  batches: [],
  open: false,
  sortKey: 'otherPartyName',
  isAscending: true,
  item: null,
  invoice: null,
  category: null,
  title: '',
  reasons: UIDatabase.objects('SupplierCreditCategory'),
});

export const SupplierCreditReducer = (state = initialState(), action) => {
  const { type } = action;

  switch (type) {
    case SUPPLIER_CREDIT_ACTIONS.EDIT_RETURN_AMOUNT: {
      const { batches } = state;
      const { payload } = action;
      const { batchId, returnAmount } = payload;

      const rowToEditIndex = batches.findIndex(({ id }) => id === batchId);

      const { totalQuantity } = batches[rowToEditIndex];
      const adjustmentAmount = parsePositiveInteger(returnAmount);

      const newRow = {
        ...batches[rowToEditIndex],
        returnAmount: adjustmentAmount <= totalQuantity ? adjustmentAmount : 0,
      };
      batches[rowToEditIndex] = newRow;

      return { ...state, batches: [...batches] };
    }

    case SUPPLIER_CREDIT_ACTIONS.EDIT_CATEGORY: {
      const { payload } = action;
      const { category } = payload;
      return { ...state, category };
    }

    case SUPPLIER_CREDIT_ACTIONS.CREATE_FROM_ITEM: {
      const { payload } = action;
      const { itemId } = payload;

      const item = UIDatabase.get('Item', itemId);

      return {
        ...state,
        batches: mapBatchToObject(item.batches.filtered('numberOfPacks > 0')),
        open: true,
        item,
        type: 'supplierCreditFromItem',
      };
    }

    case SUPPLIER_CREDIT_ACTIONS.CREATE_FROM_INVOICE: {
      const { payload } = action;
      const { invoice } = payload;

      const transactionBatches = invoice.getTransactionBatches(UIDatabase);
      const itemBatches = transactionBatches.map(({ itemBatch }) => itemBatch);

      return {
        ...state,
        open: true,
        batches: mapBatchToObject(itemBatches),
        invoice,
        type: 'supplierCreditFromInvoice',
      };
    }

    case SUPPLIER_CREDIT_ACTIONS.SORT: {
      const { isAscending, sortKey } = state;
      const { payload } = action;
      const { sortKey: newSortKey } = payload;

      return {
        ...state,
        sortKey: newSortKey,
        isAscending: sortKey === newSortKey ? !isAscending : true,
      };
    }

    case SUPPLIER_CREDIT_ACTIONS.CLOSE: {
      return initialState();
    }

    default:
      return state;
  }
};
<|endoftext|>


<commit_before>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var Class = require('../../utils/Class');
var CustomMap = require('../../structs/Map');
var CONST = require('./pipelines/const');

//  Default Phaser 3 Pipelines
var BitmapMaskPipeline = require('./pipelines/BitmapMaskPipeline');
var LightPipeline = require('./pipelines/LightPipeline');
var MultiPipeline = require('./pipelines/MultiPipeline');
var RopePipeline = require('./pipelines/RopePipeline');
var SinglePipeline = require('./pipelines/SinglePipeline');

/**
 * @classdesc
 * The Pipeline Manager is responsible for the creation, activation, running and destruction
 * of WebGL Pipelines in Phaser 3.
 *
 * The `WebGLRenderer` owns a single instance of the Pipeline Manager, which you can access
 * via the `WebGLRenderer.pipelines` property.
 *
 * By default, there are 5 pipelines installed into the Pipeline Manager when Phaser boots:
 *
 * 1. The Multi Pipeline. Responsible for all multi-texture rendering, i.e. Sprites, Shapes.
 * 2. The Single Pipeline. Responsible for rendering Game Objects that explicitly require one bound texture.
 * 3. The Rope Pipeline. Responsible for rendering the Rope Game Object.
 * 4. The Light Pipeline. Responsible for rendering the Light Game Object.
 * 5. The Bitmap Mask Pipeline. Responsible for Bitmap Mask rendering.
 *
 * You can add your own custom pipeline via the `PipelineManager.add` method. Pipelines are
 * identified by unique string-based keys.
 *
 * @class PipelineManager
 * @memberof Phaser.Renderer.WebGL
 * @constructor
 * @since 3.50.0
 *
 * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - A reference to the WebGL Renderer that owns this Pipeline Manager.
 */
var PipelineManager = new Class({

    initialize:

    function PipelineManager (renderer)
    {
        /**
         * A reference to the Game instance.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#game
         * @type {Phaser.Game}
         * @since 3.50.0
         */
        this.game = renderer.game;

        /**
         * A reference to the WebGL Renderer instance.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#renderer
         * @type {Phaser.Renderer.WebGL.WebGLRenderer}
         * @since 3.50.0
         */
        this.renderer = renderer;

        /**
         * This map stores all pipeline instances in this manager.
         *
         * This is populated with the default pipelines in the `boot` method.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#pipelines
         * @type {Phaser.Structs.Map.<string, Phaser.Renderer.WebGL.WebGLPipeline>}
         * @since 3.50.0
         */
        this.pipelines = new CustomMap();

        /**
         * Current pipeline in use by the WebGLRenderer.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#current
         * @type {Phaser.Renderer.WebGL.WebGLPipeline}
         * @default null
         * @since 3.50.0
         */
        this.current = null;

        /**
         * The previous WebGLPipeline that was in use.
         *
         * This is set when `clearPipeline` is called and restored in `rebindPipeline` if none is given.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#previous
         * @type {Phaser.Renderer.WebGL.WebGLPipeline}
         * @default null
         * @since 3.50.0
         */
        this.previous = null;

        /**
         * A constant-style reference to the Multi Pipeline Instance.
         *
         * This is the default Phaser 3 pipeline and is used by the WebGL Renderer to manage
         * camera effects and more. This property is set during the `boot` method.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#MULTI_PIPELINE
         * @type {Phaser.Renderer.WebGL.Pipelines.MultiPipeline}
         * @default null
         * @since 3.50.0
         */
        this.MULTI_PIPELINE = null;

        /**
         * A constant-style reference to the Bitmap Mask Pipeline Instance.
         *
         * This is the default Phaser 3 mask pipeline and is used Game Objects using
         * a Bitmap Mask. This property is set during the `boot` method.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#BITMAPMASK_PIPELINE
         * @type {Phaser.Renderer.WebGL.Pipelines.BitmapMaskPipeline}
         * @default null
         * @since 3.50.0
         */
        this.BITMAPMASK_PIPELINE = null;
    },

    /**
     * Internal boot handler, called by the WebGLRenderer durings its boot process.
     *
     * Adds all of the default pipelines, based on the game config, and then calls
     * the `boot` method on each one of them.
     *
     * Finally, the default pipeline is set.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#boot
     * @since 3.50.0
     */
    boot: function ()
    {
        var game = this.game;

        this.MULTI_PIPELINE = this.add(CONST.MULTI_PIPELINE, new MultiPipeline({ game: game }));
        this.BITMAPMASK_PIPELINE = this.add(CONST.BITMAPMASK_PIPELINE, new BitmapMaskPipeline({ game: game }));

        this.add(CONST.SINGLE_PIPELINE, new SinglePipeline({ game: game }));
        this.add(CONST.ROPE_PIPELINE, new RopePipeline({ game: game }));
        this.add(CONST.LIGHT_PIPELINE, new LightPipeline({ game: game }));
    },

    /**
     * Adds a pipeline instance to this Pipeline Manager.
     *
     * The name of the instance must be unique within this manager.
     *
     * Make sure to pass an instance to this method, not a base class.
     *
     * For example, you should pass it like this:
     *
     * ```javascript
     * this.add('yourName', new CustomPipeline());`
     * ```
     *
     * and **not** like this:
     *
     * ```javascript
     * this.add('yourName', CustomPipeline);`
     * ```
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#addPipeline
     * @since 3.50.0
     *
     * @param {string} name - A unique string-based key for the pipeline within the manager.
     * @param {Phaser.Renderer.WebGL.WebGLPipeline} pipeline - A pipeline _instance_ which must extend `WebGLPipeline`.
     *
     * @return {Phaser.Renderer.WebGL.WebGLPipeline} The pipeline instance that was passed.
     */
    add: function (name, pipeline)
    {
        var pipelines = this.pipelines;
        var renderer = this.renderer;

        if (!pipelines.has(name))
        {
            pipeline.name = name;

            pipelines.set(name, pipeline);
        }
        else
        {
            console.warn('Pipeline exists: ' + name);
        }

        if (renderer.width !== 0 && renderer.height !== 0)
        {
            pipeline.resize(renderer.width, renderer.height);
        }

        if (!pipeline.hasBooted)
        {
            pipeline.boot();
        }

        return pipeline;
    },

    /**
     * Resizes handler.
     *
     * This is called automatically by the `WebGLRenderer` when the game resizes.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#resize
     * @since 3.50.0
     *
     * @param {number} [width] - The new width of the renderer.
     * @param {number} [height] - The new height of the renderer.
     */
    resize: function (width, height)
    {
        var pipelines = this.pipelines;

        pipelines.each(function (pipelineName, pipelineInstance)
        {
            pipelineInstance.resize(width, height);
        });
    },

    /**
     * Calls the `onPreRender` method on each pipeline in this manager.
     *
     * This is called automatically by the `WebGLRenderer.preRender` method.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#preRender
     * @since 3.50.0
     */
    preRender: function ()
    {
        var pipelines = this.pipelines;

        pipelines.each(function (pipelineName, pipelineInstance)
        {
            pipelineInstance.onPreRender();
        });
    },

    /**
     * Calls the `onRender` method on each pipeline in this manager.
     *
     * This is called automatically by the `WebGLRenderer.render` method.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#render
     * @since 3.50.0
     *
     * @param {Phaser.Scene} scene - The Scene to render.
     * @param {Phaser.Cameras.Scene2D.Camera} camera - The Scene Camera to render with.
     */
    render: function (scene, camera)
    {
        var pipelines = this.pipelines;

        pipelines.each(function (pipelineName, pipelineInstance)
        {
            pipelineInstance.onRender(scene, camera);
        });
    },

    /**
     * Calls the `onPostRender` method on each pipeline in this manager.
     *
     * This is called automatically by the `WebGLRenderer.postRender` method.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#postRender
     * @since 3.50.0
     */
    postRender: function ()
    {
        var pipelines = this.pipelines;

        pipelines.each(function (pipelineName, pipelineInstance)
        {
            pipelineInstance.onPostRender();
        });
    },

    /**
     * Flushes the current pipeline, if one is bound.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#flush
     * @since 3.50.0
     */
    flush: function ()
    {
        if (this.current)
        {
            this.current.flush();
        }
    },

    /**
     * Checks if a pipeline is present in the Pipeline Manager.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#has
     * @since 3.50.0
     *
     * @param {string} name - The name of the pipeline to check for.
     *
     * @return {boolean} `true` if the given pipeline is loaded, otherwise `false`.
     */
    has: function (name)
    {
        return this.pipelines.has(name);
    },

    /**
     * Returns the pipeline instance based on the given name.
     *
     * If no instance exists in this manager, it returns `undefined` instead.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#get
     * @since 3.50.0
     *
     * @param {string} name - The name of the pipeline to get.
     *
     * @return {Phaser.Renderer.WebGL.WebGLPipeline} The pipeline instance, or `undefined` if not found.
     */
    get: function (name)
    {
        return this.pipelines.get(name);
    },

    /**
     * Removes a pipeline based on the given name.
     *
     * If no pipeline matches the name, this method does nothing.
     *
     * Note that the pipeline will not be flushed or destroyed, it's simply removed from
     * this manager.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#remove
     * @since 3.50.0
     *
     * @param {string} name - The name of the pipeline to be removed.
     */
    remove: function (name)
    {
        this.pipelines.delete(name);
    },

    /**
     * Sets the current pipeline to be used by the `WebGLRenderer`.
     *
     * This method accepts a pipeline instance as its parameter, not the name.
     *
     * If the pipeline isn't already the current one, it will also call `resetTextures` on
     * the `WebGLRenderer`. After this, `WebGLPipeline.bind` and then `onBind` are called.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#set
     * @since 3.50.0
     *
     * @param {Phaser.Renderer.WebGL.WebGLPipeline} pipeline - The pipeline instance to be set as current.
     * @param {Phaser.GameObjects.GameObject} [gameObject] - The Game Object that invoked this pipeline, if any.
     *
     * @return {Phaser.Renderer.WebGL.WebGLPipeline} The pipeline that was set.
     */
    set: function (pipeline, gameObject)
    {
        var renderer = this.renderer;
        var current = this.current;

        if (
            current !== pipeline ||
            current.vertexBuffer !== renderer.currentVertexBuffer ||
            current.currentShader.program !== renderer.currentProgram
        )
        {
            renderer.resetTextures();

            this.current = pipeline;

            pipeline.bind();
        }

        pipeline.onBind(gameObject);

        return pipeline;
    },

    /**
     * Sets the Multi Pipeline to be the currently bound pipeline.
     *
     * This is the default Phaser 3 rendering pipeline.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#setMulti
     * @since 3.50.0
     *
     * @return {Phaser.Renderer.WebGL.Pipelines.MultiPipeline} The Multi Pipeline instance.
     */
    setMulti: function ()
    {
        return this.set(this.MULTI_PIPELINE);
    },

    /**
     * Use this to reset the gl context to the state that Phaser requires to continue rendering.
     *
     * Calling this will:
     *
     * * Disable `DEPTH_TEST`, `CULL_FACE` and `STENCIL_TEST`.
     * * Clear the depth buffer and stencil buffers.
     * * Reset the viewport size.
     * * Reset the blend mode.
     * * Bind a blank texture as the active texture on texture unit zero.
     * * Rebinds the given pipeline instance.
     *
     * You should call this if you have previously called `clear`, and then wish to return
     * rendering control to Phaser again.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#rebind
     * @since 3.50.0
     *
     * @param {Phaser.Renderer.WebGL.WebGLPipeline} [pipeline] - The pipeline instance to be rebound. If not given, the previous pipeline will be bound.
     */
    rebind: function (pipeline)
    {
        if (pipeline === undefined && this.previous)
        {
            pipeline = this.previous;
        }

        if (!pipeline)
        {
            return;
        }

        var renderer = this.renderer;
        var gl = renderer.gl;

        gl.disable(gl.DEPTH_TEST);
        gl.disable(gl.CULL_FACE);

        if (renderer.hasActiveStencilMask())
        {
            gl.clear(gl.DEPTH_BUFFER_BIT);
        }
        else
        {
            //  If there wasn't a stencil mask set before this call, we can disable it safely
            gl.disable(gl.STENCIL_TEST);
            gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
        }

        gl.viewport(0, 0, renderer.width, renderer.height);

        renderer.currentProgram = null;
        renderer.currentVertexBuffer = null;
        renderer.currentIndexBuffer = null;

        renderer.setBlendMode(0, true);

        renderer.resetTextures();

        this.current = pipeline;

        pipeline.bind(true);
        pipeline.onBind();
    },

    /**
     * Flushes the current pipeline being used and then clears it, along with the
     * the current shader program and vertex buffer from the `WebGLRenderer`.
     *
     * Then resets the blend mode to NORMAL.
     *
     * Call this before jumping to your own gl context handler, and then call `rebind` when
     * you wish to return control to Phaser again.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#clear
     * @since 3.50.0
     */
    clear: function ()
    {
        var renderer = this.renderer;

        this.flush();

        this.previous = this.current;
        this.current = null;

        renderer.currentProgram = null;
        renderer.currentVertexBuffer = null;
        renderer.currentIndexBuffer = null;

        renderer.setBlendMode(0, true);
    },

    /**
     * Destroy the Pipeline Manager, cleaning up all related resources and references.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#destroy
     * @since 3.50.0
     */
    destroy: function ()
    {
        this.flush();

        this.pipelines.clear();

        this.renderer = null;
        this.game = null;
        this.pipelines = null;
        this.current = null;
        this.previous = null;
    }

});

module.exports = PipelineManager;
<commit_msg>Look-up based on string or instance<commit_after>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var Class = require('../../utils/Class');
var CustomMap = require('../../structs/Map');
var CONST = require('./pipelines/const');

//  Default Phaser 3 Pipelines
var BitmapMaskPipeline = require('./pipelines/BitmapMaskPipeline');
var LightPipeline = require('./pipelines/LightPipeline');
var MultiPipeline = require('./pipelines/MultiPipeline');
var RopePipeline = require('./pipelines/RopePipeline');
var SinglePipeline = require('./pipelines/SinglePipeline');

/**
 * @classdesc
 * The Pipeline Manager is responsible for the creation, activation, running and destruction
 * of WebGL Pipelines in Phaser 3.
 *
 * The `WebGLRenderer` owns a single instance of the Pipeline Manager, which you can access
 * via the `WebGLRenderer.pipelines` property.
 *
 * By default, there are 5 pipelines installed into the Pipeline Manager when Phaser boots:
 *
 * 1. The Multi Pipeline. Responsible for all multi-texture rendering, i.e. Sprites, Shapes.
 * 2. The Single Pipeline. Responsible for rendering Game Objects that explicitly require one bound texture.
 * 3. The Rope Pipeline. Responsible for rendering the Rope Game Object.
 * 4. The Light Pipeline. Responsible for rendering the Light Game Object.
 * 5. The Bitmap Mask Pipeline. Responsible for Bitmap Mask rendering.
 *
 * You can add your own custom pipeline via the `PipelineManager.add` method. Pipelines are
 * identified by unique string-based keys.
 *
 * @class PipelineManager
 * @memberof Phaser.Renderer.WebGL
 * @constructor
 * @since 3.50.0
 *
 * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - A reference to the WebGL Renderer that owns this Pipeline Manager.
 */
var PipelineManager = new Class({

    initialize:

    function PipelineManager (renderer)
    {
        /**
         * A reference to the Game instance.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#game
         * @type {Phaser.Game}
         * @since 3.50.0
         */
        this.game = renderer.game;

        /**
         * A reference to the WebGL Renderer instance.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#renderer
         * @type {Phaser.Renderer.WebGL.WebGLRenderer}
         * @since 3.50.0
         */
        this.renderer = renderer;

        /**
         * This map stores all pipeline instances in this manager.
         *
         * This is populated with the default pipelines in the `boot` method.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#pipelines
         * @type {Phaser.Structs.Map.<string, Phaser.Renderer.WebGL.WebGLPipeline>}
         * @since 3.50.0
         */
        this.pipelines = new CustomMap();

        /**
         * Current pipeline in use by the WebGLRenderer.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#current
         * @type {Phaser.Renderer.WebGL.WebGLPipeline}
         * @default null
         * @since 3.50.0
         */
        this.current = null;

        /**
         * The previous WebGLPipeline that was in use.
         *
         * This is set when `clearPipeline` is called and restored in `rebindPipeline` if none is given.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#previous
         * @type {Phaser.Renderer.WebGL.WebGLPipeline}
         * @default null
         * @since 3.50.0
         */
        this.previous = null;

        /**
         * A constant-style reference to the Multi Pipeline Instance.
         *
         * This is the default Phaser 3 pipeline and is used by the WebGL Renderer to manage
         * camera effects and more. This property is set during the `boot` method.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#MULTI_PIPELINE
         * @type {Phaser.Renderer.WebGL.Pipelines.MultiPipeline}
         * @default null
         * @since 3.50.0
         */
        this.MULTI_PIPELINE = null;

        /**
         * A constant-style reference to the Bitmap Mask Pipeline Instance.
         *
         * This is the default Phaser 3 mask pipeline and is used Game Objects using
         * a Bitmap Mask. This property is set during the `boot` method.
         *
         * @name Phaser.Renderer.WebGL.PipelineManager#BITMAPMASK_PIPELINE
         * @type {Phaser.Renderer.WebGL.Pipelines.BitmapMaskPipeline}
         * @default null
         * @since 3.50.0
         */
        this.BITMAPMASK_PIPELINE = null;
    },

    /**
     * Internal boot handler, called by the WebGLRenderer durings its boot process.
     *
     * Adds all of the default pipelines, based on the game config, and then calls
     * the `boot` method on each one of them.
     *
     * Finally, the default pipeline is set.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#boot
     * @since 3.50.0
     */
    boot: function ()
    {
        var game = this.game;

        this.MULTI_PIPELINE = this.add(CONST.MULTI_PIPELINE, new MultiPipeline({ game: game }));
        this.BITMAPMASK_PIPELINE = this.add(CONST.BITMAPMASK_PIPELINE, new BitmapMaskPipeline({ game: game }));

        this.add(CONST.SINGLE_PIPELINE, new SinglePipeline({ game: game }));
        this.add(CONST.ROPE_PIPELINE, new RopePipeline({ game: game }));
        this.add(CONST.LIGHT_PIPELINE, new LightPipeline({ game: game }));
    },

    /**
     * Adds a pipeline instance to this Pipeline Manager.
     *
     * The name of the instance must be unique within this manager.
     *
     * Make sure to pass an instance to this method, not a base class.
     *
     * For example, you should pass it like this:
     *
     * ```javascript
     * this.add('yourName', new CustomPipeline());`
     * ```
     *
     * and **not** like this:
     *
     * ```javascript
     * this.add('yourName', CustomPipeline);`
     * ```
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#addPipeline
     * @since 3.50.0
     *
     * @param {string} name - A unique string-based key for the pipeline within the manager.
     * @param {Phaser.Renderer.WebGL.WebGLPipeline} pipeline - A pipeline _instance_ which must extend `WebGLPipeline`.
     *
     * @return {Phaser.Renderer.WebGL.WebGLPipeline} The pipeline instance that was passed.
     */
    add: function (name, pipeline)
    {
        var pipelines = this.pipelines;
        var renderer = this.renderer;

        if (!pipelines.has(name))
        {
            pipeline.name = name;

            pipelines.set(name, pipeline);
        }
        else
        {
            console.warn('Pipeline exists: ' + name);
        }

        if (renderer.width !== 0 && renderer.height !== 0)
        {
            pipeline.resize(renderer.width, renderer.height);
        }

        if (!pipeline.hasBooted)
        {
            pipeline.boot();
        }

        return pipeline;
    },

    /**
     * Resizes handler.
     *
     * This is called automatically by the `WebGLRenderer` when the game resizes.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#resize
     * @since 3.50.0
     *
     * @param {number} [width] - The new width of the renderer.
     * @param {number} [height] - The new height of the renderer.
     */
    resize: function (width, height)
    {
        var pipelines = this.pipelines;

        pipelines.each(function (pipelineName, pipelineInstance)
        {
            pipelineInstance.resize(width, height);
        });
    },

    /**
     * Calls the `onPreRender` method on each pipeline in this manager.
     *
     * This is called automatically by the `WebGLRenderer.preRender` method.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#preRender
     * @since 3.50.0
     */
    preRender: function ()
    {
        var pipelines = this.pipelines;

        pipelines.each(function (pipelineName, pipelineInstance)
        {
            pipelineInstance.onPreRender();
        });
    },

    /**
     * Calls the `onRender` method on each pipeline in this manager.
     *
     * This is called automatically by the `WebGLRenderer.render` method.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#render
     * @since 3.50.0
     *
     * @param {Phaser.Scene} scene - The Scene to render.
     * @param {Phaser.Cameras.Scene2D.Camera} camera - The Scene Camera to render with.
     */
    render: function (scene, camera)
    {
        var pipelines = this.pipelines;

        pipelines.each(function (pipelineName, pipelineInstance)
        {
            pipelineInstance.onRender(scene, camera);
        });
    },

    /**
     * Calls the `onPostRender` method on each pipeline in this manager.
     *
     * This is called automatically by the `WebGLRenderer.postRender` method.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#postRender
     * @since 3.50.0
     */
    postRender: function ()
    {
        var pipelines = this.pipelines;

        pipelines.each(function (pipelineName, pipelineInstance)
        {
            pipelineInstance.onPostRender();
        });
    },

    /**
     * Flushes the current pipeline, if one is bound.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#flush
     * @since 3.50.0
     */
    flush: function ()
    {
        if (this.current)
        {
            this.current.flush();
        }
    },

    /**
     * Checks if a pipeline is present in this Pipeline Manager.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#has
     * @since 3.50.0
     *
     * @param {(string|Phaser.Renderer.WebGL.WebGLPipeline)} pipeline - Either the string-based name of the pipeline to get, or a pipeline instance to look-up.
     *
     * @return {boolean} `true` if the given pipeline is loaded, otherwise `false`.
     */
    has: function (pipeline)
    {
        var pipelines = this.pipelines;

        if (typeof pipeline === 'string')
        {
            return pipelines.has(pipeline);
        }
        else if (pipelines.contains(pipeline))
        {
            return true;
        }

        return false;
    },

    /**
     * Returns the pipeline instance based on the given name, or instance.
     *
     * If no instance, or matching name, exists in this manager, it returns `undefined`.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#get
     * @since 3.50.0
     *
     * @param {(string|Phaser.Renderer.WebGL.WebGLPipeline)} pipeline - Either the string-based name of the pipeline to get, or a pipeline instance to look-up.
     *
     * @return {Phaser.Renderer.WebGL.WebGLPipeline} The pipeline instance, or `undefined` if not found.
     */
    get: function (pipeline)
    {
        var pipelines = this.pipelines;

        if (typeof pipeline === 'string')
        {
            return pipelines.get(pipeline);
        }
        else if (pipelines.contains(pipeline))
        {
            return pipeline;
        }
    },

    /**
     * Removes a pipeline based on the given name.
     *
     * If no pipeline matches the name, this method does nothing.
     *
     * Note that the pipeline will not be flushed or destroyed, it's simply removed from
     * this manager.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#remove
     * @since 3.50.0
     *
     * @param {string} name - The name of the pipeline to be removed.
     */
    remove: function (name)
    {
        this.pipelines.delete(name);
    },

    /**
     * Sets the current pipeline to be used by the `WebGLRenderer`.
     *
     * This method accepts a pipeline instance as its parameter, not the name.
     *
     * If the pipeline isn't already the current one, it will also call `resetTextures` on
     * the `WebGLRenderer`. After this, `WebGLPipeline.bind` and then `onBind` are called.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#set
     * @since 3.50.0
     *
     * @param {Phaser.Renderer.WebGL.WebGLPipeline} pipeline - The pipeline instance to be set as current.
     * @param {Phaser.GameObjects.GameObject} [gameObject] - The Game Object that invoked this pipeline, if any.
     *
     * @return {Phaser.Renderer.WebGL.WebGLPipeline} The pipeline that was set.
     */
    set: function (pipeline, gameObject)
    {
        var renderer = this.renderer;
        var current = this.current;

        if (
            current !== pipeline ||
            current.vertexBuffer !== renderer.currentVertexBuffer ||
            current.currentShader.program !== renderer.currentProgram
        )
        {
            renderer.resetTextures();

            this.current = pipeline;

            pipeline.bind();
        }

        pipeline.onBind(gameObject);

        return pipeline;
    },

    /**
     * Sets the Multi Pipeline to be the currently bound pipeline.
     *
     * This is the default Phaser 3 rendering pipeline.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#setMulti
     * @since 3.50.0
     *
     * @return {Phaser.Renderer.WebGL.Pipelines.MultiPipeline} The Multi Pipeline instance.
     */
    setMulti: function ()
    {
        return this.set(this.MULTI_PIPELINE);
    },

    /**
     * Use this to reset the gl context to the state that Phaser requires to continue rendering.
     *
     * Calling this will:
     *
     * * Disable `DEPTH_TEST`, `CULL_FACE` and `STENCIL_TEST`.
     * * Clear the depth buffer and stencil buffers.
     * * Reset the viewport size.
     * * Reset the blend mode.
     * * Bind a blank texture as the active texture on texture unit zero.
     * * Rebinds the given pipeline instance.
     *
     * You should call this if you have previously called `clear`, and then wish to return
     * rendering control to Phaser again.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#rebind
     * @since 3.50.0
     *
     * @param {Phaser.Renderer.WebGL.WebGLPipeline} [pipeline] - The pipeline instance to be rebound. If not given, the previous pipeline will be bound.
     */
    rebind: function (pipeline)
    {
        if (pipeline === undefined && this.previous)
        {
            pipeline = this.previous;
        }

        if (!pipeline)
        {
            return;
        }

        var renderer = this.renderer;
        var gl = renderer.gl;

        gl.disable(gl.DEPTH_TEST);
        gl.disable(gl.CULL_FACE);

        if (renderer.hasActiveStencilMask())
        {
            gl.clear(gl.DEPTH_BUFFER_BIT);
        }
        else
        {
            //  If there wasn't a stencil mask set before this call, we can disable it safely
            gl.disable(gl.STENCIL_TEST);
            gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
        }

        gl.viewport(0, 0, renderer.width, renderer.height);

        renderer.currentProgram = null;
        renderer.currentVertexBuffer = null;
        renderer.currentIndexBuffer = null;

        renderer.setBlendMode(0, true);

        renderer.resetTextures();

        this.current = pipeline;

        pipeline.bind(true);
        pipeline.onBind();
    },

    /**
     * Flushes the current pipeline being used and then clears it, along with the
     * the current shader program and vertex buffer from the `WebGLRenderer`.
     *
     * Then resets the blend mode to NORMAL.
     *
     * Call this before jumping to your own gl context handler, and then call `rebind` when
     * you wish to return control to Phaser again.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#clear
     * @since 3.50.0
     */
    clear: function ()
    {
        var renderer = this.renderer;

        this.flush();

        this.previous = this.current;
        this.current = null;

        renderer.currentProgram = null;
        renderer.currentVertexBuffer = null;
        renderer.currentIndexBuffer = null;

        renderer.setBlendMode(0, true);
    },

    /**
     * Destroy the Pipeline Manager, cleaning up all related resources and references.
     *
     * @method Phaser.Renderer.WebGL.PipelineManager#destroy
     * @since 3.50.0
     */
    destroy: function ()
    {
        this.flush();

        this.pipelines.clear();

        this.renderer = null;
        this.game = null;
        this.pipelines = null;
        this.current = null;
        this.previous = null;
    }

});

module.exports = PipelineManager;
<|endoftext|>