<commit_before>/*global console, window*/

(function($) {

    "use strict";

    /**
     * Microsoft safe helper to spit out our little diagnostics information
     *
     * @ignore
     */
    function log(msg) {
        if(console && console.log) {
            console.log(msg);
        }
    }

    /**
     * Sample configuration object which can be passed to {@link jQuery.deps#enable}
     *
     * @class Configuration
     */
    var configExample = {

        /**
         * @cfg show Callback function show(elem) for showing elements
         * @type {Function}
         */
        show : null,

        /**
         * @cfg hide Callback function hide(elem) for hiding elements
         * @type {Function}
         */
        hide : null,

        /**
         * @cfg log Write console.log() output of rule applying
         * @type {Boolean}
         */
        log : false,

        /**
         * @cfg checkTargets When ruleset is enabled, check that all controllers and controls referred by ruleset exist on the page.
         *
         * @default true
         *
         * @type {Boolean}
         */
        checkTargets : true

    };

    /**
     * Define one field inter-dependency rule.
     *
     * When condition is true then this input and all
     * its children rules' inputs are visible.
     *
     * Possible condition strings:
     *
     *  * **==**  Widget value must be equal to given value
     *
     *  * **any** Widget value must be any of the values in the given value array
     *
     *  * **non-any** Widget value must not be any of the values in the given value array
     *
     *  * **!=** Widget value must not be qual to given value
     *
     *  * **()** Call value as a function(context, controller, ourWidgetValue) and if it's true then the condition is true
     *
     *  * **null** This input does not have any sub-conditions
     *
     *
     *
     */
    function Rule(controller, condition, value) {
        this.init(controller, condition, value);
    }

    $.extend(Rule.prototype, {

        /**
         * @method constructor
         *
         * @param {String} controller     jQuery expression to match the `<input>`   source
         *
         * @param {String} condition What input value must be that {@link Rule the rule takes effective}.
         *
         * @param value Matching value of **controller** when widgets become visible
         *
         */
        init : function(controller, condition, value) {
            this.controller = controller;

            this.condition = condition;

            this.value = value;

            // Child rules
            this.rules = [];

            // Controls shown/hidden by this rule
            this.controls = [];
        },

        /**
         * Evaluation engine
         *
         * @param  {String} condition Any of given conditions in Rule class description
         * @param  {Object} val1      The base value we compare against
         * @param  {Object} val2      Something we got out of input
         * @return {Boolean}          true or false
         */
        evalCondition : function(control, context, condition, val1, val2) {

           if(this.condition == "==") {
                return val1 == val2;
            } else if(condition == "!=") {
                return val1 != val2;
            } else if(condition == "()") {
                return val1(context, control, val2);
            } else if(condition == "any") {
                return $.inArray(val2, val1);
            } else if(condition == "not-any") {
                return !$.inArray(val2, val1);
            } else {
                throw new Error("Unknown condition:" + condition);
            }

        },

        /**
         * Evaluate the condition of this rule in given jQuery context.
         *
         * The widget value is extracted using getControlValue()
         *
         * @param {jQuery} context The jQuery selection in which this rule is evaluated.
         *
         */
        checkCondition : function(context, cfg) {

            // We do not have condition set, we are always true
            if(!this.condition) {
                return true;
            }

            var control = context.find(this.controller);
            if(control.size() === 0 && cfg.log) {
                log("Evaling condition: Could not find controller input " + this.controller);
            }

            var val = this.getControlValue(context, control);
            if(cfg.log && val === undefined) {
                log("Evaling condition: Could not exctract value from input " + this.controller);
            }

            if(val === undefined) {
                return false;
            }

            val = this.normalizeValue(control, this.value, val);

            return this.evalCondition(context, this.control, this.condition, this.value, val);
        },

        /**
         * Make sure that what we read from input field is comparable against Javascript primitives
         *
         */
        normalizeValue : function(control, baseValue, val) {

            if(typeof baseValue == "number") {
                // Make sure we compare numbers against numbers
                return parseFloat(val);
            }

            return val;
        },

        /**
         * Read value from a diffent HTML controls.
         *
         * Handle, text, checkbox, select.
         *
         */
        getControlValue : function(context, control) {

            // Handle checkboxes
            if(control.attr("type") == "checkbox") {
                return control.is(":checked");
            }

            return control.val();
        },

        /**
         * Create a sub-rule.
         *
         * Example:
         *
         *      var masterSwitch = ruleset.createRule("#mechanicalThrombectomyDevice")
         *      var twoAttempts = masterSwitch.createRule("#numberOfAttempts", "==", 2);
         *
         * @return Rule instance
         */
        createRule : function(controller, condition, value) {
            var rule = new Rule(controller, condition, value);
            this.rules.push(rule);
            return rule;
        },

        /**
         * Include a control in this rule.
         *
         * @param  {String} input     jQuery expression to match the input within ruleset context
         */
        include : function(input) {

            if(!input) {
                throw new Error("Must give an input selector");
            }

            this.controls.push(input);
        },

        /**
         * Apply this rule to all controls in the given context
         *
         * @param  {jQuery} context  jQuery selection within we operate
         * @param  {Object} cfg      {@link Configuration} object or undefined
         * @param  {Object} enforced Recursive rule enforcer: undefined to evaluate condition, true show always, false hide always
         *
         */
        applyRule : function(context, cfg, enforced) {

            var result;

            if(enforced === undefined) {
                result = this.checkCondition(context, cfg);
            } else {
                result = enforced;
            }

            if(cfg.log) {
                log("Applying rule on " + this.controller + "==" + this.value + " enforced:" + enforced + " result:" + result);
            }

            if(cfg.log && !this.controls.length) {
                log("Zero length controls slipped through");
            }

            // Get show/hide callback functions

            var show = cfg.show || function(control) {
                control.show();
            };

            var hide = cfg.show || function(control) {
                control.hide();
            };

            // Resolve controls from ids to jQuery selections
            // we are controlling in this context
            var controls = $.map(this.controls, function(elem, idx) {
                var control = context.find(elem);
                if(cfg.log && control.size() === 0) {
                    log("Could not find element:" + elem);
                }
                return control;
            });

            if(result) {

                $(controls).each(function() {

                    // Some friendly debug info
                    if(cfg.log && $(this).size() === 0) {
                        log("Control selection is empty when showing");
                        log(this);
                    }

                    show(this);
                });

                // Evaluate all child rules
                $(this.rules).each(function() {
                    this.applyRule(context, cfg);
                });

            } else {

                $(controls).each(function() {

                    // Some friendly debug info
                    if(cfg.log && $(this).size() === 0) {
                        log("Control selection is empty when hiding:");
                        log(this);
                    }

                    hide(this);
                });

                // Supress all child rules
                $(this.rules).each(function() {
                    this.applyRule(context, cfg, false);
                });
            }
        }
    });

    /**
     * A class which manages interdependenice rules.
     */
    function Ruleset() {

        // Hold a tree of rules
        this.rules = [];
    }

    $.extend(Ruleset.prototype, {

        /**
         * Add a new rule into this ruletset.
         *
         * See  {@link Rule} about the contstruction parameters.
         * @return {Rule}
         */
        createRule : function(controller, condition, value) {
            var rule = new Rule(controller, condition, value);
            this.rules.push(rule);
            return rule;
        },

        /**
         * Apply these rules on an element.
         *
         * @param {jQuery} context Selection we are dealing with
         *
         * @param cfg {@link Configuration} object or undefined.
         */
        applyRules: function(context, cfg) {
            var i;

            cfg = cfg || {};

            if(cfg.log) {
                log("Starting evaluation ruleset of " + this.rules.length + " rules");
            }

            for(i=0; i<this.rules.length; i++) {
                this.rules[i].applyRule(context, cfg);
            }
        },

        /**
         * Walk all rules and sub-rules in this ruleset
         * @param  {Function} callback(rule)
         *
         * @return {Array} Rules as depth-first searched
         */
        walk : function() {

            var rules = [];

            function descent(rule) {

                rules.push(rule);

                $(rule.children).each(function() {
                    descent(this);
                });
            }

            $(this.rules).each(function() {
                descent(this);
            });

            return rules;
        },

        /**
         * Check that all controllers and controls referred in ruleset exist.
         *
         * Throws an Error if any of them are missing.
         *
         * @param  {Configuration} cfg
         */
        checkTargets : function(context, cfg) {

            var controls = 0;
            var rules = this.walk();

            $(rules).each(function() {

                if(context.find(this.controller).size() === 0) {
                    throw new Error("Rule's controller does not exist:" + this.controller);
                }

                if(this.controls.length === 0) {
                    throw new Error("Rule has no controls:" + this);
                }

                $(this.controls).each(function() {
                    if(context.find(this).size() === 0) {
                        throw new Error("Rule's target control does not exist:" + this);
                    }

                    controls++;
                });

            });

            if(cfg.log) {
                log("Controller check ok, rules count " + rules.length + " controls count " + controls);
            }

        },

        /**
         * Make this ruleset effective on the whole page.
         *
         * Set event handler on **window.document** to catch all input events
         * and apply those events to defined rules.
         *
         * @param  {Configuration} cfg {@link Configuration} object or undefined
         *
         */
        install : function(cfg) {
            $.deps.enable($(document.body), this, cfg);
        }

    });

    /**
     * jQuery interdependencie plug-in
     *
     * @class jQuery.deps
     *
     */
    var deps = {

        /**
         * Create a new Ruleset instance.
         *
         * Example:
         *
         *      $(document).ready(function() {
         *           // Start creating a new ruleset
         *           var ruleset = $.deps.createRuleset();
         *
         *
         * @return {Ruleset}
         */
        createRuleset : function() {
            return new Ruleset();
        },

        /**
         * Enable ruleset on a specific jQuery selection.
         *
         * Checks the existince of all ruleset controllers and controls
         * by default (see config).
         *
         * See possible IE event bubbling problems: http://stackoverflow.com/q/265074/315168
         *
         * @param  {Object} selection jQuery selection in where we monitor all change events. All controls and controllers must exist within this selection.
         * @param  {Ruleset} ruleset
         * @param  {Configuration} cfg
         */
        enable : function(selection, ruleset, cfg) {

            cfg = cfg || {};

            if(cfg.checkTargets || cfg.checkTargets === undefined) {
                ruleset.checkTargets(selection, cfg);
            }

            var self = this;

            if(cfg.log) {
                log("Enabling dependency change monitoring on " + selection.get(0));
            }

            // Namespace our handler to avoid conflicts
            //
            var val = selection.live("change.deps", function() {
                ruleset.applyRules(selection, cfg);
            });

            ruleset.applyRules(selection, cfg);

            return val;
        }
    };

    $.deps = deps;

})(jQuery);<commit_msg>IE7 fix for an id selector bug<commit_after>/*global console, window*/

