<commit_before>

var renderElements = {};
var renderidOffset = 0;
var addRenderElement = function () {
    var element = this;
    if (!isNode(element)) return;
    if (!element.renderid) {
        element.renderid = ++renderidOffset;
    }
    renderElements[element.renderid] = element;
};
var removeRenderElement = function () {
    var element = this;
    delete renderElements[element.renderid];
};
function refresh() {
    for (var k in renderElements) {
        var element = renderElements[k];
        rebuild(element);
    }
}
function rebuild(element) {
    element.renders.map(a => a.call(element));
}
var createGetter = function (search) {
    return new Function("scope", `try{with(scope)return ${search}}catch(e){/*console.warn(String(e))*/}`);
};
var directives = {
    click(scope, search) {
        var getter = createGetter(search);
        onclick(this, getter.bind(this, scope));
    },
    src(scope, search) {
        var getter = createGetter(search);
        this.renders.push(function () {
            var value = getter(scope) || "";
            if (this.src !== value) this.src = value;
        });
    },
    bind(scope, search) {
        var getter = createGetter(search);
        this.renders.push(function () {
            var value = getter(scope);
            if (text(this) !== value) text(this, value);
        });
    },
    model(scope, search) {
        var getter = createGetter(search);
        if (/select|input|textarea/i.test(this.tagName)) {
            this.renders.push(function () {
                var value = getter(scope);
                if (value === undefined) value = "";
                if (this.value != value) this.value = value;

            });
            var change = new Function("scope", `with(scope)${search}=this.value`).bind(this, scope);
        } else {
            this.renders.push(function () {
                var value = getter(scope);
                if (value === undefined) value = "";
                if (html(this) != value) html(this, value);
            });
            var change = new Function("scope", "html", `with(scope)${search}=html(this)`).bind(this, scope, html);
        }
        var onchange = lazy(change);
        eventsHandlers.map(on => on(this, onchange));
    },
    hide(scope, search) {
        var getter = createGetter(search);
        this.renders.push(function () {
            if (getter(scope)) {
                this.style.display = "none";
            } else {
                this.style.display = "";
            }
        });
    },
    show(scope, search) {
        var getter = createGetter(search);
        this.renders.push(function () {
            if (getter(scope)) {
                this.style.display = "";
            } else {
                this.style.display = "none";
            }
        });
    },
    "if"(scope, search) {
        // 懒渲染
        var getter = createGetter(search);
        var cancelonappend = onappend(this, function () {
            cancelonappend();
            var comment = document.createComment("-if:" + search);
            comment.renders = [function () {
                if (getter(scope)) {
                    if (!this.parentNode) appendChild.before(comment, this);
                } else {
                    remove(this);
                }
            }.bind(this)];
            onappend(comment, addRenderElement);
            onremove(comment, removeRenderElement);
            appendChild.after(this, comment);
            rebuild(comment);
        });
    },
    repeat(scope, search) {
        // 懒渲染
        throw new Error("repeat is not supported! use list component instead");
    },
    "class"(scope, search) {
        var getter = createGetter(search);
        var originClassName = [];
        this.className.split(/\s+/).map(function (k) {
            if (k && !hasOwnProperty.call(originClassName, k)) {
                originClassName.push(originClassName[k] = k);
            }
        });
        this.renders.push(function () {
            var className = getter(scope);
            var deltaClassNames = [];
            if (isString(className)) {
                className.split(/\s+/).map(function (k) {
                    if (!hasOwnProperty.call(originClassName, k)) {
                        deltaClassNames.push(deltaClassNames[k] = k);
                    }
                });
            } else if (className instanceof Object) {
                for (var k in className) {
                    if (!hasOwnProperty.call(originClassName, k) && className[k]) {
                        deltaClassNames.push(deltaClassNames[k] = k);
                    }
                }
            }
            var destClassName = originClassName.concat(deltaClassNames).join(" ");
            if (this.className !== destClassName) {
                this.className = destClassName;
            }
        });
    },
    style(scope, search) {
        var getter = createGetter(search);
        this.renders.push(function () {
            var stylesheet = getter(scope);
            if (isString(stylesheet)) {
                stylesheet.replace(/[\s\u00a0]+/g, "").split(/;/).map(function (kv) {
                    var [k, v] = kv.split(":");
                    if (this.style[k] !== v) {
                        this.style[k] = v;
                    }
                }, this);
            } else if (stylesheet instanceof Object) {
                for (var k in stylesheet) {
                    if (this.style[k] !== stylesheet[k]) {
                        this.style[k] = stylesheet[k];
                    }
                }
            }
        });
    }
};
function renderElement(element, scope) {
    if (element.renderid) return element;
    var children = element.children;
    if (!children) {
        return [].slice.call(element, 0).map(function (element) {
            return renderElement(element, scope);
        });
    }
    var attrs = element.attributes;
    element.renders = [];
    [].slice.call(attrs, 0).map(function (attr) {
        var { name, value } = attr;
        if (/^(?:class|style|src)$/i.test(name)) return;
        name = name.replace(/^(ng|v|.*?)\-/i, "").toLowerCase();
        if (directives.hasOwnProperty(name) && isFunction(directives[name])) {
            directives[name].call(element, scope, value);
        }
    });
    if (children.length) renderElement(children, scope);
    if (element.renders.length) {
        onappend(element, addRenderElement);
        onremove(element, removeRenderElement);
        rebuild(element);
    }
}
function render(element, scope) {
    return renderElement(element, scope);
}

var digest = lazy(refresh);
render.digest = digest;

var eventsHandlers = "change,paste,resize,keydown,keypress,keyup,mousedown,mouseup,dragend,drop".split(",").map(k => on(k));
eventsHandlers.map(on => on(window, digest));<commit_msg>支持二次渲染<commit_after>

var renderElements = {};
var renderidOffset = 0;
var addRenderElement = function () {
    var element = this;
    if (!isNode(element)) return;
    if (!element.renderid) {
        element.renderid = ++renderidOffset;
    }
    renderElements[element.renderid] = element;
};
var removeRenderElement = function () {
    var element = this;
    delete renderElements[element.renderid];
};
function refresh() {
    for (var k in renderElements) {
        var element = renderElements[k];
        rebuild(element);
    }
}
function rebuild(element) {
    element.renders.map(a => a.call(element));
}
var createGetter = function (search) {
    return new Function(`try{with(this.$scope)return ${search}}catch(e){/*console.warn(String(e))*/}`);
};
var directives = {
    click(search) {
        var getter = createGetter(search);
        onclick(this, getter);
    },
    src(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            var value = getter() || "";
            if (this.src !== value) this.src = value;
        });
    },
    bind(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            var value = getter();
            if (text(this) !== value) text(this, value);
        });
    },
    model(search) {
        var getter = createGetter(search).bind(this);
        if (/select|input|textarea/i.test(this.tagName)) {
            this.renders.push(function () {
                var value = getter();
                if (value === undefined) value = "";
                if (this.value != value) this.value = value;

            });
            var change = new Function(`with(this.$scope)${search}=this.value`);
        } else {
            this.renders.push(function () {
                var value = getter();
                if (value === undefined) value = "";
                if (html(this) != value) html(this, value);
            });
            var change = new Function("html", `with(this.$scope)${search}=html(this)`).bind(this, html);
        }
        var onchange = lazy(change);
        eventsHandlers.map(on => on(this, onchange));
    },
    hide(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            if (getter()) {
                this.style.display = "none";
            } else {
                this.style.display = "";
            }
        });
    },
    show(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            if (getter()) {
                this.style.display = "";
            } else {
                this.style.display = "none";
            }
        });
    },
    "if"(search) {
        // 懒渲染
        var getter = createGetter(search).bind(this);
        var cancelonappend = onappend(this, function () {
            cancelonappend();
            var comment = document.createComment("-if:" + search);
            comment.renders = [function () {
                if (getter()) {
                    if (!this.parentNode) appendChild.before(comment, this);
                } else {
                    remove(this);
                }
            }.bind(this)];
            onappend(comment, addRenderElement);
            onremove(comment, removeRenderElement);
            appendChild.after(this, comment);
            rebuild(comment);
        });
    },
    repeat(search) {
        // 懒渲染
        throw new Error("repeat is not supported! use list component instead");
    },
    "class"(search) {
        var getter = createGetter(search).bind(this);
        var originClassName = [];
        this.className.split(/\s+/).map(function (k) {
            if (k && !hasOwnProperty.call(originClassName, k)) {
                originClassName.push(originClassName[k] = k);
            }
        });
        this.renders.push(function () {
            var className = getter();
            var deltaClassNames = [];
            if (isString(className)) {
                className.split(/\s+/).map(function (k) {
                    if (!hasOwnProperty.call(originClassName, k)) {
                        deltaClassNames.push(deltaClassNames[k] = k);
                    }
                });
            } else if (className instanceof Object) {
                for (var k in className) {
                    if (!hasOwnProperty.call(originClassName, k) && className[k]) {
                        deltaClassNames.push(deltaClassNames[k] = k);
                    }
                }
            }
            var destClassName = originClassName.concat(deltaClassNames).join(" ");
            if (this.className !== destClassName) {
                this.className = destClassName;
            }
        });
    },
    style(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            var stylesheet = getter();
            if (isString(stylesheet)) {
                stylesheet.replace(/[\s\u00a0]+/g, "").split(/;/).map(function (kv) {
                    var [k, v] = kv.split(":");
                    if (this.style[k] !== v) {
                        this.style[k] = v;
                    }
                }, this);
            } else if (stylesheet instanceof Object) {
                for (var k in stylesheet) {
                    if (this.style[k] !== stylesheet[k]) {
                        this.style[k] = stylesheet[k];
                    }
                }
            }
        });
    }
};
function renderElement(element, scope) {
    var children = element.children;
    if (!children) {
        return [].slice.call(element, 0).map(function (element) {
            return renderElement(element, scope);
        });
    }
    element.$scope = scope;
    if (element.renderid) return;
    var attrs = element.attributes;
    element.renders = [];
    [].slice.call(attrs, 0).map(function (attr) {
        var { name, value } = attr;
        if (/^(?:class|style|src)$/i.test(name)) return;
        name = name.replace(/^(ng|v|.*?)\-/i, "").toLowerCase();
        if (directives.hasOwnProperty(name) && isFunction(directives[name])) {
            directives[name].call(element, value);
        }
    });
    if (children.length) renderElement(children, scope);
    if (element.renders.length) {
        onappend(element, addRenderElement);
        onremove(element, removeRenderElement);
        rebuild(element);
    }
}
function render(element, scope) {
    return renderElement(element, scope);
}

var digest = lazy(refresh);
render.digest = digest;

var eventsHandlers = "change,paste,resize,keydown,keypress,keyup,mousedown,mouseup,dragend,drop".split(",").map(k => on(k));
eventsHandlers.map(on => on(window, digest));<|endoftext|>