(function($) {

    "use strict";

    /**
     * Microsoft safe helper to spit out our little diagnostics information
     *
     * @ignore
     */
    function log(msg) {
        if(console && console.log) {
            console.log(msg);
        }
    }

    /**
     * jQuery.find() workaround for IE7
     *
     * If your selector is an pure tag id (#foo) IE7 finds nothing
     * if you do jQuery.find() in a specific jQuery context.
     *
     * This workaround makes a (false) assumptions
     * ids are always unique across the page.
     *
     * @ignore
     *
     * @param  {jQuery} context  jQuery context where we look child elements
     * @param  {String} selector selector as a string
     * @return {jQuery}          context.find() result
     */
    function safeFind(context, selector) {

        if(selector[0] == "#") {

            // Pseudo-check that this is a simple id selector
            // and not a complex jQuery selector
            if(selector.indexOf(" ") < 0) {
                return $(selector);
            }
        }

        return context.find(selector);
    }

    /**
     * Sample configuration object which can be passed to {@link jQuery.deps#enable}
     *
     * @class Configuration
     */
    var configExample = {

        /**
         * @cfg show Callback function show(elem) for showing elements
         * @type {Function}
         */
        show : null,

        /**
         * @cfg hide Callback function hide(elem) for hiding elements
         * @type {Function}
         */
        hide : null,

        /**
         * @cfg log Write console.log() output of rule applying
         * @type {Boolean}
         */
        log : false,

        /**
         * @cfg checkTargets When ruleset is enabled, check that all controllers and controls referred by ruleset exist on the page.
         *
         * @default true
         *
         * @type {Boolean}
         */
        checkTargets : true

    };

    /**
     * Define one field inter-dependency rule.
     *
     * When condition is true then this input and all
     * its children rules' inputs are visible.
     *
     * Possible condition strings:
     *
     *  * **==**  Widget value must be equal to given value
     *
     *  * **any** Widget value must be any of the values in the given value array
     *
     *  * **non-any** Widget value must not be any of the values in the given value array
     *
     *  * **!=** Widget value must not be qual to given value
     *
     *  * **()** Call value as a function(context, controller, ourWidgetValue) and if it's true then the condition is true
     *
     *  * **null** This input does not have any sub-conditions
     *
     *
     *
     */
    function Rule(controller, condition, value) {
        this.init(controller, condition, value);
    }

    $.extend(Rule.prototype, {

        /**
         * @method constructor
         *
         * @param {String} controller     jQuery expression to match the `<input>`   source
         *
         * @param {String} condition What input value must be that {@link Rule the rule takes effective}.
         *
         * @param value Matching value of **controller** when widgets become visible
         *
         */
        init : function(controller, condition, value) {
            this.controller = controller;

            this.condition = condition;

            this.value = value;

            // Child rules
            this.rules = [];

            // Controls shown/hidden by this rule
            this.controls = [];
        },

        /**
         * Evaluation engine
         *
         * @param  {String} condition Any of given conditions in Rule class description
         * @param  {Object} val1      The base value we compare against
         * @param  {Object} val2      Something we got out of input
         * @return {Boolean}          true or false
         */
        evalCondition : function(control, context, condition, val1, val2) {

           if(this.condition == "==") {
                return val1 == val2;
            } else if(condition == "!=") {
                return val1 != val2;
            } else if(condition == "()") {
                return val1(context, control, val2);
            } else if(condition == "any") {
                return $.inArray(val2, val1);
            } else if(condition == "not-any") {
                return !$.inArray(val2, val1);
            } else {
                throw new Error("Unknown condition:" + condition);
            }

        },

        /**
         * Evaluate the condition of this rule in given jQuery context.
         *
         * The widget value is extracted using getControlValue()
         *
         * @param {jQuery} context The jQuery selection in which this rule is evaluated.
         *
         */
        checkCondition : function(context, cfg) {

            // We do not have condition set, we are always true
            if(!this.condition) {
                return true;
            }

            var control = context.find(this.controller);
            if(control.size() === 0 && cfg.log) {
                log("Evaling condition: Could not find controller input " + this.controller);
            }

            var val = this.getControlValue(context, control);
            if(cfg.log && val === undefined) {
                log("Evaling condition: Could not exctract value from input " + this.controller);
            }

            if(val === undefined) {
                return false;
            }

            val = this.normalizeValue(control, this.value, val);

            return this.evalCondition(context, this.control, this.condition, this.value, val);
        },

        /**
         * Make sure that what we read from input field is comparable against Javascript primitives
         *
         */
        normalizeValue : function(control, baseValue, val) {

            if(typeof baseValue == "number") {
                // Make sure we compare numbers against numbers
                return parseFloat(val);
            }

            return val;
        },

        /**
         * Read value from a diffent HTML controls.
         *
         * Handle, text, checkbox, select.
         *
         */
        getControlValue : function(context, control) {

            // Handle checkboxes
            if(control.attr("type") == "checkbox") {
                return control.is(":checked");
            }

            return control.val();
        },

        /**
         * Create a sub-rule.
         *
         * Example:
         *
         *      var masterSwitch = ruleset.createRule("#mechanicalThrombectomyDevice")
         *      var twoAttempts = masterSwitch.createRule("#numberOfAttempts", "==", 2);
         *
         * @return Rule instance
         */
        createRule : function(controller, condition, value) {
            var rule = new Rule(controller, condition, value);
            this.rules.push(rule);
            return rule;
        },

        /**
         * Include a control in this rule.
         *
         * @param  {String} input     jQuery expression to match the input within ruleset context
         */
        include : function(input) {

            if(!input) {
                throw new Error("Must give an input selector");
            }

            this.controls.push(input);
        },

        /**
         * Apply this rule to all controls in the given context
         *
         * @param  {jQuery} context  jQuery selection within we operate
         * @param  {Object} cfg      {@link Configuration} object or undefined
         * @param  {Object} enforced Recursive rule enforcer: undefined to evaluate condition, true show always, false hide always
         *
         */
        applyRule : function(context, cfg, enforced) {

            var result;

            if(enforced === undefined) {
                result = this.checkCondition(context, cfg);
            } else {
                result = enforced;
            }

            if(cfg.log) {
                log("Applying rule on " + this.controller + "==" + this.value + " enforced:" + enforced + " result:" + result);
            }

            if(cfg.log && !this.controls.length) {
                log("Zero length controls slipped through");
            }

            // Get show/hide callback functions

            var show = cfg.show || function(control) {
                control.show();
            };

            var hide = cfg.show || function(control) {
                control.hide();
            };

            // Resolve controls from ids to jQuery selections
            // we are controlling in this context
            var controls = $.map(this.controls, function(elem, idx) {
                var control = context.find(elem);
                if(cfg.log && control.size() === 0) {
                    log("Could not find element:" + elem);
                }
                return control;
            });

            if(result) {

                $(controls).each(function() {

                    // Some friendly debug info
                    if(cfg.log && $(this).size() === 0) {
                        log("Control selection is empty when showing");
                        log(this);
                    }

                    show(this);
                });

                // Evaluate all child rules
                $(this.rules).each(function() {
                    this.applyRule(context, cfg);
                });

            } else {

                $(controls).each(function() {

                    // Some friendly debug info
                    if(cfg.log && $(this).size() === 0) {
                        log("Control selection is empty when hiding:");
                        log(this);
                    }

                    hide(this);
                });

                // Supress all child rules
                $(this.rules).each(function() {
                    this.applyRule(context, cfg, false);
                });
            }
        }
    });

    /**
     * A class which manages interdependenice rules.
     */
    function Ruleset() {

        // Hold a tree of rules
        this.rules = [];
    }

    $.extend(Ruleset.prototype, {

        /**
         * Add a new rule into this ruletset.
         *
         * See  {@link Rule} about the contstruction parameters.
         * @return {Rule}
         */
        createRule : function(controller, condition, value) {
            var rule = new Rule(controller, condition, value);
            this.rules.push(rule);
            return rule;
        },

        /**
         * Apply these rules on an element.
         *
         * @param {jQuery} context Selection we are dealing with
         *
         * @param cfg {@link Configuration} object or undefined.
         */
        applyRules: function(context, cfg) {
            var i;

            cfg = cfg || {};

            if(cfg.log) {
                log("Starting evaluation ruleset of " + this.rules.length + " rules");
            }

            for(i=0; i<this.rules.length; i++) {
                this.rules[i].applyRule(context, cfg);
            }
        },

        /**
         * Walk all rules and sub-rules in this ruleset
         * @param  {Function} callback(rule)
         *
         * @return {Array} Rules as depth-first searched
         */
        walk : function() {

            var rules = [];

            function descent(rule) {

                rules.push(rule);

                $(rule.children).each(function() {
                    descent(this);
                });
            }

            $(this.rules).each(function() {
                descent(this);
            });

            return rules;
        },

        /**
         * Check that all controllers and controls referred in ruleset exist.
         *
         * Throws an Error if any of them are missing.
         *
         * @param {jQuery} context jQuery selection of items
         *
         * @param  {Configuration} cfg
         */
        checkTargets : function(context, cfg) {

            var controls = 0;
            var rules = this.walk();

            $(rules).each(function() {

                if(context.find(this.controller).size() === 0) {
                    throw new Error("Rule's controller does not exist:" + this.controller);
                }

                if(this.controls.length === 0) {
                    throw new Error("Rule has no controls:" + this);
                }

                $(this.controls).each(function() {

                    if(safeFind(context, this) === 0) {
                        throw new Error("Rule's target control " + this + " does not exist in context " + context.get(0));
                    }

                    controls++;
                });

            });

            if(cfg.log) {
                log("Controller check ok, rules count " + rules.length + " controls count " + controls);
            }

        },

        /**
         * Make this ruleset effective on the whole page.
         *
         * Set event handler on **window.document** to catch all input events
         * and apply those events to defined rules.
         *
         * @param  {Configuration} cfg {@link Configuration} object or undefined
         *
         */
        install : function(cfg) {
            $.deps.enable($(document.body), this, cfg);
        }

    });

    /**
     * jQuery interdependencie plug-in
     *
     * @class jQuery.deps
     *
     */
    var deps = {

        /**
         * Create a new Ruleset instance.
         *
         * Example:
         *
         *      $(document).ready(function() {
         *           // Start creating a new ruleset
         *           var ruleset = $.deps.createRuleset();
         *
         *
         * @return {Ruleset}
         */
        createRuleset : function() {
            return new Ruleset();
        },

        /**
         * Enable ruleset on a specific jQuery selection.
         *
         * Checks the existince of all ruleset controllers and controls
         * by default (see config).
         *
         * See possible IE event bubbling problems: http://stackoverflow.com/q/265074/315168
         *
         * @param  {Object} selection jQuery selection in where we monitor all change events. All controls and controllers must exist within this selection.
         * @param  {Ruleset} ruleset
         * @param  {Configuration} cfg
         */
        enable : function(selection, ruleset, cfg) {

            cfg = cfg || {};

            if(cfg.checkTargets || cfg.checkTargets === undefined) {
                ruleset.checkTargets(selection, cfg);
            }

            var self = this;

            if(cfg.log) {
                log("Enabling dependency change monitoring on " + selection.get(0));
            }

            // Namespace our handler to avoid conflicts
            //
            var val = selection.live("change.deps", function() {
                ruleset.applyRules(selection, cfg);
            });

            ruleset.applyRules(selection, cfg);

            return val;
        }
    };

    $.deps = deps;

})(jQuery);<|endoftext|>


<commit_before>$(function() {
	function isImage(type) {
		switch (type) {
		case 'image/jpeg':
		case 'image/png':
		case 'image/gif':
		case 'image/bmp':
		case 'image/jpg':
			return true;
		default:
			return false;
		}
	}
	function computeSize(size) {
		var units = ['', 'K', 'M', 'G', 'T'];
		while (size / 1024 >= .75) {
			size = size / 1024;
			units.shift();
		}
		size = Math.round(size * 10) / 10 + units[0] + 'B';
		return size;
	}
	function fixTransparentPNG() {
		var img = new Image;
		var fr = new FileReader;
		var file_name = file.name;
		fr.onload = function(e) {
			img.src = fr.result;
			Ripple.helpers.image2canvas(img).
			next(function(canvas) {
				var ctx = canvas.getContext('2d');
				var image_data = ctx.getImageData(0, 0, canvas.width, canvas.height);
				var pixel_array = image_data.data;
				var m, a, s;
				for (var i = 0, len = pixel_array.length; i < len; i += 4) {
					a = pixel_array[i+3];
					if (a === 255) continue;
					s = 255 - a;
					a /= 255;
					m = 3;
					while (m--) {
						pixel_array[i+m] = pixel_array[i+m] * a + s;
					}
					pixel_array[i+3] = 255;
				}
				ctx.putImageData(image_data, 0, 0);
				canvas.toBlob(function(blob) {
					file = blob;
					file.name = file_name;
					setTip();
				});
			});
		}
		fr.readAsDataURL(file);
	}
	function setTip() {
		var tip = file.name + ' (' + computeSize(file.size) + ')';
		$('#tip').text(tip).prop('title', tip);
	}
	function submit() {
		if (! file) return;
		PREFiX.user.postPhoto({
			photo: file,
			status: $status.val().trim()
		}).setupAjax({
			lock: $status,
			timeout: 2 * 60 * 1000,
			onstart: function(e) {
				$('#img-droparea').addClass('loading');
			},
			onprogress: function(e) {
				if (! e.lengthComputable) return;
				var percent = Math.floor(e.loaded / e.total * 100);
				document.title = 'PREFiX - 上传照片 (' + percent + '%)';
			},
			oncomplete: function(e) {
				$('#img-droparea').removeClass('loading');
				document.title = 'PREFiX - 上传照片';
			}
		}).next(function() {
			PREFiX.update();
			close();
		});
	}

	var bg_win = chrome.extension.getBackgroundPage();
	var PREFiX = bg_win.PREFiX;
	var Ripple = bg_win.Ripple;
	var file;

	var $wrapper = $('#wrapper');

	$wrapper.on({
		dragenter: function(e) {},
		dragover: function(e) {
			e.stopPropagation();
			e.preventDefault();
		},
		dragleave: function(e) {},
		drop: function(e) {
			e = e.originalEvent;

			e.stopPropagation();
			e.preventDefault();

			file = e.dataTransfer.files[0];
			if (! file || ! isImage(file.type))
				return file = null;
			if (file.type === 'image/png') {
				fixTransparentPNG();
			}

			setTip();
		}
	});

	var $file = $('#file');

	$file.on('change', function(e) {
		file = $file[0].files[0];
		if (! file || ! isImage(file.type))
			return file = null;
		if (file.type === 'image/png') {
			fixTransparentPNG();
		}
		setTip();
	});

	var $status = $('#status');

	$status.on({
		input: function(e) {
			var length = $status.val().trim().length;
			$status.toggleClass('over', length > 140);
		},
		keyup: function(e) {
			if (e.which == 13 && e.ctrlKey)
				submit();
		},
		dblclick: function(e) {
			submit();
		}
	});

	$(window).scroll(function(e) {
		$('body').scrollTop(0);
	});

	var fixing_size = false;
	onresize = _.throttle(function() {
		if (fixing_size) return;
		fixing_size = true;
		var de = document.documentElement;
		resizeTo(316, 192);
		setTimeout(function() {
			resizeBy(300 - de.clientWidth, 150 - de.clientHeight);
			setTimeout(function() {
				fixing_size = false;
			}, 16);
		}, 16);
	}, 16);
	onresize();
});<commit_msg>修改参数以兼容 Mac<commit_after>$(function() {
	function isImage(type) {
		switch (type) {
		case 'image/jpeg':
		case 'image/png':
		case 'image/gif':
		case 'image/bmp':
		case 'image/jpg':
			return true;
		default:
			return false;
		}
	}
	function computeSize(size) {
		var units = ['', 'K', 'M', 'G', 'T'];
		while (size / 1024 >= .75) {
			size = size / 1024;
			units.shift();
		}
		size = Math.round(size * 10) / 10 + units[0] + 'B';
		return size;
	}
	function fixTransparentPNG() {
		var img = new Image;
		var fr = new FileReader;
		var file_name = file.name;
		fr.onload = function(e) {
			img.src = fr.result;
			Ripple.helpers.image2canvas(img).
			next(function(canvas) {
				var ctx = canvas.getContext('2d');
				var image_data = ctx.getImageData(0, 0, canvas.width, canvas.height);
				var pixel_array = image_data.data;
				var m, a, s;
				for (var i = 0, len = pixel_array.length; i < len; i += 4) {
					a = pixel_array[i+3];
					if (a === 255) continue;
					s = 255 - a;
					a /= 255;
					m = 3;
					while (m--) {
						pixel_array[i+m] = pixel_array[i+m] * a + s;
					}
					pixel_array[i+3] = 255;
				}
				ctx.putImageData(image_data, 0, 0);
				canvas.toBlob(function(blob) {
					file = blob;
					file.name = file_name;
					setTip();
				});
			});
		}
		fr.readAsDataURL(file);
	}
	function setTip() {
		var tip = file.name + ' (' + computeSize(file.size) + ')';
		$('#tip').text(tip).prop('title', tip);
	}
	function submit() {
		if (! file) return;
		PREFiX.user.postPhoto({
			photo: file,
			status: $status.val().trim()
		}).setupAjax({
			lock: $status,
			timeout: 2 * 60 * 1000,
			onstart: function(e) {
				$('#img-droparea').addClass('loading');
			},
			onprogress: function(e) {
				if (! e.lengthComputable) return;
				var percent = Math.floor(e.loaded / e.total * 100);
				document.title = 'PREFiX - 上传照片 (' + percent + '%)';
			},
			oncomplete: function(e) {
				$('#img-droparea').removeClass('loading');
				document.title = 'PREFiX - 上传照片';
			}
		}).next(function() {
			PREFiX.update();
			close();
		});
	}

	var bg_win = chrome.extension.getBackgroundPage();
	var PREFiX = bg_win.PREFiX;
	var Ripple = bg_win.Ripple;
	var file;

	var $wrapper = $('#wrapper');

	$wrapper.on({
		dragenter: function(e) {},
		dragover: function(e) {
			e.stopPropagation();
			e.preventDefault();
		},
		dragleave: function(e) {},
		drop: function(e) {
			e = e.originalEvent;

			e.stopPropagation();
			e.preventDefault();

			file = e.dataTransfer.files[0];
			if (! file || ! isImage(file.type))
				return file = null;
			if (file.type === 'image/png') {
				fixTransparentPNG();
			}

			setTip();
		}
	});

	var $file = $('#file');

	$file.on('change', function(e) {
		file = $file[0].files[0];
		if (! file || ! isImage(file.type))
			return file = null;
		if (file.type === 'image/png') {
			fixTransparentPNG();
		}
		setTip();
	});

	var $status = $('#status');

	$status.on({
		input: function(e) {
			var length = $status.val().trim().length;
			$status.toggleClass('over', length > 140);
		},
		keyup: function(e) {
			if (e.which == 13 && e.ctrlKey)
				submit();
		},
		dblclick: function(e) {
			submit();
		}
	});

	$(window).scroll(function(e) {
		$('body').scrollTop(0);
	});

	var fixing_size = false;
	onresize = _.throttle(function() {
		if (fixing_size) return;
		fixing_size = true;
		var de = document.documentElement;
		resizeTo(316, 192);
		setTimeout(function() {
			resizeBy(300 - de.clientWidth, 150 - de.clientHeight);
			setTimeout(function() {
				fixing_size = false;
			}, 200);
		}, 100);
	}, 100);
	onresize();
});<|endoftext|>