<commit_before>(function(W, U) {
	'use strict';

	W.fn.make('view', {
		// Render specified data into specified template string
		// Return string
		render: function(temp, data, opt) {
			return this.$private('process', temp, data, {}, W.$extend({
				data: data,
				escape: true
			}, opt), 0);
		}
	}, {
		pair: /{{#(.+?)}}([\s\S]+?){{\/\1}}(?!.*{{\/\1}})/g,
		single: /{{(.+?)}}/g,
		process: function(temp, data, prev, conf, index) {
			var scope = this;

			return temp.replace(this.pair, function(m, tag, inner) {
				var segs = tag.match(/(?:[^|]|\|\|)+/g),
					len = segs.length,
					filter = len > 1 ? segs[len - 1] : '';
				tag = segs[0];

				var val = scope.getValue(data, prev, tag, conf, index),
					empty = val === false || val === U || val.length === 0,
					resp = '';

				if (filter === '' && ! empty && typeof val == 'object') {
					// Loop through objects and arrays
					var isObj = W.$isObject(val),
						i = 0;

					for (var key in val) {
						if (val.hasOwnProperty(key)) {
							var el = val[key],
								item = W.$extend({
									$key: key,
									$val: el,
									$i: i
								}, isObj ? val : W.$isObject(el) ? el : {});

							resp += scope.process(inner, item, data, conf, i);

							i++;
						}
					}
				} else if ((filter == 'notEmpty' && ! empty) || (filter == 'empty' && empty)) {
					return scope.process(inner, data, {}, conf);
				}

				return resp;
			}).replace(this.single, function(m, tag) {
				var opt = conf,
					segs = tag.match(/(?:[^|]|\|\|)+/g),
					len = segs.length,
					filter = len > 1 ? segs[len - 1] : '';
				tag = segs[0];

				if (filter == 'raw') {
					opt = W.$extend({
						escape: false
					});
				}

				var resp = scope.getValue(data, prev, tag, opt, index);

				return resp === U || typeof resp == 'object' ? '' : resp;
			});
		},
		getValue: function(data, prev, key, conf, x) {
			var segs = key.split('||'),
				trim = segs[0].trim(),
				resp = trim.split('.'),
				orig = data,
				i = 0;

			// Alter context
			if (resp[0] == '$root') {
				data = conf.data;
				resp.shift();
			} else if (trim.substring(0, 3) == '../') {
				data = prev;
				resp.splice(0, 3, trim.substring(3));
			}

			var len = resp.length - 1;

			// Loop through object segments
			for (; i <= len; i++) {
				key = resp[i];

				if (data.hasOwnProperty(key)) {
					data = data[key];

					// Return value on last segment
					if (i === len) {
						if (typeof data == 'function') {
							data = data(orig, conf.data, x);
						}

						if (typeof data == 'string') {
							// Encode tags by default
							return conf.escape ?
								data.replace(/&/g, '&')
									.replace(/&/g, '&')
									.replace(/</g, '<')
									.replace(/>/g, '>')
									.replace(/"/g, '"') :
								data;
						} else if (data !== U) {
							return data;
						}
					}
				}
			}

			// Return fallback or empty string
			return segs.length > 1 ? segs[1].trim() : '';
		}
	});
})(Wee, undefined);<commit_msg>Update escape rendering parameter to raw If the key value is an object in a template loop expose direct access to the object properties as child properties<commit_after>(function(W, U) {
	'use strict';

	W.fn.make('view', {
		// Render specified data into specified template string
		// Return string
		render: function(temp, data, opt) {
			return this.$private('process', temp, data, {}, W.$extend({
				data: data,
				raw: false
			}, opt), 0);
		}
	}, {
		pair: /{{#(.+?)}}([\s\S]+?){{\/\1}}(?!.*{{\/\1}})/g,
		single: /{{(.+?)}}/g,
		process: function(temp, data, prev, conf, index) {
			var scope = this;

			return temp.replace(this.pair, function(m, tag, inner) {
				var segs = tag.match(/(?:[^|]|\|\|)+/g),
					len = segs.length,
					filter = len > 1 ? segs[len - 1] : '';
				tag = segs[0];

				var val = scope.getValue(data, prev, tag, conf, index),
					empty = val === false || val === U || val.length === 0,
					resp = '';

				if (filter === '' && ! empty && typeof val == 'object') {
					// Loop through objects and arrays
					var isObj = W.$isObject(val),
						i = 0;

					for (var key in val) {
						if (val.hasOwnProperty(key)) {
							var el = val[key],
								item = W.$extend({
									$key: key,
									$val: el,
									$i: i
								}, W.$isObject(el) ? el : (isObj ? val : {}));

							resp += scope.process(inner, item, data, conf, i);

							i++;
						}
					}
				} else if ((filter == 'notEmpty' && ! empty) || (filter == 'empty' && empty)) {
					return scope.process(inner, data, {}, conf);
				}

				return resp;
			}).replace(this.single, function(m, tag) {
				var opt = conf,
					segs = tag.match(/(?:[^|]|\|\|)+/g),
					len = segs.length,
					filter = len > 1 ? segs[len - 1] : '';
				tag = segs[0];

				if (filter == 'raw') {
					opt = W.$extend({
						raw: true
					});
				}

				var resp = scope.getValue(data, prev, tag, opt, index);

				return resp === U || typeof resp == 'object' ? '' : resp;
			});
		},
		getValue: function(data, prev, key, conf, x) {
			var segs = key.split('||'),
				trim = segs[0].trim(),
				resp = trim.split('.'),
				orig = data,
				i = 0;

			// Alter context
			if (resp[0] == '$root') {
				data = conf.data;
				resp.shift();
			} else if (trim.substring(0, 3) == '../') {
				data = prev;
				resp.splice(0, 3, trim.substring(3));
			}

			var len = resp.length - 1;

			// Loop through object segments
			for (; i <= len; i++) {
				key = resp[i];

				if (data.hasOwnProperty(key)) {
					data = data[key];

					// Return value on last segment
					if (i === len) {
						if (typeof data == 'function') {
							data = data(orig, conf.data, x);
						}

						if (typeof data == 'string') {
							// Encode tags by default
							return conf.raw ?
								data :
								data.replace(/&/g, '&')
									.replace(/&/g, '&')
									.replace(/</g, '<')
									.replace(/>/g, '>')
									.replace(/"/g, '"');
						} else if (data !== U) {
							return data;
						}
					}
				}
			}

			// Return fallback or empty string
			return segs.length > 1 ? segs[1].trim() : '';
		}
	});
})(Wee, undefined);<|endoftext|>


<commit_before>namespace('Sy.Form');

/**
 * Default implementation of `FormInterface`
 *
 * @package Sy
 * @subpackage Form
 * @class
 * @implements {Sy.Form.FormInterface}
 */

Sy.Form.Form = function () {
    this.elements = [];
    this.name = null;
    this.config = null;
    this.validator = null;
    this.form = null;
    this.object = null;
};
Sy.Form.Form.prototype = Object.create(Sy.Form.FormInterface.prototype, {

    /**
     * @inheritDoc
     */

    add: {
        value: function (name) {
            this.elements.push(name);

            return this;
        }
    },

    /**
     * @inheritDoc
     */

    setName: {
        value: function (name) {
            this.name = name;

            return this;
        }
    },

    /**
     * @inheritDoc
     */

    getName: {
        value: function () {
            return this.name;
        }
    },

    /**
     * @inheritDoc
     */

    setOptions: {
        value: function (config) {
            if (!(config instanceof Sy.ConfiguratorInterface)) {
                throw new TypeError('Invalid configurator');
            }

            this.config = config;

            return this;
        }
    },

    /**
     * @inheritDoc
     */

    isValid: {
        value: function () {

            if (this.validator) {
                return !!this.validator.validate(
                    this.object,
                    this.config.get('validationGroups')
                );
            } else if (this.form) {
                return this.form.checkValidity();
            }

            return true;

        }
    },

    /**
     * @inheritDoc
     */

    setObject: {
        value: function (object) {
            this.object = object;

            return this;
        }
    },

    /**
     * @inheritDoc
     */

    getObject: {
        value: function () {
            return this.object;
        }
    },

    /**
     * @inheritDoc
     */

    handle: {
        value: function (form) {
            if (form) {
                this.form = form;
            } else if (!this.form && this.getName()) {
                this.form = document.querySelector('form[id=' + this.getName() + ']');
            }

            if (!this.form) {
                throw new ReferenceError('Form element not found');
            }

            var refl = new ReflectionObject(this.object),
                els = this.elements,
                setter,
                value;

            for (var i = 0, l = els.length; i < l; i++) {
                if (this.form.elements[els[i]] !== undefined) {
                    setter = 'set' + els[i].charAt(0).toUpperCase() + els[i].substr(1);
                    value = this.form.elements[els[i]].value;

                    if (refl.hasMethod(setter)) {
                        refl.getMethod(setter).call(value);
                    } else if (refl.hasMethod('set')) {
                        refl.getMethod('set').call(els[i], value);
                    } else {
                        this.object[els[i]] = value;
                    }
                }
            }

            return this;
        }
    },

    /**
     * @inheritDoc
     */

    setValidator: {
        value: function (validator) {
            if (!(validator instanceof Sy.Validator.Core)) {
                throw new TypeError('Invalid validator');
            }

            this.validator = validator;

            return this;
        }
    }

});
<commit_msg>do not try to inject data if there's no object<commit_after>namespace('Sy.Form');

/**
 * Default implementation of `FormInterface`
 *
 * @package Sy
 * @subpackage Form
 * @class
 * @implements {Sy.Form.FormInterface}
 */

Sy.Form.Form = function () {
    this.elements = [];
    this.name = null;
    this.config = null;
    this.validator = null;
    this.form = null;
    this.object = null;
};
Sy.Form.Form.prototype = Object.create(Sy.Form.FormInterface.prototype, {

    /**
     * @inheritDoc
     */

    add: {
        value: function (name) {
            this.elements.push(name);

            return this;
        }
    },

    /**
     * @inheritDoc
     */

    setName: {
        value: function (name) {
            this.name = name;

            return this;
        }
    },

    /**
     * @inheritDoc
     */

    getName: {
        value: function () {
            return this.name;
        }
    },

    /**
     * @inheritDoc
     */

    setOptions: {
        value: function (config) {
            if (!(config instanceof Sy.ConfiguratorInterface)) {
                throw new TypeError('Invalid configurator');
            }

            this.config = config;

            return this;
        }
    },

    /**
     * @inheritDoc
     */

    isValid: {
        value: function () {

            if (this.validator) {
                return !!this.validator.validate(
                    this.object,
                    this.config.get('validationGroups')
                );
            } else if (this.form) {
                return this.form.checkValidity();
            }

            return true;

        }
    },

    /**
     * @inheritDoc
     */

    setObject: {
        value: function (object) {
            this.object = object;

            return this;
        }
    },

    /**
     * @inheritDoc
     */

    getObject: {
        value: function () {
            return this.object;
        }
    },

    /**
     * @inheritDoc
     */

    handle: {
        value: function (form) {
            if (form) {
                this.form = form;
            } else if (!this.form && this.getName()) {
                this.form = document.querySelector('form[id=' + this.getName() + ']');
            }

            if (!this.form) {
                throw new ReferenceError('Form element not found');
            }

            if (!this.object) {
                return;
            }

            var refl = new ReflectionObject(this.object),
                els = this.elements,
                setter,
                value;

            for (var i = 0, l = els.length; i < l; i++) {
                if (this.form.elements[els[i]] !== undefined) {
                    setter = 'set' + els[i].charAt(0).toUpperCase() + els[i].substr(1);
                    value = this.form.elements[els[i]].value;

                    if (refl.hasMethod(setter)) {
                        refl.getMethod(setter).call(value);
                    } else if (refl.hasMethod('set')) {
                        refl.getMethod('set').call(els[i], value);
                    } else {
                        this.object[els[i]] = value;
                    }
                }
            }

            return this;
        }
    },

    /**
     * @inheritDoc
     */

    setValidator: {
        value: function (validator) {
            if (!(validator instanceof Sy.Validator.Core)) {
                throw new TypeError('Invalid validator');
            }

            this.validator = validator;

            return this;
        }
    }

});
<|endoftext|>


<commit_before>import { utcToLocal } from '../util.js';
import {displayedColorbar} from './Controller.js';

export class TimeSeriesChart extends HTMLElement {
    constructor() {
        super();
        this.innerHTML = `
            <link rel="stylesheet" href="css/timeSeriesChart.css"/>

                x
                <canvas id="timeSeriesChart" width="400px" height="400px"></canvas>

                    y-axis threshold:
                    <input id="threshold-setter"></input>
                    threshold label:
                    <input id="threshold-label"></input>

        `;
        this.ctx = null;
        this.chart = null;
        this.data = null;
    }

    connectedCallback() {
        const timeSeriesChart = this.querySelector('#timeSeriesChartContainer');
        L.DomEvent.disableScrollPropagation(timeSeriesChart);
        L.DomEvent.disableClickPropagation(timeSeriesChart);
        this.ctx = this.querySelector('#timeSeriesChart').getContext('2d');
        const thresholdSetter = this.querySelector('#threshold-setter');
        thresholdSetter.oninput = () => this.setThreshold(thresholdSetter.value);
        const labelSetter = this.querySelector('#threshold-label');
        labelSetter.oninput = () => this.setLabel(labelSetter.value);
        this.querySelector('#closeTimeSeriesChart').onclick = () => {
            thresholdSetter.value = "";
            labelSetter.value = "";
            timeSeriesChart.style.display = 'none';
        }

    }

    setLabel(label) {
        // var display = label != "";
        // this.chart.options.plugins.annotation.annotations[0].label.enabled = display;
        // this.chart.options.plugins.annotation.annotations[0].label.content = label;
        // console.log(this.chart.options.plugins.annotation.annotations[0].display);
        // console.log(this.chart.options.plugins.annotation.annotations[0].value);
        // this.chart.update();
        this.populateChart(this.data, this.val, label);
    }

    setThreshold(threshold) {
        this.populateChart(this.data, threshold, this.label);
    }

    populateChart(data, val=0, label="") {
        if (data.length == 0) return;
        this.data = data;
        this.val = val;
        this.label = label;
        var labels = Object.keys(data[0].dataset).map(timeStamp => utcToLocal(timeStamp));
        if (this.chart) this.chart.destroy();
        const roundLatLon = (num) => Math.round(num*100) / 100;
        var dataset = [];
        for (var timeSeriesDataset of data) {
            var rgb = timeSeriesDataset.rgb;
            var color = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
            var timeSeriesData = {
                    label: timeSeriesDataset.label + " values at lat: " + roundLatLon(timeSeriesDataset.latLon.lat) + " lon: " + roundLatLon(timeSeriesDataset.latLon.lng),
                    fill: false,
                    data: Object.entries(timeSeriesDataset.dataset).map(entry => entry[1]),
                    borderColor: color,
                    backgroundColor: color,
                    lineTension: 0,
                    borderWidth: 1
            }
            dataset.push(timeSeriesData);
        }
        this.chart = new Chart(this.ctx, {
            type: 'line',
            data: {
                labels: labels,
                datasets: dataset
            },
            options: {
                animation: {
                    duration: 0
                },
                scales: {
                    yAxes: {
                        title: {
                            display: true,
                            text: displayedColorbar.getValue()
                        }
                    },
                    xAxes: {
                        title: {
                            display: true,
                            text: "Timestamp"
                        }
                    }
                },
                plugins: {
                    annotation: {
                        annotations: [{
                            display: val != "" && !isNaN(val),
                            type: 'line',
                            mode: 'horizontal',
                            scaleID: 'yAxes',
                            value: val,
                            borderColor: 'rgb(255, 99, 132)',
                            borderWidth: 2,
                            label: {
                                enabled: label != "",
                                content: label,
                                xAdjust: 220 - 2*label.length
                            }
                        }]
                      }
                },
            }
        });
        this.querySelector('#timeSeriesChartContainer').style.display = 'block';
    }
}

window.customElements.define('timeseries-chart', TimeSeriesChart);<commit_msg>Changes colors of points depending on whether they are above or below a threshold<commit_after>import { utcToLocal } from '../util.js';
import {displayedColorbar} from './Controller.js';

export class TimeSeriesChart extends HTMLElement {
    constructor() {
        super();
        this.innerHTML = `
            <link rel="stylesheet" href="css/timeSeriesChart.css"/>

                x
                <canvas id="timeSeriesChart" width="400px" height="400px"></canvas>

                    y-axis threshold:
                    <input id="threshold-setter"></input>
                    threshold label:
                    <input id="threshold-label"></input>

        `;
        this.ctx = null;
        this.chart = null;
        this.data = null;
    }

    connectedCallback() {
        const timeSeriesChart = this.querySelector('#timeSeriesChartContainer');
        L.DomEvent.disableScrollPropagation(timeSeriesChart);
        L.DomEvent.disableClickPropagation(timeSeriesChart);
        this.ctx = this.querySelector('#timeSeriesChart').getContext('2d');
        const thresholdSetter = this.querySelector('#threshold-setter');
        thresholdSetter.oninput = () => this.populateChart(this.data, thresholdSetter.value, this.label);
        const labelSetter = this.querySelector('#threshold-label');
        labelSetter.oninput = () => this.populateChart(this.data, this.val, labelSetter.value);
        this.querySelector('#closeTimeSeriesChart').onclick = () => {
            thresholdSetter.value = "";
            labelSetter.value = "";
            timeSeriesChart.style.display = 'none';
        }
    }

    populateChart(data, val="", label="") {
        if (data.length == 0) return;
        this.data = data;
        this.val = val;
        this.label = label;
        var labels = Object.keys(data[0].dataset).map(timeStamp => utcToLocal(timeStamp));
        if (this.chart) this.chart.destroy();
        const roundLatLon = (num) => Math.round(num*100) / 100;
        var dataset = [];
        const complementColor = (rgb) => {
            var complement = [];
            for (var colorValue of rgb) {
                var upper = (colorValue + 255) / 2;
                var lower = colorValue / 2;
                if ((upper - colorValue) > (colorValue - lower)) complement.push(upper);
                else complement.push(lower);
            }
            return `rgb(${complement[0]}, ${complement[1]}, ${complement[2]})`;
        };
        const createBackgroundFunction = (rgb) => {
            var color = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
            return function(context) {
                var index = context.dataIndex;
                var value = context.dataset.data[index];
                return (val ==="" || isNaN(val) || value > val) ? color: complementColor(rgb);
            }
        }
        for (var timeSeriesDataset of data) {
            var rgb = timeSeriesDataset.rgb;
            var color = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
            var timeSeriesData = {
                    label: timeSeriesDataset.label + " values at lat: " + roundLatLon(timeSeriesDataset.latLon.lat) + " lon: " + roundLatLon(timeSeriesDataset.latLon.lng),
                    fill: false,
                    data: Object.entries(timeSeriesDataset.dataset).map(entry => entry[1]),
                    borderColor: color,
                    backgroundColor: color,
                    pointBackgroundColor: createBackgroundFunction(rgb),
                    lineTension: 0,
                    borderWidth: 1
            }
            dataset.push(timeSeriesData);
        }
        this.chart = new Chart(this.ctx, {
            type: 'line',
            data: {
                labels: labels,
                datasets: dataset
            },
            options: {
                animation: {
                    duration: 0
                },
                scales: {
                    yAxes: {
                        title: {
                            display: true,
                            text: displayedColorbar.getValue()
                        }
                    },
                    xAxes: {
                        title: {
                            display: true,
                            text: "Timestamp"
                        }
                    }
                },
                plugins: {
                    annotation: {
                        annotations: [{
                            display: val !== "" && !isNaN(val),
                            type: 'line',
                            mode: 'horizontal',
                            scaleID: 'yAxes',
                            value: val,
                            borderColor: 'rgb(255, 99, 132)',
                            borderWidth: 2,
                            label: {
                                enabled: label != "",
                                content: label,
                                xAdjust: 220 - 2*label.length
                            }
                        }]
                      }
                },
            }
        });
        this.querySelector('#timeSeriesChartContainer').style.display = 'block';
    }
}

window.customElements.define('timeseries-chart', TimeSeriesChart);<|endoftext|>


<commit_before>module.exports = {
	env: {
		browser: true
	},

	plugins: [
		'qunit'
	],

	rules: require('./rules/qunit')
};
<commit_msg>fix: Qunit config also extends `plugin:wordpress/recommended` config (#14)<commit_after>module.exports = {
	extends: 'plugin:wordpress/recommended',

	env: {
		browser: true
	},

	plugins: [
		'qunit'
	],

	rules: require('./rules/qunit')
};
<|endoftext|>


<commit_before>var app = app || {};

$(function( $ ) {
	'use strict';

	// PieChart View
	// ---------------

	// This view renders an interactive pie chart given a set of data.
	// Requires width and height to be passed in during construction.
	app.PieChartView = Backbone.View.extend({

		// The DOM element for a pie chart is a canvas
		tagName: 'canvas',

		// DOM id
		id: 'overview-chart',

		// class
		className: 'pie-chart',

		// Delegated events
		events: {
		},

		// At initialization we bind to the relevant events in the 'Debts"
		// so that we can update this chart appropriately. Also setup the
		// Paper.js canvas object.
		initialize: function() {
			window.app.Debts.on( 'reset add destroy sync', this.render, this );

			this.setupPaper();
		},

		// Rendering the pie chart means redrawing it based on data from the Debts collection.
		render: function( eventName ) {
			console.log( 'PieChartView render() called with "' + eventName + '"' );

			// Only attempt to draw the pie char if there are models in the Debts collection.
			if ( app.Debts.length  > 0 ) {
				console.log('-- attempting to draw pie chart');

				// Map each debt type to a value that is the sum of all debts of that type.
				// Query the Debts collection for all models of a given type, and reduce the
				// resulting array down to one value.
				var totals = _.map(DEBT_TYPES, function(type) {
					return _.reduce(app.Debts.where({ type: type }), function(sum, debt) {
						return sum + debt.attributes.principal;
					}, 0);
				});

				// The values now need to be normalized to add up to 360.
				// Start by finding the sum of all the values, then iterate over each one
				// and normalize it.
				var totalSum = _.reduce(totals, function(sum, num) {
					return sum + num;
				});
				var data = _.map(totals, function(num) {
					return (num/totalSum) * 360;
				});

				// Draw the Pie Chart, giving it a compacted (no falsy values) data and color array.
				var colors = ['#FA2', '#999', 'FireBrick', 'Navy', 'DarkGreen', '#333', 'Purple'];
				this.drawPieChart( _.compact(data), colors );
			}

			return this;
		},

		// On view close, we unbind all callbacks previously bound in initialize().
		onClose: function() {
			window.app.Debts.off( null, null, this );
		},

		// Sets up the Paperjs project and view for the desired canvas element.
		setupPaper: function() {
			// TODO: Set the width and height and modify when window resizes
			this.el.width = 570;
			this.el.height = 290;

			// Create an empty project and a view for the canvas:
			paper.setup(this.el);
		},

		// Draw a pie chart using Paper.js vector graphics scripting library.
		// Create a new path for each non-zero entry in the data array
		// TODO: Interaction: MouseOver (or click) to scale and transform
		// TOOD: Animation: onFrame handler to animate scale and transform
		drawPieChart: function(values, colors) {
			// Pre-compute points that will be used by all paths
			// Use the dimensions of the canvas view to calculate the center of the chart.
			var centerX = Math.floor(paper.view.size.width / 2);
			var centerY = Math.floor(paper.view.size.height / 2);
			var radius = Math.floor(paper.view.size.width / 4);
			var center = new paper.Point(centerX, centerY);

			// Set up a point that corresponds to 0 degrees
			var zero = new paper.Point(center.add([radius, 0]));
			this.colors = colors;

			_.each(values, function(value, idx, values) {

				// Only draw arcs if there is more than 1 value, otherwise draw a circle
				if ( values.length >  1) {
					// Create a new path and set up a few configuration attributes
					var path = new paper.Path();
					path.fillColor = this.colors[idx];
					path.strokeColor = 'GhostWhite';
					path.closed = true;

					// Calculate the offset degrees by summing previous data values
					var offset = this.sumTo(values, idx);

					// Calculate the first slice corner by rotating the zero point around the center
					// by the offset degrees
					var sliceCorner1 = zero.rotate(offset, center);

					// Calculate the half way point between the first and second slice corners
					var halfway = zero.rotate(offset + value / 2, center);

					// Calculate the second slice corner by rotating the first by the amount
					// for this data value
					var sliceCorner2 = sliceCorner1.rotate(value, center);

					// Add the points to the path
					path.add(center);
					path.add(sliceCorner1);
					path.arcTo(halfway, sliceCorner2);
				} else {
					var circle = new paper.Path.Circle(center, radius);
					circle.fillColor = this.colors[idx];
				}
			}, this);

			// Draw the view
			paper.view.draw();
		},

		// TODO: Document
		sumTo: function(a, i) {
			var sum = 0;
			for (var j = 0; j < i; j++) {
				sum += a[j];
			}
			return sum;
		}

	});
});
<commit_msg>Implemented hitOptions, mouse tool, and mouse move handler for testing interaction.<commit_after>var app = app || {};

$(function( $ ) {
	'use strict';

	// PieChart View
	// ---------------

	// This view renders an interactive pie chart given a set of data.
	// Requires width and height to be passed in during construction.
	app.PieChartView = Backbone.View.extend({

		// The DOM element for a pie chart is a canvas
		tagName: 'canvas',

		// DOM id
		id: 'overview-chart',

		// class
		className: 'pie-chart',

		// Delegated events
		events: {
		},

		// At initialization we bind to the relevant events in the 'Debts"
		// so that we can update this chart appropriately. Also setup the
		// Paper.js canvas object.
		initialize: function() {
			window.app.Debts.on( 'reset add destroy sync', this.render, this );

			this.setupPaper();
		},

		// Rendering the pie chart means redrawing it based on data from the Debts collection.
		render: function( eventName ) {
			console.log( 'PieChartView render() called with "' + eventName + '"' );

			// Only attempt to draw the pie char if there are models in the Debts collection.
			if ( app.Debts.length  > 0 ) {
				console.log('-- attempting to draw pie chart');

				// Map each debt type to a value that is the sum of all debts of that type.
				// Query the Debts collection for all models of a given type, and reduce the
				// resulting array down to one value.
				var totals = _.map(DEBT_TYPES, function(type) {
					return _.reduce(app.Debts.where({ type: type }), function(sum, debt) {
						return sum + debt.attributes.principal;
					}, 0);
				});

				// The values now need to be normalized to add up to 360.
				// Start by finding the sum of all the values, then iterate over each one
				// and normalize it.
				var totalSum = _.reduce(totals, function(sum, num) {
					return sum + num;
				});
				var data = _.map(totals, function(num) {
					return (num/totalSum) * 360;
				});

				// Draw the Pie Chart, giving it a compacted (no falsy values) data and color array.
				var colors = ['#FA2', '#999', 'FireBrick', 'Navy', 'DarkGreen', '#333', 'Purple'];
				this.drawPieChart( _.compact(data), colors );
			}

			return this;
		},

		// On view close, we unbind all callbacks previously bound in initialize().
		onClose: function() {
			window.app.Debts.off( null, null, this );
		},

		// Sets up the Paperjs project and view for the desired canvas element.
		setupPaper: function() {
			// TODO: Set the width and height and modify when window resizes
			this.el.width = 570;
			this.el.height = 290;

			// Create an empty project and a view for the canvas:
			paper.setup(this.el);
		},

		// Draw a pie chart using Paper.js vector graphics scripting library.
		// Create a new path for each non-zero entry in the data array
		// TODO: Interaction: MouseOver (or click) to scale and transform
		// TOOD: Animation: onFrame handler to animate scale and transform
		drawPieChart: function(values, colors) {
			// Pre-compute points that will be used by all paths
			// Use the dimensions of the canvas view to calculate the center of the chart.
			var centerX = Math.floor(paper.view.size.width / 2);
			var centerY = Math.floor(paper.view.size.height / 2);
			var radius = Math.floor(paper.view.size.width / 4);
			var center = new paper.Point(centerX, centerY);

			// Set up a point that corresponds to 0 degrees
			var zero = new paper.Point(center.add([radius, 0]));
			this.colors = colors;

			_.each(values, function(value, idx, values) {

				// Only draw arcs if there is more than 1 value, otherwise draw a circle
				if ( values.length >  1) {
					// Create a new path and set up a few configuration attributes
					var path = new paper.Path();
					path.fillColor = this.colors[idx];
					path.strokeColor = 'GhostWhite';
					path.closed = true;

					// Calculate the offset degrees by summing previous data values
					var offset = this.sumTo(values, idx);

					// Calculate the first slice corner by rotating the zero point around the center
					// by the offset degrees
					var sliceCorner1 = zero.rotate(offset, center);

					// Calculate the half way point between the first and second slice corners
					var halfway = zero.rotate(offset + value / 2, center);

					// Calculate the second slice corner by rotating the first by the amount
					// for this data value
					var sliceCorner2 = sliceCorner1.rotate(value, center);

					// Add the points to the path
					path.add(center);
					path.add(sliceCorner1);
					path.arcTo(halfway, sliceCorner2);
				} else {
					var circle = new paper.Path.Circle(center, radius);
					circle.fillColor = this.colors[idx];
				}
			}, this);

			// Define hit options, create a new tool, and define a mouse move handler
			var hitOptions = {
				segments: true,
				stroke: true,
				fill: true,
				tolerance: 5
			};

			var tool = new paper.Tool();
			tool.onMouseMove = function(event) {
				var hitResult = paper.project.hitTest(event.point, hitOptions);
				paper.project.activeLayer.selected = false;
				if (hitResult && hitResult.item)
					hitResult.item.selected = true;
			}

			// Draw the view
			paper.view.draw();
		},

		// TODO: Document
		sumTo: function(a, i) {
			var sum = 0;
			for (var j = 0; j < i; j++) {
				sum += a[j];
			}
			return sum;
		}

	});
});
<|endoftext|>


<commit_before>const { LayerController } = require('../components/layerController');

var globalMap = {};
var imageUrl = '';
global.L = { DomEvent: {disableClickPropagation: jest.fn(), disableScrollPropagation: jest.fn()},
            imageOverlay: (raster, coordinates, settings) => ({setUrl: (url, coords, options) => {globalMap[url] = coords; imageUrl = url;},
                                                               addTo: (map) => {globalMap[raster] = coordinates},
                                                               remove: (map) => {delete globalMap[raster]},
                                                               bringToFront: () => {},
                                                               bringToBack: () => {},
                                                               _image: {}}),
            icon: (options) => {}};

const controller = require('../components/Controller.js');
jest.mock('../components/Controller.js', () => ({
    controllers: ({
        currentDomain: ({
            getValue: () => 1,
            subscribe: () => {}
        }),
        syncImageLoad: ({
            subscribe: (fun) => {},
            increment: () => {}
        }),
        currentTimestamp: ({
            getValue: () => '2020',
            subscribe: (fun) => {}
        }),
    }),

}));

const util = require('../util.js');
jest.mock('../util.js', () => ({
    simVars: ({
        currentSimulation: 'test',
        rasters: ({
            1: {
                "2020": {"raster": {raster: "raster test", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]}, "colorbar": "test colorbar raster"},
                    "overlay": {raster: "overlay test", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]}, "colorbar": "test colorbar overlay" }},
                "2021": {"raster": {raster: "raster test current timestamp", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]}},
                    "overlay": {raster: "overlay test", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]} }}
            },
            2: {
                "2020": {"raster": {raster: "raster test 2", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]}},
                    "overlay": {raster: "overlay test 2", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]} }}
            }
        }),
        rasterBase: 'testBase/',
        sortedTimestamps: ['2020'],
        overlayOrder: [],
        displayedColorbar: null,
        organization: 'SJSU',
        overlayList: ['overlay'],
        baseLayerDict: {},
    }),
    map: {
        addTo: jest.fn(),
        fitBounds: jest.fn(),
        on: jest.fn()
    },
    dragElement: jest.fn(),
    debounce: jest.fn()
}));

HTMLCanvasElement.prototype.getContext = () => {
    return {drawImage: jest.fn()}
};

describe('Tests for adding layers to menu and selecting layers', () => {
    var layerController;

    beforeEach(async () => {
        globalMap = {};
        controller.controllers.currentDomain.getValue = () => 1;
        controller.controllers.currentTimestamp.getValue = () =>'2020';
        util.simVars.overlayOrder = []
        // controllers.current_timestamp.getValue = () => "2020";
        // controllers.overlayOrder = [];

        const div = document.createElement('div');
        div.id = 'raster-colorbar';
        await document.body.appendChild(div);
        layerController = await document.body.appendChild(new LayerController());
        layerController.domainSwitch();
    });

    test('Layer Controller should populate its raster and overlay layers correctly', () => {
        const rasterDict = layerController.rasterDict;
        const overlayDict = layerController.overlayDict;
        expect(Object.entries(rasterDict).length).toEqual(1);
        expect('raster' in rasterDict).toEqual(true);
        expect(Object.entries(overlayDict).length).toEqual(1);
        expect('overlay' in overlayDict).toEqual(true);
    });

    test('Layers should be correctly added to the map when selected', () => {
        layerController.loadWithPriority = jest.fn(),
        layerController.handleOverlayadd("raster");
        expect('testBase/raster test' in globalMap).toEqual(true);
        expect(util.simVars.overlayOrder.includes('raster')).toEqual(true);
    });

    // test('Layers should be correctly removed from the map when selected', () => {
    //     layerController.handleOverlayadd("raster");
    //     layerController.handleOverlayRemove("raster");
    //     expect("test_base/raster test" in globalMap).toEqual(false);
    // });

    // test('Layer Controller should preserve previous selected layers when domain is switched on the same simulation', () => {
    //     layerController.handleOverlayadd("raster");
    //     controllers.currentDomain.getValue = () => 2;
    //     layerController.domainSwitch();
    //     expect(controllers.overlayOrder.includes("raster")).toEqual(true);
    //     expect("test_base/raster test 2" in globalMap).toEqual(true);
    //     expect("test_base/raster test" in globalMap).toEqual(false);
    // });

    // test('Layer Controller should clear selected layers when domain is switched to new simulation', () => {
    //     layerController.handleOverlayadd("raster");
    //     controllers.currentSimulation.getValue = () => "new simulation";
    //     layerController.domainSwitch();
    //     expect(controllers.overlayOrder.length).toEqual(0);
    //     expect(globalMap).toEqual({});
    // });

    // test('Layer Controller should show the current_timestamp', () => {
    //     controllers.current_timestamp.getValue = () => "2021";
    //     layerController.handleOverlayadd("raster");
    //     expect("test_base/raster test current timestamp" in globalMap).toEqual(true);
    // });

    // test('updateSlider should set img according to currentTimestamp', () => {
    //     controllers.current_timestamp.getValue = () => "2021";
    //     layerController.handleOverlayadd("raster");
    //     expect(imageUrl).toEqual("test_base/raster test current timestamp");
    // });
});