<commit_before>/**
 * The Urthecast layer module for use on canvas.
 *
 * @return UrthecastLayer class (extends CartoDBLayerClass)
 */
define([
  'abstract/layer/ImageLayerClass',
  'uri',
  'moment',
  'map/views/layers/CustomInfowindow',
], function(ImageLayerClass, UriTemplate, moment, CustomInfowindow) {

  'use strict';

  var UrthecastLayer = ImageLayerClass.extend({
    options: {
      urlTemplate:'http://uc.gfw-apis.appspot.com/urthecast/map-tiles{/sat}{/z}{/x}{/y}?cloud_coverage_lte={cloud}&acquired_gte={mindate}&acquired_lte={maxdate}T23:59:59Z&sensor_platform={sensor_platform}',
      urlInfoWindow: 'http://uc.gfw-apis.appspot.com/urthecast/archive/scenes/?geometry_intersects=POINT({lng}+{lat})&cloud_coverage_lte={cloud}&tiled_lte={tileddate}&acquired_gte={mindate}&acquired_lte={maxdate}&sort=-acquired&sensor_platform={sensor_platform}',
      urlBounds: 'http://uc.gfw-apis.appspot.com/urthecast/archive/scenes/?cloud_coverage_lte={cloud}&tiled_lte={tileddate}&acquired_gte={mindate}&acquired_lte={maxdate}&geometry_intersects={geo}&sort=-acquired&sensor_platform={sensor_platform}',
      dataMaxZoom: {
        'rgb': 14,
        'ndvi': 13,
        'evi': 13,
        'ndwi': 13,
        'false-color-nir' : 13
      },

      infowindowImagelayer: true
    },

    init: function(layer, options, map) {
      this._super(layer, options, map);
      this.addEvents();
    },

    _getParams: function() {
      var params = {};
      if (window.location.search.contains('hresolution=') && window.location.search.indexOf('=', window.location.search.indexOf('hresolution=') + 11) !== -1) {
        var params_new_url = {};
        var parts = location.search.substring(1).split('&');
        for (var i = 0; i < parts.length; i++) {
          var nv = parts[i].split('=');
          if (!nv[0]) continue;
            params_new_url[nv[0]] = nv[1] || true;
        }
        params = JSON.parse(atob(params_new_url.hresolution));
      }
      else if (!!sessionStorage.getItem('high-resolution')) {
        params = JSON.parse(atob(sessionStorage.getItem('high-resolution')));
      }
      params = {
         'color_filter': params.color_filter || 'rgb',
         'cloud':        params.cloud        || '100',
         'mindate':      params.mindate      || '2000-09-01',
         'maxdate':      params.maxdate      || '2015-09-01',
         'sensor_platform' : params.sensor_platform || 'theia,landsat-8,deimos-1'
        }
      return params;
    },

    _getUrl: function(x, y, z, params) {
      return new UriTemplate(this.options.urlTemplate).fillFromObject({
        x: x,
        y: y,
        z: z,
        sat: params.color_filter,
        cloud: params.cloud,
        mindate: params.mindate,
        maxdate: params.maxdate,
        sensor_platform: params.sensor_platform
      });
    },

    _getInfoWindowUrl: function(params) {
      return new UriTemplate(this.options.urlInfoWindow).fillFromObject({
        lng: params.lng,
        lat: params.lat,
        cloud: params.cloud,
        mindate: moment(params.mindate).format("YYYY-MM-DD"),
        maxdate: moment(params.maxdate).format("YYYY-MM-DD"),
        tileddate: params.tileddate,
        sensor_platform: params.sensor_platform
      });
    },

    _getBoundsUrl: function(params) {
      return new UriTemplate(this.options.urlBounds).fillFromObject({
        geo: params.geo,
        cloud: params.cloud,
        mindate: moment(params.mindate).format("YYYY-MM-DD"),
        maxdate: moment(params.maxdate).format("YYYY-MM-DD"),
        tileddate: params.tileddate,
        sensor_platform: params.sensor_platform
      });
    },

    // TILES
    getTile: function(coord, zoom, ownerDocument) {

      if(zoom < 5) {
        return;
      }
      var zsteps = this._getZoomSteps(zoom);
      var srcX = 256 * (coord.x % Math.pow(2, zsteps));
      var srcY = 256 * (coord.y % Math.pow(2, zsteps));
      var widthandheight = (zsteps > 0) ? 256 * Math.pow(2, zsteps) + 'px' : this.tileSize.width + 'px';

      var url = this._getUrl.apply(this,
        this._getTileCoords(coord.x, coord.y, zoom,this._getParams()));

      // Image to render
      var image = new Image();
      image.src = url;
      image.className += this.name;
      image.style.position = 'absolute';
      image.style.top      = -srcY + 'px';
      image.style.left     = -srcX + 'px';
      image.style.width = '100%';
      image.style.height = '100%';

      // Loader
      var loader = ownerDocument.createElement('div');
      loader.className += 'loader spinner start';
      loader.style.position = 'absolute';
      loader.style.top      = '50%';
      loader.style.left     = '50%';
      loader.style.border = '4px solid #FFF';
      loader.style.borderRadius = '50%';
      loader.style.borderTopColor = '#555';

      // Wwrap the loader and the image
      var div = ownerDocument.createElement('div');
      div.appendChild(image);
      div.appendChild(loader);
      div.style.width = widthandheight;
      div.style.height = widthandheight;
      div.style.position = 'relative';
      div.style.overflow = 'hidden';
      div.className += this.name;

      image.onload = function() {
        div.removeChild(loader);
      };

      image.onerror = function() {
        div.removeChild(loader)
        this.style.display = 'none';
      };

      return div;
    },

    _getTileCoords: function(x, y, z, params) {
      var maxZoom = this.options.dataMaxZoom[params['color_filter']];
      if (z > maxZoom) {
        x = Math.floor(x / (Math.pow(2, z - maxZoom)));
        y = Math.floor(y / (Math.pow(2, z - maxZoom)));
        z = maxZoom;
      } else {
        y = (y > Math.pow(2, z) ? y % Math.pow(2, z) : y);
        if (x >= Math.pow(2, z)) {
          x = x % Math.pow(2, z);
        } else if (x < 0) {
          x = Math.pow(2, z) - Math.abs(x);
        }
      }

      return [x, y, z, params];
    },

    // INFOWINDOW
    setInfoWindow: function (_data, event) {
      var data = _data;
      if (!!data) {
        var infoWindowOptions = {
          offset: [0, 100],
          infowindowData: {
            acquired: moment(data['acquired']).format("MMMM Do, YYYY"),
            platform: data['platform'].toUpperCase(),
            sensor_platform: data['sensor_platform'].toUpperCase(),
            cloud_coverage: (data['cloud_coverage']) ? Math.ceil( data['cloud_coverage'] * 10) / 10 : '0'
          }
        }
        this.infowindow = new CustomInfowindow(event.latLng, this.map, infoWindowOptions);
        this.removeMultipolygon();
        this.drawMultipolygon(data.geometry);
      }
    },

    removeInfoWindow: function() {
      if(this.infowindow) {
        this.infowindow.remove();
      }
    },

    // MAP EVENTS
    addEvents: function() {
      this.clickevent = google.maps.event.addListener(this.map, "click", _.bind(this.onClickEvent, this ));
      this.dragendevent = google.maps.event.addListener(this.map, "dragend", _.bind(this.checkForImagesInBounds, this ));
    },

    clearEvents: function() {
      google.maps.event.clearListeners(this.map, 'dragend')
    },

    checkForImagesInBounds: function() {
      // // Set Date
      var today = moment();
      var tomorrow = today.add('days', 1);
      var geo = this.getBoundsPolygon();

      if (!!geo) {
        // Set options to get the url of the api
        var options = _.extend({}, this._getParams(), {
          geo: geo,
          tileddate: moment(tomorrow).format("YYYY-MM-DD"),
        });
        var url = this._getBoundsUrl(options);
        $.get(url).done(_.bind(function(data) {
          this.hidenotification();
          if (!!data && !!data.payload && !data.payload.length) {
            this.notificate('not-no-images-urthecast');
          }
        }, this ));
      }
    },

    onClickEvent: function(event) {
      // Set Date
      var today = moment();
      var tomorrow = today.add('days', 1);

      // Set options to get the url of the api
      var options = _.extend({}, this._getParams(), {
        lng: event.latLng.lng(),
        lat: event.latLng.lat(),
        tileddate: moment(tomorrow).format("YYYY-MM-DD"),
      });
      var url = this._getInfoWindowUrl(options);

      $.get(url).done(_.bind(function(data) {
        this.removeInfoWindow();
        this.setInfoWindow(data.payload[0], event);
      }, this ));
    },

    // HELPERS
    setStyle: function() {
      this.map.data.setStyle(_.bind(function(feature){
        var strokeColor = (feature.getProperty('color')) ? feature.getProperty('color') : '#A2BC28';
        return ({
          strokeWeight: 2,
          fillOpacity: 0,
          fillColor: '#FFF',
          strokeColor: strokeColor
        });
      }, this ));
    },

    drawMultipolygon: function(geom) {
      var multipolygon_todraw = {
        type: "Feature",
        geometry: geom
      }

      this.multipolygon = this.map.data.addGeoJson(multipolygon_todraw)[0];
      if (this.listener) {
        google.maps.event.removeListener(this.listener, 'click');
      }
      this.listener = this.map.data.addListener("click", function(e){
        google.maps.event.trigger(this.map, 'click', e);
      }.bind(this));
      this.setStyle();
    },

    getBoundsPolygon: function() {
      var bounds = this.map.getBounds();
      if (!!bounds) {
        var nlat = bounds.getNorthEast().lat(),
            nlng = bounds.getNorthEast().lng(),
            slat = bounds.getSouthWest().lat(),
            slng = bounds.getSouthWest().lng();

        // Define the LngLat coordinates for the polygon.
        var boundsJson = {
          "type": "Polygon",
          "coordinates":[[
            [slng,nlat],
            [nlng,nlat],
            [nlng,slat],
            [slng,slat],
            [slng,nlat]
          ]]
        }
        return JSON.stringify(boundsJson);
      }
      return null;
    },

    _getZoomSteps: function(z) {
      var params = this._getParams();
      return z - this.options.dataMaxZoom[params['color_filter']];
    },

  });

  return UrthecastLayer;

});
<commit_msg>Use pro/staging API for urthecast<commit_after>/**
 * The Urthecast layer module for use on canvas.
 *
 * @return UrthecastLayer class (extends CartoDBLayerClass)
 */