// describe('Tests for adding layers with colorbars', () => {
//     var layerController;

//     beforeEach(async () => {
//         controllers.currentDomain.getValue = () => 1;
//         controllers.current_timestamp.getValue = () => "2020";
//         controllers.overlayOrder = [];
//         var colorbar = "";
//         controllers.displayedColorbar.getValue = () => colorbar;
//         controllers.displayedColorbar.setValue = (newColorbar) => {colorbar = newColorbar};
//         const div = document.createElement("div");
//         div.id = "raster-colorbar";
//         await document.body.appendChild(div);
//         layerController = await document.body.appendChild(new LayerController());
//         layerController.domainSwitch();
//     });

//     test('Layer Controller should add any colorbars', () => {
//         layerController.handleOverlayadd("raster");
//         expect(controllers.displayedColorbar.getValue()).toEqual("raster");
//     });

//     test('Layer Controller should remove colorbars when layer deselected', () => {
//         layerController.handleOverlayadd("raster");
//         layerController.handleOverlayRemove("raster");
//         expect(controllers.displayedColorbar.getValue()).toEqual(null);
//     });

//     test('Layer Controller should put most recent selected colorbar on top', () => {
//         layerController.handleOverlayadd("raster");
//         layerController.handleOverlayadd("overlay");
//         expect(controllers.displayedColorbar.getValue()).toEqual("overlay");
//     });

//     test('Layer Controller should put last selected colorbar on top when another is deselected', () => {
//         layerController.handleOverlayadd("raster");
//         layerController.handleOverlayadd("overlay");
//         layerController.handleOverlayRemove("overlay");
//         expect(controllers.displayedColorbar.getValue()).toEqual("raster");
//     });

//     test('Layer Controller should remove colorbar when switching to a domain without one', () => {
//         layerController.handleOverlayadd("raster");
//         controllers.currentDomain.getValue = () => 2;
//         layerController.domainSwitch();
//         expect(controllers.displayedColorbar.getValue()).toEqual(null);
//     });
// });<commit_msg>Fixes more tests<commit_after>const { LayerController } = require('../components/layerController');

var globalMap = {};
var imageUrl = '';
global.L = { DomEvent: {disableClickPropagation: jest.fn(), disableScrollPropagation: jest.fn()},
            imageOverlay: (raster, coordinates, settings) => ({setUrl: (url, coords, options) => {globalMap[url] = coords; imageUrl = url;},
                                                               addTo: (map) => {globalMap[raster] = coordinates},
                                                               remove: (map) => {delete globalMap[raster]},
                                                               bringToFront: () => {},
                                                               bringToBack: () => {},
                                                               _image: {}}),
            icon: (options) => {}};

const controller = require('../components/Controller.js');
jest.mock('../components/Controller.js', () => ({
    controllers: ({
        currentDomain: ({
            getValue: () => 1,
            subscribe: () => {}
        }),
        syncImageLoad: ({
            subscribe: (fun) => {},
            increment: () => {}
        }),
        currentTimestamp: ({
            getValue: () => '2020',
            subscribe: (fun) => {}
        }),
    }),

}));

const util = require('../util.js');
jest.mock('../util.js', () => ({
    simVars: ({
        currentSimulation: 'test',
        rasters: ({
            1: {
                "2020": {"raster": {raster: "raster test", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]}, "colorbar": "test colorbar raster"},
                    "overlay": {raster: "overlay test", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]}, "colorbar": "test colorbar overlay" }},
                "2021": {"raster": {raster: "raster test current timestamp", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]}},
                    "overlay": {raster: "overlay test", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]} }}
            },
            2: {
                "2020": {"raster": {raster: "raster test 2", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]}},
                    "overlay": {raster: "overlay test 2", coords: {0: [0, 0], 1: [0, 1], 2: [1, 0], 3: [1, 1]} }}
            }
        }),
        rasterBase: 'testBase/',
        sortedTimestamps: ['2020'],
        overlayOrder: [],
        displayedColorbar: null,
        organization: 'SJSU',
        overlayList: ['overlay'],
        baseLayerDict: {},
    }),
    map: {
        addTo: jest.fn(),
        fitBounds: jest.fn(),
        on: jest.fn()
    },
    dragElement: jest.fn(),
    debounce: jest.fn()
}));

HTMLCanvasElement.prototype.getContext = () => {
    return {drawImage: jest.fn()}
};

describe('Tests for adding layers to menu and selecting layers', () => {
    var layerController;

    beforeEach(async () => {
        globalMap = {};
        controller.controllers.currentDomain.getValue = () => 1;
        controller.controllers.currentTimestamp.getValue = () =>'2020';
        util.simVars.overlayOrder = []
        // controllers.current_timestamp.getValue = () => "2020";
        // controllers.overlayOrder = [];

        const div = document.createElement('div');
        div.id = 'raster-colorbar';
        await document.body.appendChild(div);
        layerController = await document.body.appendChild(new LayerController());
        layerController.domainSwitch();
    });

    test('Layer Controller should populate its raster and overlay layers correctly', () => {
        const rasterDict = layerController.rasterDict;
        const overlayDict = layerController.overlayDict;
        expect(Object.entries(rasterDict).length).toEqual(1);
        expect('raster' in rasterDict).toEqual(true);
        expect(Object.entries(overlayDict).length).toEqual(1);
        expect('overlay' in overlayDict).toEqual(true);
    });

    test('Layers should be correctly added to the map when selected', () => {
        layerController.loadWithPriority = jest.fn(),
        layerController.handleOverlayadd('raster');
        expect('testBase/raster test' in globalMap).toEqual(true);
        expect(util.simVars.overlayOrder.includes('raster')).toEqual(true);
    });

    test('Layers should be correctly removed from the map when selected', () => {
        layerController.loadWithPriority = jest.fn(),
        layerController.handleOverlayadd('raster');
        layerController.handleOverlayRemove('raster');
        expect('testBase/raster test' in globalMap).toEqual(false);
    });

    test('Layer Controller should preserve previous selected layers when domain is switched on the same simulation', () => {
        layerController.loadWithPriority = jest.fn(),
        layerController.handleOverlayadd('raster');
        controller.controllers.currentDomain.getValue = () => 2;
        layerController.domainSwitch();
        expect(util.simVars.overlayOrder.includes('raster')).toEqual(true);
        expect('testBase/raster test 2' in globalMap).toEqual(true);
        expect('testBase/raster test' in globalMap).toEqual(false);
    });

    // test('Layer Controller should clear selected layers when domain is switched to new simulation', () => {
    //     layerController.handleOverlayadd("raster");
    //     controllers.currentSimulation.getValue = () => "new simulation";
    //     layerController.domainSwitch();
    //     expect(controllers.overlayOrder.length).toEqual(0);
    //     expect(globalMap).toEqual({});
    // });

    // test('Layer Controller should show the current_timestamp', () => {
    //     controllers.current_timestamp.getValue = () => "2021";
    //     layerController.handleOverlayadd("raster");
    //     expect("test_base/raster test current timestamp" in globalMap).toEqual(true);
    // });

    // test('updateSlider should set img according to currentTimestamp', () => {
    //     controllers.current_timestamp.getValue = () => "2021";
    //     layerController.handleOverlayadd("raster");
    //     expect(imageUrl).toEqual("test_base/raster test current timestamp");
    // });
});

// describe('Tests for adding layers with colorbars', () => {
//     var layerController;

//     beforeEach(async () => {
//         controllers.currentDomain.getValue = () => 1;
//         controllers.current_timestamp.getValue = () => "2020";
//         controllers.overlayOrder = [];
//         var colorbar = "";
//         controllers.displayedColorbar.getValue = () => colorbar;
//         controllers.displayedColorbar.setValue = (newColorbar) => {colorbar = newColorbar};
//         const div = document.createElement("div");
//         div.id = "raster-colorbar";
//         await document.body.appendChild(div);
//         layerController = await document.body.appendChild(new LayerController());
//         layerController.domainSwitch();
//     });