define([
  'abstract/layer/ImageLayerClass',
  'uri',
  'moment',
  'map/views/layers/CustomInfowindow',
], function(ImageLayerClass, UriTemplate, moment, CustomInfowindow) {

  'use strict';

  var API_URL = window.gfw.config.GFW_API_HOST;

  var UrthecastLayer = ImageLayerClass.extend({
    options: {
      urlTemplate: API_URL + '/urthecast/map-tiles{/sat}{/z}{/x}{/y}?cloud_coverage_lte={cloud}&acquired_gte={mindate}&acquired_lte={maxdate}T23:59:59Z&sensor_platform={sensor_platform}',
      urlInfoWindow: API_URL + '/urthecast/archive/scenes/?geometry_intersects=POINT({lng}+{lat})&cloud_coverage_lte={cloud}&tiled_lte={tileddate}&acquired_gte={mindate}&acquired_lte={maxdate}&sort=-acquired&sensor_platform={sensor_platform}',
      urlBounds: API_URL + '/urthecast/archive/scenes/?cloud_coverage_lte={cloud}&tiled_lte={tileddate}&acquired_gte={mindate}&acquired_lte={maxdate}&geometry_intersects={geo}&sort=-acquired&sensor_platform={sensor_platform}',
      dataMaxZoom: {
        'rgb': 14,
        'ndvi': 13,
        'evi': 13,
        'ndwi': 13,
        'false-color-nir' : 13
      },

      infowindowImagelayer: true
    },

    init: function(layer, options, map) {
      this._super(layer, options, map);
      this.addEvents();
    },

    _getParams: function() {
      var params = {};
      if (window.location.search.contains('hresolution=') && window.location.search.indexOf('=', window.location.search.indexOf('hresolution=') + 11) !== -1) {
        var params_new_url = {};
        var parts = location.search.substring(1).split('&');
        for (var i = 0; i < parts.length; i++) {
          var nv = parts[i].split('=');
          if (!nv[0]) continue;
            params_new_url[nv[0]] = nv[1] || true;
        }
        params = JSON.parse(atob(params_new_url.hresolution));
      }
      else if (!!sessionStorage.getItem('high-resolution')) {
        params = JSON.parse(atob(sessionStorage.getItem('high-resolution')));
      }
      params = {
         'color_filter': params.color_filter || 'rgb',
         'cloud':        params.cloud        || '100',
         'mindate':      params.mindate      || '2000-09-01',
         'maxdate':      params.maxdate      || '2015-09-01',
         'sensor_platform' : params.sensor_platform || 'theia,landsat-8,deimos-1'
        }
      return params;
    },

    _getUrl: function(x, y, z, params) {
      return new UriTemplate(this.options.urlTemplate).fillFromObject({
        x: x,
        y: y,
        z: z,
        sat: params.color_filter,
        cloud: params.cloud,
        mindate: params.mindate,
        maxdate: params.maxdate,
        sensor_platform: params.sensor_platform
      });
    },

    _getInfoWindowUrl: function(params) {
      return new UriTemplate(this.options.urlInfoWindow).fillFromObject({
        lng: params.lng,
        lat: params.lat,
        cloud: params.cloud,
        mindate: moment(params.mindate).format("YYYY-MM-DD"),
        maxdate: moment(params.maxdate).format("YYYY-MM-DD"),
        tileddate: params.tileddate,
        sensor_platform: params.sensor_platform
      });
    },

    _getBoundsUrl: function(params) {
      return new UriTemplate(this.options.urlBounds).fillFromObject({
        geo: params.geo,
        cloud: params.cloud,
        mindate: moment(params.mindate).format("YYYY-MM-DD"),
        maxdate: moment(params.maxdate).format("YYYY-MM-DD"),
        tileddate: params.tileddate,
        sensor_platform: params.sensor_platform
      });
    },

    // TILES
    getTile: function(coord, zoom, ownerDocument) {

      if(zoom < 5) {
        return;
      }
      var zsteps = this._getZoomSteps(zoom);
      var srcX = 256 * (coord.x % Math.pow(2, zsteps));
      var srcY = 256 * (coord.y % Math.pow(2, zsteps));
      var widthandheight = (zsteps > 0) ? 256 * Math.pow(2, zsteps) + 'px' : this.tileSize.width + 'px';

      var url = this._getUrl.apply(this,
        this._getTileCoords(coord.x, coord.y, zoom,this._getParams()));

      // Image to render
      var image = new Image();
      image.src = url;
      image.className += this.name;
      image.style.position = 'absolute';
      image.style.top      = -srcY + 'px';
      image.style.left     = -srcX + 'px';
      image.style.width = '100%';
      image.style.height = '100%';

      // Loader
      var loader = ownerDocument.createElement('div');
      loader.className += 'loader spinner start';
      loader.style.position = 'absolute';
      loader.style.top      = '50%';
      loader.style.left     = '50%';
      loader.style.border = '4px solid #FFF';
      loader.style.borderRadius = '50%';
      loader.style.borderTopColor = '#555';

      // Wwrap the loader and the image
      var div = ownerDocument.createElement('div');
      div.appendChild(image);
      div.appendChild(loader);
      div.style.width = widthandheight;
      div.style.height = widthandheight;
      div.style.position = 'relative';
      div.style.overflow = 'hidden';
      div.className += this.name;

      image.onload = function() {
        div.removeChild(loader);
      };

      image.onerror = function() {
        div.removeChild(loader)
        this.style.display = 'none';
      };

      return div;
    },

    _getTileCoords: function(x, y, z, params) {
      var maxZoom = this.options.dataMaxZoom[params['color_filter']];
      if (z > maxZoom) {
        x = Math.floor(x / (Math.pow(2, z - maxZoom)));
        y = Math.floor(y / (Math.pow(2, z - maxZoom)));
        z = maxZoom;
      } else {
        y = (y > Math.pow(2, z) ? y % Math.pow(2, z) : y);
        if (x >= Math.pow(2, z)) {
          x = x % Math.pow(2, z);
        } else if (x < 0) {
          x = Math.pow(2, z) - Math.abs(x);
        }
      }

      return [x, y, z, params];
    },

    // INFOWINDOW
    setInfoWindow: function (_data, event) {
      var data = _data;
      if (!!data) {
        var infoWindowOptions = {
          offset: [0, 100],
          infowindowData: {
            acquired: moment(data['acquired']).format("MMMM Do, YYYY"),
            platform: data['platform'].toUpperCase(),
            sensor_platform: data['sensor_platform'].toUpperCase(),
            cloud_coverage: (data['cloud_coverage']) ? Math.ceil( data['cloud_coverage'] * 10) / 10 : '0'
          }
        }
        this.infowindow = new CustomInfowindow(event.latLng, this.map, infoWindowOptions);
        this.removeMultipolygon();
        this.drawMultipolygon(data.geometry);
      }
    },

    removeInfoWindow: function() {
      if(this.infowindow) {
        this.infowindow.remove();
      }
    },

    // MAP EVENTS
    addEvents: function() {
      this.clickevent = google.maps.event.addListener(this.map, "click", _.bind(this.onClickEvent, this ));
      this.dragendevent = google.maps.event.addListener(this.map, "dragend", _.bind(this.checkForImagesInBounds, this ));
    },

    clearEvents: function() {
      google.maps.event.clearListeners(this.map, 'dragend')
    },

    checkForImagesInBounds: function() {
      // // Set Date
      var today = moment();
      var tomorrow = today.add('days', 1);
      var geo = this.getBoundsPolygon();

      if (!!geo) {
        // Set options to get the url of the api
        var options = _.extend({}, this._getParams(), {
          geo: geo,
          tileddate: moment(tomorrow).format("YYYY-MM-DD"),
        });
        var url = this._getBoundsUrl(options);
        $.get(url).done(_.bind(function(data) {
          this.hidenotification();
          if (!!data && !!data.payload && !data.payload.length) {
            this.notificate('not-no-images-urthecast');
          }
        }, this ));
      }
    },

    onClickEvent: function(event) {
      // Set Date
      var today = moment();
      var tomorrow = today.add('days', 1);

      // Set options to get the url of the api
      var options = _.extend({}, this._getParams(), {
        lng: event.latLng.lng(),
        lat: event.latLng.lat(),
        tileddate: moment(tomorrow).format("YYYY-MM-DD"),
      });
      var url = this._getInfoWindowUrl(options);

      $.get(url).done(_.bind(function(data) {
        this.removeInfoWindow();
        this.setInfoWindow(data.payload[0], event);
      }, this ));
    },

    // HELPERS
    setStyle: function() {
      this.map.data.setStyle(_.bind(function(feature){
        var strokeColor = (feature.getProperty('color')) ? feature.getProperty('color') : '#A2BC28';
        return ({
          strokeWeight: 2,
          fillOpacity: 0,
          fillColor: '#FFF',
          strokeColor: strokeColor
        });
      }, this ));
    },

    drawMultipolygon: function(geom) {
      var multipolygon_todraw = {
        type: "Feature",
        geometry: geom
      }

      this.multipolygon = this.map.data.addGeoJson(multipolygon_todraw)[0];
      if (this.listener) {
        google.maps.event.removeListener(this.listener, 'click');
      }
      this.listener = this.map.data.addListener("click", function(e){
        google.maps.event.trigger(this.map, 'click', e);
      }.bind(this));
      this.setStyle();
    },

    getBoundsPolygon: function() {
      var bounds = this.map.getBounds();
      if (!!bounds) {
        var nlat = bounds.getNorthEast().lat(),
            nlng = bounds.getNorthEast().lng(),
            slat = bounds.getSouthWest().lat(),
            slng = bounds.getSouthWest().lng();

        // Define the LngLat coordinates for the polygon.
        var boundsJson = {
          "type": "Polygon",
          "coordinates":[[
            [slng,nlat],
            [nlng,nlat],
            [nlng,slat],
            [slng,slat],
            [slng,nlat]
          ]]
        }
        return JSON.stringify(boundsJson);
      }
      return null;
    },

    _getZoomSteps: function(z) {
      var params = this._getParams();
      return z - this.options.dataMaxZoom[params['color_filter']];
    },

  });

  return UrthecastLayer;

});
<|endoftext|>