//     test('Layer Controller should add any colorbars', () => {
//         layerController.handleOverlayadd("raster");
//         expect(controllers.displayedColorbar.getValue()).toEqual("raster");
//     });

//     test('Layer Controller should remove colorbars when layer deselected', () => {
//         layerController.handleOverlayadd("raster");
//         layerController.handleOverlayRemove("raster");
//         expect(controllers.displayedColorbar.getValue()).toEqual(null);
//     });

//     test('Layer Controller should put most recent selected colorbar on top', () => {
//         layerController.handleOverlayadd("raster");
//         layerController.handleOverlayadd("overlay");
//         expect(controllers.displayedColorbar.getValue()).toEqual("overlay");
//     });

//     test('Layer Controller should put last selected colorbar on top when another is deselected', () => {
//         layerController.handleOverlayadd("raster");
//         layerController.handleOverlayadd("overlay");
//         layerController.handleOverlayRemove("overlay");
//         expect(controllers.displayedColorbar.getValue()).toEqual("raster");
//     });

//     test('Layer Controller should remove colorbar when switching to a domain without one', () => {
//         layerController.handleOverlayadd("raster");
//         controllers.currentDomain.getValue = () => 2;
//         layerController.domainSwitch();
//         expect(controllers.displayedColorbar.getValue()).toEqual(null);
//     });
// });<|endoftext|>


<commit_before>'use strict';

var path = require('path');
var Module = require('module');
var yaml = require('js-yaml');
var json = require('parse-json');
var debug = require('debug')('unified-engine:configuration');
var resolve = require('load-plugin').resolve;
var fault = require('fault');
var has = require('has');
var xtend = require('xtend');
var array = require('isarray');
var object = require('is-object');
var string = require('x-is-string');
var func = require('x-is-function');
var FindUp = require('./find-up');

module.exports = Config;

var extname = path.extname;
var basename = path.basename;
var dirname = path.dirname;
var relative = path.relative;

var loaders = {
  '.json': loadJSON,
  '.js': loadScript,
  '.yaml': loadYAML,
  '.yml': loadYAML
};

var defaultLoader = loadJSON;

Config.prototype.load = load;

function Config(options) {
  var rcName = options.rcName;
  var packageField = options.packageField;
  var names = [];

  this.cwd = options.cwd;
  this.packageField = options.packageField;
  this.pluginPrefix = options.pluginPrefix;
  this.configTransform = options.configTransform;

  if (rcName) {
    names.push(rcName, rcName + '.js', rcName + '.yml', rcName + '.yaml');
    debug('Looking for `%s` configuration files', names);
  }

  if (packageField) {
    names.push('package.json');
    debug('Looking for `%s` fields in `package.json` files', packageField);
  }

  this.given = {settings: options.settings, plugins: options.plugins};
  this.create = create.bind(this);

  this.findUp = new FindUp({
    filePath: options.rcPath,
    cwd: options.cwd,
    detect: options.detectConfig,
    names: names,
    create: this.create
  });
}

function load(filePath, callback) {
  var self = this;

  self.findUp.load(filePath, done);

  function done(err, res) {
    if (err || res) {
      return callback(err, res);
    }

    callback(null, self.create());
  }
}

function create(buf, filePath) {
  var self = this;
  var transform = self.configTransform;
  var fn = (filePath && loaders[extname(filePath)]) || defaultLoader;
  var options = {prefix: self.pluginPrefix, cwd: self.cwd};
  var result = {settings: {}, plugins: []};
  var contents = buf ? fn.apply(self, arguments) : undefined;

  if (transform && contents !== undefined) {
    contents = transform(contents, filePath);
  }

  /* Exit if we did find a `package.json`, but it doesn’t have configuration. */
  if (buf && contents === undefined && basename(filePath) === 'package.json') {
    return;
  }

  if (contents !== undefined) {
    merge(result, contents, null, xtend(options, {root: dirname(filePath)}));
  }

  merge(result, self.given, null, xtend(options, {root: self.cwd}));

  return result;
}

/* Basically `Module.prototype.load`, but for a buffer instead
 * of a filepath. */
function loadScript(buf, filePath) {
  var submodule = Module._cache[filePath];

  if (!submodule) {
    submodule = new Module(filePath, module);
    submodule.filename = filePath;
    submodule.paths = Module._nodeModulePaths(dirname(filePath));
    submodule._compile(String(buf), filePath);
    submodule.loaded = true;
    Module._cache[filePath] = submodule;
  }

  return submodule.exports;
}

function loadYAML(buf, filePath) {
  return yaml.safeLoad(buf, {filename: basename(filePath)});
}

function loadJSON(buf, filePath) {
  var result = json(buf, filePath);

  if (basename(filePath) === 'package.json') {
    result = result[this.packageField];
  }

  return result;
}

function merge(target, raw, val, options) {
  var root = options.root;
  var cwd = options.cwd;
  var prefix = options.prefix;

  if (object(raw)) {
    addPreset(raw);
  } else {
    throw new Error('Expected preset, not `' + raw + '`');
  }

  return target;

  function addPreset(result) {
    var plugins = result.plugins;

    if (plugins === null || plugins === undefined) {
      /* empty */
    } else if (array(plugins)) {
      addEach(plugins);
    } else if (object(plugins)) {
      addIn(plugins);
    } else {
      throw new Error('Expected a list or object of plugins, not `' + plugins + '`');
    }

    target.settings = xtend(target.settings, result.settings);
  }

  function addEach(result) {
    var length = result.length;
    var index = -1;
    var value;

    while (++index < length) {
      value = result[index];

      if (array(value)) {
        use.apply(null, value);
      } else {
        use(value);
      }
    }
  }

  function addIn(result) {
    var key;

    for (key in result) {
      use(key, result[key]);
    }
  }

  function use(usable, value) {
    if (string(usable)) {
      addModule(usable, value);
    } else if (func(usable)) {
      addPlugin(usable, value);
    } else {
      merge(target, usable, value, options);
    }
  }

  function addModule(id, value) {
    var fp = resolve(id, {cwd: root, prefix: prefix});
    var res;

    if (fp) {
      try {
        res = require(fp); // eslint-disable-line import/no-dynamic-require
      } catch (err) {
        throw fault('Cannot parse script `%s`\n%s', relative(root, fp), err.stack);
      }

      try {
        if (func(res)) {
          addPlugin(res, value);
        } else {
          merge(target, res, value, xtend(options, {root: dirname(fp)}));
        }
      } catch (err) {
        throw fault('Error: Expected preset or plugin, not %s, at `%s`', res, relative(root, fp));
      }
    } else {
      fp = relative(cwd, path.resolve(root, id));
      addPlugin(failingModule(fp, new Error('Could not find module `' + id + '`')), value);
    }
  }

  function addPlugin(result, value) {
    var entry = find(target.plugins, result);

    if (entry) {
      reconfigure(entry, value);
    } else {
      target.plugins.push([result, value]);
    }
  }
}

function reconfigure(entry, value) {
  if (value !== false && entry[1] !== false && !array(value)) {
    value = xtend(entry[1], value);
  }

  entry[1] = value;
}

function find(entries, plugin) {
  var length = entries.length;
  var index = -1;
  var entry;

  while (++index < length) {
    entry = entries[index];

    if (entry[0] === plugin) {
      return entry;
    }
  }
}

function failingModule(id, err) {
  var cache = failingModule.cache || (failingModule.cache = {});
  var submodule = has(cache, id) ? cache[id] : (cache[id] = fail);
  return submodule;
  function fail() {
    throw err;
  }
}
<commit_msg>Fix reconfiguring plugins with non-objects as options<commit_after>'use strict';

var path = require('path');
var Module = require('module');
var yaml = require('js-yaml');
var json = require('parse-json');
var debug = require('debug')('unified-engine:configuration');
var resolve = require('load-plugin').resolve;
var fault = require('fault');
var has = require('has');
var xtend = require('xtend');
var array = require('isarray');
var object = require('is-object');
var string = require('x-is-string');
var func = require('x-is-function');
var FindUp = require('./find-up');

module.exports = Config;

var extname = path.extname;
var basename = path.basename;
var dirname = path.dirname;
var relative = path.relative;

var loaders = {
  '.json': loadJSON,
  '.js': loadScript,
  '.yaml': loadYAML,
  '.yml': loadYAML
};

var defaultLoader = loadJSON;

Config.prototype.load = load;

function Config(options) {
  var rcName = options.rcName;
  var packageField = options.packageField;
  var names = [];

  this.cwd = options.cwd;
  this.packageField = options.packageField;
  this.pluginPrefix = options.pluginPrefix;
  this.configTransform = options.configTransform;

  if (rcName) {
    names.push(rcName, rcName + '.js', rcName + '.yml', rcName + '.yaml');
    debug('Looking for `%s` configuration files', names);
  }

  if (packageField) {
    names.push('package.json');
    debug('Looking for `%s` fields in `package.json` files', packageField);
  }

  this.given = {settings: options.settings, plugins: options.plugins};
  this.create = create.bind(this);

  this.findUp = new FindUp({
    filePath: options.rcPath,
    cwd: options.cwd,
    detect: options.detectConfig,
    names: names,
    create: this.create
  });
}

function load(filePath, callback) {
  var self = this;

  self.findUp.load(filePath, done);

  function done(err, res) {
    if (err || res) {
      return callback(err, res);
    }

    callback(null, self.create());
  }
}

function create(buf, filePath) {
  var self = this;
  var transform = self.configTransform;
  var fn = (filePath && loaders[extname(filePath)]) || defaultLoader;
  var options = {prefix: self.pluginPrefix, cwd: self.cwd};
  var result = {settings: {}, plugins: []};
  var contents = buf ? fn.apply(self, arguments) : undefined;

  if (transform && contents !== undefined) {
    contents = transform(contents, filePath);
  }

  /* Exit if we did find a `package.json`, but it doesn’t have configuration. */
  if (buf && contents === undefined && basename(filePath) === 'package.json') {
    return;
  }

  if (contents !== undefined) {
    merge(result, contents, null, xtend(options, {root: dirname(filePath)}));
  }

  merge(result, self.given, null, xtend(options, {root: self.cwd}));

  return result;
}

/* Basically `Module.prototype.load`, but for a buffer instead
 * of a filepath. */
function loadScript(buf, filePath) {
  var submodule = Module._cache[filePath];

  if (!submodule) {
    submodule = new Module(filePath, module);
    submodule.filename = filePath;
    submodule.paths = Module._nodeModulePaths(dirname(filePath));
    submodule._compile(String(buf), filePath);
    submodule.loaded = true;
    Module._cache[filePath] = submodule;
  }

  return submodule.exports;
}

function loadYAML(buf, filePath) {
  return yaml.safeLoad(buf, {filename: basename(filePath)});
}