<commit_before>// Copyright 2002-2012, University of Colorado

/*
 * An HTMLImageElement that is backed by a scene. Call update() on this SceneImage to update the image from the scene.
 *
 * @author Jonathan Olson <olsonsjc@gmail.com>
 */

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

  var assert = require( 'ASSERT/assert' )( 'scenery' );

  var scenery = require( 'SCENERY/scenery' );

  // NOTE: ideally the scene shouldn't use SVG, since rendering that to a canvas takes a callback
  scenery.SceneImage = function( scene ) {
    this.scene = scene;

    // we write the scene to a canvas, get its data URL, and pass that to the image.
    this.canvas = document.createElement( 'canvas' );
    this.context = this.canvas.getContext( '2d' );

    this.img = document.createElement( 'img' );
    this.update();
  };
  var SceneImage = scenery.SceneImage;

  SceneImage.prototype = {
    constructor: SceneImage,

    // NOTE: calling this before the previous update() completes may cause the previous onComplete to not be executed
    update: function( onComplete ) {
      var self = this;

      this.scene.updateScene();

      this.canvas.width = this.scene.getSceneWidth();
      this.canvas.height = this.scene.getSceneHeight();

      this.scene.renderToCanvas( this.canvas, this.context, function() {
        var url = self.toDataURL();

        self.img.onload = function() {
          onComplete();
          delete self.img.onload;
        };
        self.img.src = url;
      } );
    }
  };

  return SceneImage;
} );
<commit_msg>Notes<commit_after>// Copyright 2002-2012, University of Colorado

/*
 * An HTMLImageElement that is backed by a scene. Call update() on this SceneImage to update the image from the scene.
 *
 * @author Jonathan Olson <olsonsjc@gmail.com>
 */

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

  var assert = require( 'ASSERT/assert' )( 'scenery' );

  var scenery = require( 'SCENERY/scenery' );

  // NOTE: ideally the scene shouldn't use SVG, since rendering that to a canvas takes a callback (and usually requires canvg)
  scenery.SceneImage = function( scene ) {
    this.scene = scene;

    // we write the scene to a canvas, get its data URL, and pass that to the image.
    this.canvas = document.createElement( 'canvas' );
    this.context = this.canvas.getContext( '2d' );

    this.img = document.createElement( 'img' );
    this.update();
  };
  var SceneImage = scenery.SceneImage;

  SceneImage.prototype = {
    constructor: SceneImage,

    // NOTE: calling this before the previous update() completes may cause the previous onComplete to not be executed
    update: function( onComplete ) {
      var self = this;

      this.scene.updateScene();

      this.canvas.width = this.scene.getSceneWidth();
      this.canvas.height = this.scene.getSceneHeight();

      this.scene.renderToCanvas( this.canvas, this.context, function() {
        var url = self.toDataURL();

        self.img.onload = function() {
          onComplete();
          delete self.img.onload;
        };
        self.img.src = url;
      } );
    }
  };

  return SceneImage;
} );
<|endoftext|>


<commit_before>'use strict';

module.exports = function(value, name) {
  return check(value, name);
};

module.exports.addAssertion = addAssertion;
module.exports.Assertion = Assertion;
module.exports.Alias = Alias;

// contains all assertion objects
var assertions = {};

// contains all check methods which call assertions
var assertProto = {};

// registers a new assertion
function addAssertion(name, assertion) {
  checkAssertionName(name);
  checkAssertion(assertion);

  if (assertion instanceof Alias) {
    addAssertion(name, getAliasedAssertion(assertion));
    return;
  }

  assertions[name] = assertion;
  assertion.name = name;

  if (assertion instanceof AssertionWithArguments) {
    Object.defineProperty(assertProto, name, {
      value: function() { return this.add(assertion, [].slice.call(arguments)); },
      enumerable: true,
    });
  } else {
    Object.defineProperty(assertProto, name, {
      get: function() { return this.add(assertion); },
      enumerable: true,
    });
  }
}

// this must work when no assertions are added yet,
// so checks are implemented in vanilla javascript
function checkAssertionName(name) {
  if (typeof name !== 'string') {
    throw new Error('name must be a string; got '+ name);
  }
  if (name in assertions) {
    throw new Error('assertion of name '+ name +' already registered');
  }
}
function checkAssertion(assertion) {
  if (!(assertion instanceof Assertion)) {
    throw new Error('assertion must be an instance of Assertion');
  }
}
function getAliasedAssertion(assertion) {
  var aliased = assertions[assertion.aliasFor];
  if (!aliased) {
    throw new Error('assertion of name '+ assertion.aliasFor +' not registered');
  }
  return aliased;
}

// built in getters
var getters = {
  value: {
    name: function(context) { return context.name; },
    value: function(context) { return context.value; },
  },
  property: function(propertyName) {
    return {
      name: function(context) { return context.name +'.'+ propertyName; },
      value: function(context) { return context.value[propertyName]; },
    };
  },
  element: function(index) {
    return {
      name: function(context) { return context.name +'['+ index +']'; },
      value: function(context) { return context.value[index]; },
    };
  },
};

module.exports.getters = getters;

var globalObject = typeof window !== 'undefined'? window: global;

// assertion classes
function Assertion(assertFunction) {
  if (this === globalObject) {
    return new Assertion(assertFunction);
  }
  this.runInContext = assertFunction;
  this.prefix = '';
}

Assertion.prototype = {
  getter: getters.value,
  message: [],
};

function AssertionWithArguments(assertFunction) {
  if (this === globalObject) {
    return new AssertionWithArguments(assertFunction);
  }
  Assertion.call(this, assertFunction);
}

AssertionWithArguments.prototype = new Assertion();

function Alias(originalName) {
  if (this === globalObject) {
    return new Alias(originalName);
  }
  this.aliasFor = originalName;
}

Alias.prototype = new Assertion();

function TypeofAssertion(requiredType) {
  if (this === globalObject) {
    return new TypeofAssertion(requiredType);
  }
  Assertion.call(this, function(context) {
    this.message = getTypePrefix(requiredType) + requiredType;
    return context.assert(function(value) { return typeof value === requiredType; });
  });
}

TypeofAssertion.prototype = new Assertion();

var builtInAssertions = {
  // does nothing
  'is': new Assertion(function(context) {
    return context;
  }),
  'be': new Alias('is'),
  'being': new Alias('is'),
  'it': new Alias('is'),
  'with': new Alias('is'),
  'which': new Alias('is'),
  'that': new Alias('is'),
  'to': new Alias('is'),
  'of': new Alias('is'),
  'from': new Alias('is'),
  'either': new Alias('is'),
  'has': new Alias('is'),
  'have': new Alias('is'),
  'defines': new Alias('is'),
  'define': new Alias('is'),
  'contains': new Alias('is'),
  'contain': new Alias('is'),
  'precondition': new Alias('is'),
  'postcondition': new Alias('is'),
  'invariant': new Alias('is'),

  // boolean operators (and is default)
  'and': new Alias('is'),
  'or': new Assertion(function(context) {
    context.strategy = orStrategy;
  }),
  'not': new Assertion(function(context) {
    var originalModifier = context.modifier;
    context.modifier = function(result) {
      this.modifier = originalModifier;
      return !this.modifier(result);
    };
    var originalStrategy = context.strategy;
    context.strategy = function(condition) {
      this.current.message = [ 'not' ].concat(ensureArray(this.current.message));
      this.strategy = originalStrategy;
      return this.strategy(condition);
    };
  }),

  // null assertions
  'Null': new Assertion(function(context) {
    this.message = 'null';
    context.assert(isNull);
  }),
  'empty': new Assertion(function(context) {
    this.message = 'empty';
    context.assert(isNullOrUndefined);
  }),

  // typeof assertions
  'aString': new TypeofAssertion('string'),
  'aNumber': new TypeofAssertion('number'),
  'aFunction': new TypeofAssertion('function'),
  'anObject': new TypeofAssertion('object'),
  'Undefined': new TypeofAssertion('undefined'),

  // duck typing assertions
  'anArray': new Assertion(function(context) {
    this.message = 'an array';
    context.assert(isArray);
  }),

  // property assertions
  'property': new AssertionWithArguments(function(context, propertyName, propertyValue) {
    check(propertyName, 'propertyName').is.aString();
    context.clone().precondition.of.being.not.empty();

    this.getter = getters.property(propertyName);
    if (typeof propertyValue !== 'undefined') {
      this.message = propertyValue;
      context.assert(function(value) { return value[propertyName] === propertyValue; });
    }
    this.message = 'not undefined';
    context.assert(function(value) { return !isUndefined(value[propertyName]); });
  }),

  // length assertions
  'length': new AssertionWithArguments(function(context, requiredLength) {
    check(requiredLength, 'requiredLength').is.aNumber();
    context.clone().precondition.of.being.anArray();

    this.message = requiredLength;
    this.getter = getters.property('length');
    context.assert(function(value) { return value.length === requiredLength; });
  }),
  'lengthGreaterThan': new AssertionWithArguments(function(context, requiredLength) {
    check(requiredLength, 'requiredLength').is.aNumber();
    context.clone().precondition.of.being.anArray();

    this.message = [ '>', requiredLength ];
    this.getter = getters.property('length');
    context.assert(function(value) { return value.length <= requiredLength; });
  }),
  'lengthLessThan': new AssertionWithArguments(function(context, requiredLength) {
    check(requiredLength, 'requiredLength').is.aNumber();
    context.clone().precondition.of.being.anArray();

    this.message = [ '<', requiredLength ];
    this.getter = getters.property('length');
    context.assert(function(value) { return value.length >= requiredLength; });
  }),
  'lengthGT': new Alias('lengthGreaterThan'),
  'lengthLT': new Alias('lengthLessThan'),

  // array element assertions
  'eachElementIs': new AssertionWithArguments(function(context, assertName, assertFunction) {
    this.message = check(assertName, 'assertName').is.aString();
    check(assertFunction, 'assertFunction').is.aFunction();
    context.clone().precondition.of.being.anArray();

    var that = this;
    context.value.forEach(function(elem, i) {
      that.getter = getters.element(i);
      context.assert(assertFunction.bind(context, elem));
    });
  }),
  'numberElements': new AssertionWithArguments(function(context) {
    context.eachElementIs('a number', isNumber);
  }),
};

Object.keys(builtInAssertions).forEach(function(name) {
  addAssertion(name, builtInAssertions[name]);
});

// exported check function
function check(value, name) {
  var context = function() {
    return context.finish();
  };

  context.value = value;
//  context.name = name;
  context.result = true;
  context.modifier = pass;
  context.strategy = andStrategy;
  context.active = [];
  context.done = [];

  Object.setPrototypeOf(context, assertProto);

  // hacks for things that magically happen in v8
  // even though prototype Function.prototype is detached
  Object.defineProperty(context, 'name', { value: name, enumberable: true });
  Object.defineProperty(context, 'length', { value: assertProto.length, enumberable: true });

  return context;
}

var checkProto = {
  // all assertion methods call this one
  // it can also be used to run assertions without adding them globally
  // `check(arg, 'arg').add(new Assertion(...))()`
  add: function(assertionProto, args) {
    var assertion = Object.create(assertionProto);
    assertion.args = args || [];
    this.active.push(assertion);
    assertion.runInContext.apply(assertion, [ this ].concat(assertion.args));
    this.active.pop();
    return this;
  },
  // `check(arg, 'arg').is.not.Empty.finish()`
  // is just a longer notation of:
  // `check(arg, 'arg').is.not.Empty()`
  finish: function() {
    if (!this.result) {
      throw new Error(buildMessage(this));
    }
    return this.value;
  },
  // to be used inside assertions
  // (I wounder if there is a way to implement this without double IoC)
  assert: function(condition) {
    return this.strategy(condition);
  },
  // creates fresh instance of context with the same name and value
  // (to be used inside assertions)
  // TODO negation operator must handle all calls on context inside a top-level assertion
  // then this function will not be necessary
  clone: function() {
    return check(this.value, this.name);
  },
};

// returns currently running assertions
Object.defineProperty(checkProto, 'current', {
  get: function() { return this.active[this.active.length - 1]; },
  enumerable: true,
});

Object.setPrototypeOf(assertProto, checkProto);

// strategies for hangling boolean operators
function andStrategy(condition) {
  this.current.result = this.modifier(condition(this.value));
  this.result = this.result && this.current.result;
  this.current.prefix = 'and ';
  this.done.push(this.current);
  return this;
}
function orStrategy(condition) {
  if (this.done.length !== 0 && this.result === true) {
    // Condition already satisfied, no need to do subsequent checks.
    this.strategy = doneStrategy;
    return this;
  }
  this.current.prefix = 'or ';
  this.result = this.current.result = this.modifier(condition(this.value));
  this.done.push(this.current);
  this.strategy = andStrategy;
  return this;
}
function doneStrategy() {
  return this;
}

// default modifier
function pass(result) {
  return result;
}

// check helpers
function isNumber(value) {
  return typeof value === 'number';
}
function isArray(value) {
  return typeof value === 'object' && !isNull(value) &&
    typeof value.splice === 'function' && typeof value.forEach === 'function';
}
function isNull(value) {
  return value === null;
}
function isUndefined(value) {
  return typeof value === 'undefined';
}
function isNullOrUndefined(value) {
  return isNull(value) || isUndefined(value);
}
function getTypePrefix(type) {
  return type === 'object'? 'an ': type === 'undefined'? '': 'a ';
}

// code that builds error message is invoked only when assertion fails
// from this moment, performace is not a concern here
function buildMessage(context) {
  var groupByName = groupByVariableName.bind(null, context);
  var toString = groupToString(context);

  var message = context.done
    .reduce(removeDuplicates, [])
//    .map(tee.bind(null, console.log))
    .reduce(groupByName, [])
    .filter(onlyWithNegativeResult)
    .reduce(groupByName, [])
    .reduce(function(builder, group) { return builder + toString(group); }, '');

  return message;
}

function removeDuplicates(result, assertion) {
  if (result.length === 0 || result[result.length - 1].message !== assertion.message) {
    result.push(assertion);
  }
  return result;
}

function groupByVariableName(context, result, assertion) {
  var name = assertion.getter.name(context);
  var current = result.length === 0? createGroup(assertion): result.pop();
  var currentName = current.getter.name(context);
  if (name !== currentName) {
    result.push(current);
    current = createGroup(assertion);
  }
  current.message.push(assertion.prefix + ensureArray(assertion.message).join(' '));
  current.result &= assertion.result;
  result.push(current);
  return result;
}

function createGroup(assertion) {
  // has the same properties as assertion
  var group = {
    prefix: assertion.prefix,
    getter: assertion.getter,
    message: [],
    result: true,
  };
  assertion.prefix = '';
  return group;
}

function groupToString(context) {
  var toString = first;

  function impl(group) {
    var name = group.getter.name(context);
    var conditions = group.message.join(' ');
    var value = group.getter.value(context);
    var retVal = group.prefix + name +' must be '+ conditions +'; got '+ value;
    return retVal;
  }
  function first(group) {
    toString = next;
    group.prefix = '';
    return impl(group);
  }
  function next(group) {
    return ' '+ impl(group);
  }

  return function(group) {
    return toString(group);
  };
}

function ensureArray(value) {
  return isArray(value)? value: [ value ];
}

function onlyWithNegativeResult(group) {
  return !group.result;
}

// debugging

/* eslint-disable no-unused-vars */

function tee(func, group) {
  func(group);
  return group;
}

/*
  eslint-env node
 */

/*
  global window
 */

<commit_msg>~ context.push and context.pop instead of context.clone<commit_after>'use strict';

module.exports = function(value, name) {
  return check(value, name);
};

module.exports.addAssertion = addAssertion;
module.exports.Assertion = Assertion;
module.exports.Alias = Alias;

// contains all assertion objects
var assertions = {};

// contains all check methods which call assertions
var assertProto = {};

// registers a new assertion
function addAssertion(name, assertion) {
  checkAssertionName(name);
  checkAssertion(assertion);

  if (assertion instanceof Alias) {
    addAssertion(name, getAliasedAssertion(assertion));
    return;
  }

  assertions[name] = assertion;
  assertion.name = name;

  if (assertion instanceof AssertionWithArguments) {
    Object.defineProperty(assertProto, name, {
      value: function() { return this.add(assertion, [].slice.call(arguments)); },
      enumerable: true,
    });
  } else {
    Object.defineProperty(assertProto, name, {
      get: function() { return this.add(assertion); },
      enumerable: true,
    });
  }
}

// this must work when no assertions are added yet,
// so checks are implemented in vanilla javascript
function checkAssertionName(name) {
  if (typeof name !== 'string') {
    throw new Error('name must be a string; got '+ name);
  }
  if (name in assertions) {
    throw new Error('assertion of name '+ name +' already registered');
  }
}
function checkAssertion(assertion) {
  if (!(assertion instanceof Assertion)) {
    throw new Error('assertion must be an instance of Assertion');
  }
}
function getAliasedAssertion(assertion) {
  var aliased = assertions[assertion.aliasFor];
  if (!aliased) {
    throw new Error('assertion of name '+ assertion.aliasFor +' not registered');
  }
  return aliased;
}

// built in getters
var getters = {
  value: {
    name: function(context) { return context.name; },
    value: function(context) { return context.value; },
  },
  property: function(propertyName) {
    return {
      name: function(context) { return context.name +'.'+ propertyName; },
      value: function(context) { return context.value[propertyName]; },
    };
  },
  element: function(index) {
    return {
      name: function(context) { return context.name +'['+ index +']'; },
      value: function(context) { return context.value[index]; },
    };
  },
};

module.exports.getters = getters;

var globalObject = typeof window !== 'undefined'? window: global;

// assertion classes
function Assertion(assertFunction) {
  if (this === globalObject) {
    return new Assertion(assertFunction);
  }
  this.runInContext = assertFunction;
  this.prefix = '';
}

Assertion.prototype = {
  getter: getters.value,
  message: [],
};

function AssertionWithArguments(assertFunction) {
  if (this === globalObject) {
    return new AssertionWithArguments(assertFunction);
  }
  Assertion.call(this, assertFunction);
}

AssertionWithArguments.prototype = new Assertion();

function Alias(originalName) {
  if (this === globalObject) {
    return new Alias(originalName);
  }
  this.aliasFor = originalName;
}