function loadJSON(buf, filePath) {
  var result = json(buf, filePath);

  if (basename(filePath) === 'package.json') {
    result = result[this.packageField];
  }

  return result;
}

function merge(target, raw, val, options) {
  var root = options.root;
  var cwd = options.cwd;
  var prefix = options.prefix;

  if (object(raw)) {
    addPreset(raw);
  } else {
    throw new Error('Expected preset, not `' + raw + '`');
  }

  return target;

  function addPreset(result) {
    var plugins = result.plugins;

    if (plugins === null || plugins === undefined) {
      /* empty */
    } else if (array(plugins)) {
      addEach(plugins);
    } else if (object(plugins)) {
      addIn(plugins);
    } else {
      throw new Error('Expected a list or object of plugins, not `' + plugins + '`');
    }

    target.settings = xtend(target.settings, result.settings);
  }

  function addEach(result) {
    var length = result.length;
    var index = -1;
    var value;

    while (++index < length) {
      value = result[index];

      if (array(value)) {
        use.apply(null, value);
      } else {
        use(value);
      }
    }
  }

  function addIn(result) {
    var key;

    for (key in result) {
      use(key, result[key]);
    }
  }

  function use(usable, value) {
    if (string(usable)) {
      addModule(usable, value);
    } else if (func(usable)) {
      addPlugin(usable, value);
    } else {
      merge(target, usable, value, options);
    }
  }

  function addModule(id, value) {
    var fp = resolve(id, {cwd: root, prefix: prefix});
    var res;

    if (fp) {
      try {
        res = require(fp); // eslint-disable-line import/no-dynamic-require
      } catch (err) {
        throw fault('Cannot parse script `%s`\n%s', relative(root, fp), err.stack);
      }

      try {
        if (func(res)) {
          addPlugin(res, value);
        } else {
          merge(target, res, value, xtend(options, {root: dirname(fp)}));
        }
      } catch (err) {
        throw fault('Error: Expected preset or plugin, not %s, at `%s`', res, relative(root, fp));
      }
    } else {
      fp = relative(cwd, path.resolve(root, id));
      addPlugin(failingModule(fp, new Error('Could not find module `' + id + '`')), value);
    }
  }

  function addPlugin(result, value) {
    var entry = find(target.plugins, result);

    if (entry) {
      reconfigure(entry, value);
    } else {
      target.plugins.push([result, value]);
    }
  }
}

function reconfigure(entry, value) {
  if (value !== false && entry[1] !== false && object(value)) {
    value = xtend(entry[1], value);
  }

  entry[1] = value;
}

function find(entries, plugin) {
  var length = entries.length;
  var index = -1;
  var entry;

  while (++index < length) {
    entry = entries[index];

    if (entry[0] === plugin) {
      return entry;
    }
  }
}