Alias.prototype = new Assertion();

function TypeofAssertion(requiredType) {
  if (this === globalObject) {
    return new TypeofAssertion(requiredType);
  }
  Assertion.call(this, function(context) {
    this.message = getTypePrefix(requiredType) + requiredType;
    context.assert(function(value) {
      return typeof value === requiredType;
    });
  });
}

TypeofAssertion.prototype = new Assertion();

var builtInAssertions = {
  // does nothing
  'is': new Assertion(function(context) {
    return context;
  }),
  'be': new Alias('is'),
  'being': new Alias('is'),
  'it': new Alias('is'),
  'with': new Alias('is'),
  'which': new Alias('is'),
  'that': new Alias('is'),
  'to': new Alias('is'),
  'of': new Alias('is'),
  'from': new Alias('is'),
  'either': new Alias('is'),
  'has': new Alias('is'),
  'have': new Alias('is'),
  'defines': new Alias('is'),
  'define': new Alias('is'),
  'contains': new Alias('is'),
  'contain': new Alias('is'),
  'precondition': new Alias('is'),
  'postcondition': new Alias('is'),
  'invariant': new Alias('is'),

  // boolean operators (and is default)
  'and': new Alias('is'),
  'or': new Assertion(function(context) {
    context.state.strategy = orStrategy;
  }),
  'not': new Assertion(function(context) {
    var originalModifier = context.state.modifier;
    context.state.modifier = function(result) {
      this.modifier = originalModifier;
      return !this.modifier(result);
    };
    var originalStrategy = context.state.strategy;
    context.state.strategy = function(condition, value) {
      this.current.message = [ 'not' ].concat(ensureArray(this.current.message));
      this.strategy = originalStrategy;
      return this.strategy(condition, value);
    };
  }),

  // null assertions
  'Null': new Assertion(function(context) {
    this.message = 'null';
    context.assert(isNull);
  }),
  'empty': new Assertion(function(context) {
    this.message = 'empty';
    context.assert(isNullOrUndefined);
  }),

  // typeof assertions
  'aString': new TypeofAssertion('string'),
  'aNumber': new TypeofAssertion('number'),
  'aFunction': new TypeofAssertion('function'),
  'anObject': new TypeofAssertion('object'),
  'Undefined': new TypeofAssertion('undefined'),

  // duck typing assertions
  'anArray': new Assertion(function(context) {
    this.message = 'an array';
    context.assert(isArray);
  }),

  // property assertions
  'property': new AssertionWithArguments(function(context, propertyName, propertyValue) {
    check(propertyName, 'propertyName').is.aString();

    context.push();
    if (!context.is.not.empty.result) {
      context.pop();
      return;
    }

    this.getter = getters.property(propertyName);
    if (typeof propertyValue !== 'undefined') {
      this.message = propertyValue;
      context.assert(function(value) { return value[propertyName] === propertyValue; });
    } else {
      this.message = 'not undefined';
      context.assert(function(value) { return !isUndefined(value[propertyName]); });
    }
    context.pop();
  }),

  // length assertions
  'length': new AssertionWithArguments(function(context, requiredLength) {
    check(requiredLength, 'requiredLength').is.aNumber();
    context.has.property('length', requiredLength);
  }),
  // TODO 'lengthGT': new Alias('lengthGreaterThan'),
  // TODO 'lengthLT': new Alias('lengthLessThan'),

  // array element assertions
  'eachElementIs': new AssertionWithArguments(function(context, assertName, assertFunction) {
    check(assertName, 'assertName').is.aString();
    check(assertFunction, 'assertFunction').is.aFunction();

    context.push();
    context.is.anArray();

    context.value.forEach(function(elem, i) {
      context.add(new Assertion(function(innerContext) {
        this.getter = getters.element(i);
        this.message = assertName;
        innerContext.assert(assertFunction.bind(null, elem));
      }));
    });
    context.pop();
  }),
  'numberElements': new AssertionWithArguments(function(context) {
    context.eachElementIs('a number', isNumber);
  }),
};

Object.keys(builtInAssertions).forEach(function(name) {
  addAssertion(name, builtInAssertions[name]);
});

// exported check function
function check(value, name) {
  var context = function() {
    return context.finish();
  };

  context.value = value;
//  context.name = name;
  context.state = new State();
  context.stack = [];

  Object.setPrototypeOf(context, assertProto);

  // hacks for things that magically happen in v8
  // even though prototype Function.prototype is detached
  Object.defineProperty(context, 'name', { value: name, enumberable: true });
  Object.defineProperty(context, 'length', { value: assertProto.length, enumberable: true });

  return context;
}

var checkProto = {
  // all assertion methods call this one
  // it can also be used to run assertions without adding them globally
  // `check(arg, 'arg').add(new Assertion(...))()`
  add: function(assertionProto, args) {
    var assertion = Object.create(assertionProto);
    assertion.args = args || [];
    this.state.active.push(assertion);
    assertion.runInContext.apply(assertion, [ this ].concat(assertion.args));
    this.state.active.pop();
    this.state.done.push(assertion);
    return this;
  },
  // `check(arg, 'arg').is.not.Empty.finish()`
  // is just a longer notation of:
  // `check(arg, 'arg').is.not.Empty()`
  finish: throwOrReturnValue,
  // to be used inside assertions
  // (I wounder if there is a way to implement this without double IoC)
  assert: function(condition) {
    return this.state.strategy(condition, this.value);
  },
  push: function() {
    var current = this.state.current;
    this.stack.push(this.state);
    this.state = new State();
    this.state.active.push(current);
    this.finish = justReturnValue;
    return this;
  },
  pop: function() {
    var deletedState = this.state;
    this.state = this.stack.pop();
    this.state.current.done = deletedState.done;
    this.state.strategy(function() {
      return deletedState.result;
    });
    if (this.stack.length === 0) {
      this.finish = throwOrReturnValue;
    }
    return this;
  },
};

Object.defineProperty(checkProto, 'result', {
  get: function() { return this.state.result; },
  enumerable: true,
});

Object.setPrototypeOf(assertProto, checkProto);

// this gets pushed around alot
function State() {
  this.result = true;
  this.active = [];
  this.done = [];
}

State.prototype = {
  modifier: pass,
  strategy: andStrategy,
};

Object.defineProperty(State.prototype, 'current', {
  get: function() { return this.active[this.active.length - 1]; },
});

// strategies for hangling boolean operators
function andStrategy(condition, value) {
  this.current.result = this.modifier(condition(value));
  this.result = this.result && this.current.result;
  this.current.prefix = 'and ';
  return this;
}
function orStrategy(condition, value) {
  if (this.done.length !== 0 && this.result === true) {
    // Condition already satisfied, no need to do subsequent checks.
    this.strategy = doneStrategy;
    return this;
  }
  this.current.prefix = 'or ';
  this.result = this.current.result = this.modifier(condition(value));
  this.strategy = andStrategy;
  return this;
}
function doneStrategy() {
  return this;
}

// finish functions
function throwOrReturnValue() {
  if (!this.state.result) {
    throw new Error(buildMessage(this));
  }
  return this.value;
}
function justReturnValue() {
  return this.value;
}

// default modifier
function pass(result) {
  return result;
}

// check helpers
function isNumber(value) {
  return typeof value === 'number';
}
function isArray(value) {
  return typeof value === 'object' && !isNull(value) &&
    typeof value.splice === 'function' && typeof value.forEach === 'function';
}
function isNull(value) {
  return value === null;
}
function isUndefined(value) {
  return typeof value === 'undefined';
}
function isNullOrUndefined(value) {
  return isNull(value) || isUndefined(value);
}
function getTypePrefix(type) {
  return type === 'object'? 'an ': type === 'undefined'? '': 'a ';
}

// code that builds error message is invoked only when assertion fails
// from this moment, performace is not a concern here
function buildMessage(context) {
  var groupByName = groupByVariableName.bind(null, context);
  var toString = groupToString(context);

  var message = context.state.done
    .filter(onlyNotEmpty)
//    .map(tee.bind(null, console.log))
    .reduce(removeDuplicates, [])
    .reduce(groupByName, [])
    .filter(onlyWithNegativeResult)
    .reduce(groupByName, [])
    .reduce(function(builder, group) { return builder + toString(group); }, '');

  return message;
}

function onlyNotEmpty(assertion) {
  return typeof assertion.result !== 'undefined';
}

function removeDuplicates(result, assertion) {
  if (result.length === 0 || result[result.length - 1].message !== assertion.message) {
    result.push(assertion);
  }
  return result;
}

function groupByVariableName(context, result, assertion) {
  var name = assertion.getter.name(context);
  var current = result.length === 0? createGroup(assertion): result.pop();
  var currentName = current.getter.name(context);
  if (name !== currentName) {
    result.push(current);
    current = createGroup(assertion);
  }
  current.message.push(assertion.prefix + ensureArray(assertion.message).join(' '));
  current.result &= assertion.result;
  result.push(current);
  return result;
}

function createGroup(assertion) {
  // has the same properties as assertion
  var group = {
    prefix: assertion.prefix,
    getter: assertion.getter,
    message: [],
    result: true,
  };
  assertion.prefix = '';
  return group;
}

function groupToString(context) {
  var toString = first;

  function impl(group) {
    var name = group.getter.name(context);
    var conditions = group.message.join(' ');
    var value = group.getter.value(context);
    var retVal = group.prefix + name +' must be '+ conditions +'; got '+ value;
    return retVal;
  }
  function first(group) {
    toString = next;
    group.prefix = '';
    return impl(group);
  }
  function next(group) {
    return ' '+ impl(group);
  }

  return function(group) {
    return toString(group);
  };
}

function ensureArray(value) {
  return isArray(value)? value: [ value ];
}

function onlyWithNegativeResult(group) {
  return !group.result;
}

// debugging

/* eslint-disable no-unused-vars */

function tee(func, group) {
  func(group);
  return group;
}

/*
  eslint-env node
 */

/*
  global window
 */

<|endoftext|>