function failingModule(id, err) {
  var cache = failingModule.cache || (failingModule.cache = {});
  var submodule = has(cache, id) ? cache[id] : (cache[id] = fail);
  return submodule;
  function fail() {
    throw err;
  }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

define(
  ["designer/utils", "ceci/ceci-designer", "l10n", "analytics"],
  function(Util, CeciDesigner, L10n, analytics) {
    "use strict";

    var resolvePath = function(tag, url) {
      try {
        return document.createElement(tag).resolvePath(url);
      }
      catch (e) {
        console.error('resolvePath failed for component ' + tag + '.');
        return url;
      }
    };

    var knownComponents = [];
    var tags = [];

    var categories = {
      "basic" : [
         "ceci-button",
         "ceci-counter",
         "ceci-image",
         "ceci-header",
         "ceci-metronome"
      ],
      "layout" : [
        "ceci-button",
        "ceci-button-with-confirmation",
        "ceci-double-button",
        "ceci-header",
        "ceci-image",
        "ceci-spacer",
        "ceci-textbox"
      ],
      "connectors" : [
        "ceci-transformer",
        "ceci-channel-gate",
        "ceci-alternating-gate",
        "ceci-splitter",
        "ceci-combiner"
      ],
      "audio" : [
        "ceci-audio",
        "ceci-cowbell",
        "ceci-snaredrum",
        "ceci-kickdrum",
        "ceci-metronome",
        "ceci-microphone-button",
        "ceci-sequencer"
      ],
      "logic" : [
        "ceci-bool",
        "ceci-counter",
        "ceci-daily-counter",
        "ceci-random"
      ],
      "media" : [
        "ceci-chart",
        "ceci-component-canvas",
        "ceci-camera-button",
        "ceci-chat-window",
        "ceci-fireworks",
        "ceci-hot-potato",
        "ceci-jazzhands",
        "ceci-meatspaces-messages",
        "ceci-meatspaces-input",
        "ceci-notebook",
        "ceci-pad-grid",
        "ceci-text-input",
        "ceci-todo-list"
      ]
    }

    categories.names = [];

    for (var key in categories) {
      if (categories.hasOwnProperty(key)) {
        categories.names.push(key);
      }
    }

    var DesignerTray = {
      buildItem : function(name){
        var item = document.createElement('designer-component-tray-item');
        var ceciDefinition = CeciDesigner.getCeciDefinitionScript(name);

        item.setAttribute('name', name);
        item.setAttribute('thumbnail', resolvePath(name, ceciDefinition.thumbnail));
        item.setAttribute('label', ceciDefinition.name || Util.prettyName(name));
        item.setAttribute('description', L10n.get(name + "/description") || ceciDefinition.description);
        item.setAttribute('author', ceciDefinition.author);
        item.setAttribute('updatedat', ceciDefinition.updatedAt);

        var bricktags = ceciDefinition.tags;

        for(var k = 0; k < bricktags.length; k++){
          var tag = bricktags[k];
          if(tags.indexOf(tag) == -1) {
            tags.push(tag);
          }
        }

        item.addEventListener('click', function (e) {
          var card = document.querySelector('ceci-card[visible]');
          if (card) {
            var newElement = document.createElement(name);
            // wait until Polymer has prepared the element completely
            newElement.async(function() {
              newElement.applyDefaults();
              card.appendChild(newElement);
              analytics.event("Added Component", {label: name});
            });
          }
        }, false);
        item.label = L10n.get(name) || item.label;
        return item;
      },
      addComponentWithName: function(name) {

        if(!DesignerTray.isKnownComponent(name)) {
          var added = false;
          // See if it goes in any categories
          for(var i = 0; i < categories.names.length; i++){
            var category = categories.names[i];
            var components = categories[category.toLowerCase()] || [];
            for(var j = 0; j < components.length; j++){
              if(name == components[j]){
                var item = DesignerTray.buildItem(name)
                var containerCount = document.querySelectorAll(".category-container." + category.toLowerCase()).length;
                if(containerCount == 0) {
                  DesignerTray.buildCategory(category.toLowerCase());
                }
                document.querySelector(".category-container." + category.toLowerCase()).appendChild(item);
                added = true;
              }
            }
          }
          //Means it wasn't in any categories
          if(!added){
            var item = DesignerTray.buildItem(name)
            var containerCount = document.querySelectorAll(".category-container.other").length;
            if(containerCount == 0) {
              DesignerTray.buildCategory("other");
            }
            document.querySelector(".category-container.other").appendChild(item);
          }
          knownComponents.push(name);
        }

      },
      sortComponents: function (container) {
        // Sorts components in each category container
        var containers = document.querySelectorAll('.category-container');
        for(var i = 0; i < containers.length; i++){
          var container = containers[i];
          var components = container.querySelectorAll('designer-component-tray-item').array();
          components = components.sort(function (a, b) {
            if (a.label > b.label) return 1;
            if (a.label < b.label) return -1;
            return 0;
          });
          components.forEach(function (c) {
            container.appendChild(c);
          });
        }
      },
      buildCategory : function(category){
        var categoryContainer = document.querySelector("[data-category="+category.toLowerCase()+"]");

        //Check if this category container exists already
        if(!categoryContainer) {
          var that = this;

          if(category != "all") {
            //Build Category Container
            var container = document.createElement("div");
            var title = document.createElement("h2");
            title.innerHTML = category.charAt(0).toUpperCase() + category.slice(1) + " Bricks";
            container.appendChild(title);
            container.classList.add("category-container");
            container.classList.add(category.toLowerCase());
            document.querySelector("#components").appendChild(container);
          }

          //Build tag
          var tagContainer = document.querySelector(".brick-category");
          var option = document.createElement("a");

          if(category == "all") {
            option.innerHTML = "All Bricks";
          } else {
            option.innerHTML = category.charAt(0).toUpperCase() + category.slice(1);
          }

          option.setAttribute("data-category",category.toLowerCase());
          tagContainer.appendChild(option);

          option.addEventListener("click",function(e){
            that.filterCategory(e.target.getAttribute("data-category"));
          });
        }
      },
      addComponentsFromRegistry: function() {
        DesignerTray.buildCategory("all");
        CeciDesigner.forEachComponent(this.addComponentWithName);
        DesignerTray.filterCategory("basic");
        DesignerTray.sortComponents();
      },
      filterCategory : function(category){
        var categoryLinks = document.querySelectorAll(".brick-category a");
        Array.prototype.forEach.call(categoryLinks, function(el, i){
          el.classList.remove("selected-category");
        });

        document.querySelector(".brick-category a[data-category="+category+"]").classList.add("selected-category");
        document.querySelector("#components").setAttribute("data-category",category);

        var categoryEls = document.querySelectorAll(".category-container");
        if(category == "all"){
          Array.prototype.forEach.call(categoryEls, function(el, i){
            el.style.display = "";
          });
        } else {
          Array.prototype.forEach.call(categoryEls, function(el, i){
            el.style.display = "none";
          });
          document.querySelector(".category-container." + category).style.display = "";
        }
      },
      isKnownComponent: function(name) {
        return knownComponents.indexOf(name) > -1;
      },
      forgetComponent: function(name) {
        var pos = knownComponents.indexOf(name);
        if (pos > -1) {
          knownComponents.splice(pos, 1);
          var componentTrayContainer = document.getElementById('components');
          var item = componentTrayContainer.querySelector("[name='" + name + "']");
          item.parentNode.removeChild(item);
        }
      }
    };

    window.addEventListener("polymer-ready", function () {
      DesignerTray.addComponentsFromRegistry();
      var categoryContainers = document.querySelectorAll(".category-container");
      var searchBox = document.querySelector('.component-search');
      searchBox.addEventListener('keyup', function (e) {
        var searchValue = searchBox.value.trim().toLowerCase();
        var componentsContainer = document.querySelector('#components');
        if (searchValue.length > 0) {
          CeciDesigner.forEachComponent(function (componentTag) {
            var menuElement = componentsContainer.querySelector('designer-component-tray-item[name="' + componentTag + '"]');

            if (window.CeciDefinitions[componentTag] && menuElement) {
              var tags = window.CeciDefinitions[componentTag].tags || [];
              var found = false;

              var stringsToSearch = typeof tags === 'string' ? [tags] : tags;
              stringsToSearch.push(componentTag.toLowerCase());
              window.CeciDefinitions[componentTag].name && stringsToSearch.push(window.CeciDefinitions[componentTag].name.toLowerCase());
              window.CeciDefinitions[componentTag].description && stringsToSearch.push(window.CeciDefinitions[componentTag].description.toLowerCase());

              stringsToSearch.forEach(function (string) {
                found = found || string.toLowerCase().indexOf(searchValue) > -1;
              });

              if (!found) {
                menuElement.classList.add('hide');
              }
              else {
                menuElement.classList.remove('hide');
              }
            }
          });

          Array.prototype.forEach.call(categoryContainers, function(el, i){
            if(el.querySelectorAll("designer-component-tray-item:not(.hide)").length == 0) {
              el.classList.add("no-results");
            } else {
              el.classList.remove("no-results");
            }
          });

        }
        else {
          Array.prototype.forEach.call(categoryContainers, function(el, i){
            el.classList.remove("no-results");
          });
          Array.prototype.forEach.call(componentsContainer.querySelectorAll('designer-component-tray-item'), function (e) {
            e.classList.remove('hide');
          });
        }
      });
    });

    return DesignerTray;
  }
);
<commit_msg>fixes #1594 Create categories all at once, then show them when needed, this ensures the order they are in when we create them is the order they get displayed in<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

define(
  ["designer/utils", "ceci/ceci-designer", "l10n", "analytics"],
  function(Util, CeciDesigner, L10n, analytics) {
    "use strict";

    var resolvePath = function(tag, url) {
      try {
        return document.createElement(tag).resolvePath(url);
      }
      catch (e) {
        console.error('resolvePath failed for component ' + tag + '.');
        return url;
      }
    };

    var knownComponents = [];
    var tags = [];

    var categories = {
      "basic" : [
         "ceci-button",
         "ceci-counter",
         "ceci-image",
         "ceci-header",
         "ceci-metronome"
      ],
      "layout" : [
        "ceci-button",
        "ceci-button-with-confirmation",
        "ceci-double-button",
        "ceci-header",
        "ceci-image",
        "ceci-spacer",
        "ceci-textbox"
      ],
      "connectors" : [
        "ceci-transformer",
        "ceci-channel-gate",
        "ceci-alternating-gate",
        "ceci-splitter",
        "ceci-combiner"
      ],
      "audio" : [
        "ceci-audio",
        "ceci-cowbell",
        "ceci-snaredrum",
        "ceci-kickdrum",
        "ceci-metronome",
        "ceci-microphone-button",
        "ceci-sequencer"
      ],
      "logic" : [
        "ceci-bool",
        "ceci-counter",
        "ceci-daily-counter",
        "ceci-random"
      ],
      "media" : [
        "ceci-chart",
        "ceci-component-canvas",
        "ceci-camera-button",
        "ceci-chat-window",
        "ceci-fireworks",
        "ceci-hot-potato",
        "ceci-jazzhands",
        "ceci-meatspaces-messages",
        "ceci-meatspaces-input",
        "ceci-notebook",
        "ceci-pad-grid",
        "ceci-text-input",
        "ceci-todo-list"
      ]
    }

    var categoryNames = [];

    for (var key in categories) {
      if (categories.hasOwnProperty(key)) {
        categoryNames.push(key);
      }
    }

    var DesignerTray = {
      buildItem : function(name){
        var item = document.createElement('designer-component-tray-item');
        var ceciDefinition = CeciDesigner.getCeciDefinitionScript(name);

        item.setAttribute('name', name);
        item.setAttribute('thumbnail', resolvePath(name, ceciDefinition.thumbnail));
        item.setAttribute('label', ceciDefinition.name || Util.prettyName(name));
        item.setAttribute('description', L10n.get(name + "/description") || ceciDefinition.description);
        item.setAttribute('author', ceciDefinition.author);
        item.setAttribute('updatedat', ceciDefinition.updatedAt);

        var bricktags = ceciDefinition.tags;

        for(var k = 0; k < bricktags.length; k++){
          var tag = bricktags[k];
          if(tags.indexOf(tag) == -1) {
            tags.push(tag);
          }
        }

        item.addEventListener('click', function (e) {
          var card = document.querySelector('ceci-card[visible]');
          if (card) {
            var newElement = document.createElement(name);
            // wait until Polymer has prepared the element completely
            newElement.async(function() {
              newElement.applyDefaults();
              card.appendChild(newElement);
              analytics.event("Added Component", {label: name});
            });
          }
        }, false);
        item.label = L10n.get(name) || item.label;
        return item;
      },
      addComponentWithName: function(name) {
        if(!DesignerTray.isKnownComponent(name)) {
          var added = false;
          // See if it goes in any categories
          for(var i = 0; i < categoryNames.length; i++){
            var category = categoryNames[i];

            var components = categories[category.toLowerCase()] || [];
            for(var j = 0; j < components.length; j++){
              if(name == components[j]){
                var item = DesignerTray.buildItem(name);
                DesignerTray.showCategory(category.toLowerCase());
                document.querySelector(".category-container." + category.toLowerCase()).appendChild(item);
                added = true;
              }
            }
          }
          //Means it wasn't in any categories
          if(!added){
            var item = DesignerTray.buildItem(name);
            DesignerTray.showCategory("other");
            document.querySelector(".category-container.other").appendChild(item);
          }
          knownComponents.push(name);
        }

      },
      sortComponents: function (container) {
        // Sorts components in each category container
        var containers = document.querySelectorAll('.category-container');
        for(var i = 0; i < containers.length; i++){
          var container = containers[i];
          var components = container.querySelectorAll('designer-component-tray-item').array();
          components = components.sort(function (a, b) {
            if (a.label > b.label) return 1;
            if (a.label < b.label) return -1;
            return 0;
          });
          components.forEach(function (c) {
            container.appendChild(c);
          });
        }
      },
      showCategory : function(category){
        var container = document.querySelector('.' + category.toLowerCase());
        var tag = document.querySelector('*[data-category="'+ category.toLowerCase() + '"]');
        container.classList.remove("hidden");
        tag.classList.remove("hidden");
      },
      buildCategory : function(category){
        var categoryContainer = document.querySelector("[data-category="+category.toLowerCase()+"]");

        //Check if this category container exists already
        if(!categoryContainer) {
          var that = this;

          if(category != "all") {
            //Build Category Container
            var container = document.createElement("div");
            container.classList.add("hidden");
            var title = document.createElement("h2");
            title.innerHTML = category.charAt(0).toUpperCase() + category.slice(1) + " Bricks";
            container.appendChild(title);
            container.classList.add("category-container");
            container.classList.add(category.toLowerCase());
            document.querySelector("#components").appendChild(container);
          }

          //Build tag
          var tagContainer = document.querySelector(".brick-category");
          var option = document.createElement("a");

          if(category == "all") {
            option.innerHTML = "All Bricks";
          } else {
            option.classList.add("hidden");
            option.innerHTML = category.charAt(0).toUpperCase() + category.slice(1);
          }

          option.setAttribute("data-category",category.toLowerCase());
          tagContainer.appendChild(option);

          option.addEventListener("click",function(e){
            that.filterCategory(e.target.getAttribute("data-category"));
          });
        }
      },
      addComponentsFromRegistry: function() {
        DesignerTray.buildCategory("all");
        for (var i = 0; i < categoryNames.length; i++) {
          DesignerTray.buildCategory(categoryNames[i]);
        }
        DesignerTray.buildCategory("other");
        CeciDesigner.forEachComponent(this.addComponentWithName);
        DesignerTray.filterCategory("basic");
        DesignerTray.sortComponents();
      },
      filterCategory : function(category){
        var categoryLinks = document.querySelectorAll(".brick-category a");
        Array.prototype.forEach.call(categoryLinks, function(el, i){
          el.classList.remove("selected-category");
        });

        document.querySelector(".brick-category a[data-category="+category+"]").classList.add("selected-category");
        document.querySelector("#components").setAttribute("data-category",category);

        var categoryEls = document.querySelectorAll(".category-container");
        if(category == "all"){
          Array.prototype.forEach.call(categoryEls, function(el, i){
            el.style.display = "";
          });
        } else {
          Array.prototype.forEach.call(categoryEls, function(el, i){
            el.style.display = "none";
          });
          document.querySelector(".category-container." + category).style.display = "";
        }
      },
      isKnownComponent: function(name) {
        return knownComponents.indexOf(name) > -1;
      },
      forgetComponent: function(name) {
        var pos = knownComponents.indexOf(name);
        if (pos > -1) {
          knownComponents.splice(pos, 1);
          var componentTrayContainer = document.getElementById('components');
          var item = componentTrayContainer.querySelector("[name='" + name + "']");
          item.parentNode.removeChild(item);
        }
      }
    };

    window.addEventListener("polymer-ready", function () {
      DesignerTray.addComponentsFromRegistry();
      var categoryContainers = document.querySelectorAll(".category-container");
      var searchBox = document.querySelector('.component-search');
      searchBox.addEventListener('keyup', function (e) {
        var searchValue = searchBox.value.trim().toLowerCase();
        var componentsContainer = document.querySelector('#components');
        if (searchValue.length > 0) {
          CeciDesigner.forEachComponent(function (componentTag) {
            var menuElement = componentsContainer.querySelector('designer-component-tray-item[name="' + componentTag + '"]');

            if (window.CeciDefinitions[componentTag] && menuElement) {
              var tags = window.CeciDefinitions[componentTag].tags || [];
              var found = false;

              var stringsToSearch = typeof tags === 'string' ? [tags] : tags;
              stringsToSearch.push(componentTag.toLowerCase());
              window.CeciDefinitions[componentTag].name && stringsToSearch.push(window.CeciDefinitions[componentTag].name.toLowerCase());
              window.CeciDefinitions[componentTag].description && stringsToSearch.push(window.CeciDefinitions[componentTag].description.toLowerCase());

              stringsToSearch.forEach(function (string) {
                found = found || string.toLowerCase().indexOf(searchValue) > -1;
              });

              if (!found) {
                menuElement.classList.add('hide');
              }
              else {
                menuElement.classList.remove('hide');
              }
            }
          });

          Array.prototype.forEach.call(categoryContainers, function(el, i){
            if(el.querySelectorAll("designer-component-tray-item:not(.hide)").length == 0) {
              el.classList.add("no-results");
            } else {
              el.classList.remove("no-results");
            }
          });

        }
        else {
          Array.prototype.forEach.call(categoryContainers, function(el, i){
            el.classList.remove("no-results");
          });
          Array.prototype.forEach.call(componentsContainer.querySelectorAll('designer-component-tray-item'), function (e) {
            e.classList.remove('hide');
          });
        }
      });
    });

    return DesignerTray;
  }
);
<|endoftext|>