<commit_before>(function (enyo) {

	//* @protected
	enyo.$ = {};

	enyo.dispatcher = {
		// these events come from document
		events: ["mousedown", "mouseup", "mouseover", "mouseout", "mousemove", "mousewheel",
			"click", "dblclick", "change", "keydown", "keyup", "keypress", "input",
			"paste", "copy", "cut", "webkitTransitionEnd", "transitionend", "webkitAnimationEnd", "animationEnd"],
		// these events come from window
		windowEvents: ["resize", "load", "unload", "message", "hashchange", "popstate"],
		// feature plugins (aka filters)
		features: [],
		connect: function() {
			var d = enyo.dispatcher, i, n;
			for (i=0; (n=d.events[i]); i++) {
				d.listen(document, n);
			}
			for (i=0; (n=d.windowEvents[i]); i++) {
				// Chrome Packaged Apps don't like "unload"
				if(n === "unload" &&
					(typeof window.chrome === "object") &&
					window.chrome.app) {
					continue;
				}

				d.listen(window, n);
			}
		},
		listen: function(inListener, inEventName, inHandler) {
			var d = enyo.dispatch;
			if (inListener.addEventListener) {
				this.listen = function(inListener, inEventName, inHandler) {
					inListener.addEventListener(inEventName, inHandler || d, false);
				};
			} else {
				//enyo.log("IE8 COMPAT: using 'attachEvent'");
				this.listen = function(inListener, inEvent, inHandler) {
					inListener.attachEvent("on" + inEvent, function(e) {
						e.target = e.srcElement;
						if (!e.preventDefault) {
							e.preventDefault = enyo.iePreventDefault;
						}
						return (inHandler || d)(e);
					});
				};
			}
			this.listen(inListener, inEventName, inHandler);
		},
		stopListening: function(inListener, inEventName, inHandler) {
			var d = enyo.dispatch;
			if (inListener.addEventListener) {
				this.stopListening = function(inListener, inEventName, inHandler) {
					inListener.removeEventListener(inEventName, inHandler || d, false);
				};
			} else {
				//enyo.log("IE8 COMPAT: using 'detachEvent'");
				this.stopListening = function(inListener, inEvent, inHandler) {
					inListener.detachEvent("on" + inEvent, inHandler || d);
				};
			}
			this.stopListening(inListener, inEventName, inHandler);
		},
		//* Fires an event for Enyo to listen for.
		dispatch: function(e) {
			// Find the control who maps to e.target, or the first control that maps to an ancestor of e.target.
			var c = this.findDispatchTarget(e.target) || this.findDefaultTarget();
			// Cache the original target
			e.dispatchTarget = c;
			// support pluggable features return true to abort immediately or set e.preventDispatch to avoid processing.
			for (var i=0, fn; (fn=this.features[i]); i++) {
				if (fn.call(this, e) === true) {
					return;
				}
			}
			if (c && !e.preventDispatch) {
				return this.dispatchBubble(e, c);
			}
		},
		//* Takes an event target and finds the corresponding Enyo control.
		findDispatchTarget: function(inNode) {
			var t, n = inNode;
			// FIXME: Mozilla: try/catch is here to squelch "Permission denied to access property xxx from a non-chrome context"
			// which appears to happen for scrollbar nodes in particular. It's unclear why those nodes are valid targets if
			// it is illegal to interrogate them. Would like to trap the bad nodes explicitly rather than using an exception block.
			try {
				while (n) {
					if ((t = enyo.$[n.id])) {
						// there could be multiple nodes with this id, the relevant node for this event is n
						// we don't push this directly to t.node because sometimes we are just asking what
						// the target 'would be' (aka, calling findDispatchTarget from handleMouseOverOut)
						t.eventNode = n;
						break;
					}
					n = n.parentNode;
				}
			} catch(x) {
				enyo.log(x, n);
			}
			return t;
		},
		//* Returns the default Enyo control for events.
		findDefaultTarget: function() {
			return enyo.master;
		},
		dispatchBubble: function(e, c) {
			var type = e.type;
			type = e.customEvent ? type : "on" + type;
			return c.bubble(type, e, c);
		},
		triggerHandler: function() {
			var args = Array.prototype.slice.call(arguments, 0),
				control = args.shift();
			return control.dispatchEvent(args);
		}
	};

	// Called in the context of an event
	enyo.iePreventDefault = function() {
		try {
			this.returnValue = false;
		}
		catch(e) {
			// do nothing
		}
	};

	enyo.dispatch = function(inEvent) {
		return enyo.dispatcher.dispatch(inEvent);
	};

	/**
		Triggers the handler for a given event type on the specified control.
		The _inControl_ parameter is the control object, and the _inEventType_ parameter is the event type string.
		The _inEvent_ and _inSender_ parameters are optional and represent event and sender objects, respectively,
		to pass to the handler. For example:

		enyo.triggerHandler(myControl, "ontap");
	*/
	enyo.triggerHandler = function(/* inControl, inEventType, [inEvent], [inSender] */) {
		return enyo.dispatcher.triggerHandler.apply(this, arguments);
	};

	enyo.bubble = function(inEvent) {
		// '|| window.event' clause needed for IE8
		var e = inEvent || window.event;
		if (e) {
			// We depend on e.target existing for event tracking and dispatching.
			if (!e.target) {
				e.target = e.srcElement;
			}
			enyo.dispatch(e);
		}
	};

	// This string is set on event handlers attributes for DOM elements that
	// don't normally bubble (like onscroll) so that they can participate in the
	// Enyo event system.
	enyo.bubbler = "enyo.bubble(arguments[0])";

	// The code below helps make Enyo compatible with Google Packaged Apps
	// Content Security Policy(http://developer.chrome.com/extensions/contentSecurityPolicy.html),
	// which, among other things, forbids the use of inline scripts.
	// We replace online scripting with equivalent means, leaving enyo.bubbler
	// for backward compatibility.
	(function() {
		var bubbleUp = function() {
			enyo.bubble(arguments[0]);
		};

		/**
			Makes given events bubble on a specified Enyo control.
		*/
		enyo.makeBubble = function() {
			var args = Array.prototype.slice.call(arguments, 0),
				control = args.shift();

			if((typeof control === "object") && (typeof control.hasNode === "function")) {
				enyo.forEach(args, function(event) {
					if(this.hasNode()) {
						enyo.dispatcher.listen(this.node, event, bubbleUp);
					}
				}, control);
			}
		};
		/**
			Removes the event listening and bubbling initiated by _enyo.makeBubble()_
			on a specific control.
		 */
		enyo.unmakeBubble = function() {
			var args = Array.prototype.slice.call(arguments, 0),
				control = args.shift();

			if((typeof control === "object") && (typeof control.hasNode === "function")) {
				enyo.forEach(args, function(event) {
					if(this.hasNode()) {
						enyo.dispatcher.stopListening(this.node, event, bubbleUp);
					}
				}, control);
			}
		};
	})();

	// FIXME: we need to create and initialize dispatcher someplace else to allow overrides
	enyo.requiresWindow(enyo.dispatcher.connect);

	// generate a tapped event for a raw-click event
	enyo.dispatcher.features.push(
		function (e) {
			if ("click" === e.type) {
				if (e.clientX === 0 && e.clientY === 0) {
					// this allows the click to dispatch as well
					// but note the tap event will fire first
					var cp = enyo.clone(e);
					cp.type = "tap";
					cp.preventDefault = enyo.nop;
					enyo.dispatch(cp);
				}
			}
		}
	);

	/**
		Instead of having multiple _features_ pushed and handled in separate methods
		for these events we will handle them uniformly here to expose as accurate as
		possible the last known interaction coordinates.
	*/
	var _xy = {};
	enyo.dispatcher.features.push(
		function (e) {
			if (
				(e.type == "mousemove")  ||
				(e.type == "tap")        ||
				(e.type == "click")      ||
				(e.type == "touchmove")
			) {
				_xy.clientX = e.clientX;
				_xy.clientY = e.clientY;
				// note only ie8 does not support pageX/pageY
				_xy.pageX   = e.pageX;
				_xy.pageY   = e.pageY;
				// note ie8 and opera report these values incorrectly
				_xy.screenX = e.screenX;
				_xy.screenY = e.screenY;
			}
		}
	);
	//*@public
	/**
		Retrieve the last known coordinates of the cursor or user-interaction point
		in _touch_ environments. Returns an immutable object with the _clientX, clientY_,
		_pageX, pageY_ and _screenX, screenY_ properties. It is important to note that
		IE 8 and Opera have improper reporting for the _screenX, screenY_
		properties (they both use CSS pixels as opposed to device pixels) and IE8 has no
		support for the _pageX, pageY_ properties so they are facaded.
	*/
	enyo.getPosition = function () {
		var p = enyo.clone(_xy);
		// if we are in ie8 we facade the _pageX, pageY_ properties
		if (enyo.platform.ie < 9) {
			var d = (document.documentElement || document.body.parentNode || document.body);
			p.pageX = (p.clientX + d.scrollLeft);
			p.pageY = (p.clientY + d.scrollTop);
		}
		return p;
	};

})(enyo);
<commit_msg>ENYO-3928: Use enyo.cloneArray instead of Array slice.<commit_after>(function (enyo) {

	//* @protected
	enyo.$ = {};

	enyo.dispatcher = {
		// these events come from document
		events: ["mousedown", "mouseup", "mouseover", "mouseout", "mousemove", "mousewheel",
			"click", "dblclick", "change", "keydown", "keyup", "keypress", "input",
			"paste", "copy", "cut", "webkitTransitionEnd", "transitionend", "webkitAnimationEnd", "animationEnd"],
		// these events come from window
		windowEvents: ["resize", "load", "unload", "message", "hashchange", "popstate"],
		// feature plugins (aka filters)
		features: [],
		connect: function() {
			var d = enyo.dispatcher, i, n;
			for (i=0; (n=d.events[i]); i++) {
				d.listen(document, n);
			}
			for (i=0; (n=d.windowEvents[i]); i++) {
				// Chrome Packaged Apps don't like "unload"
				if(n === "unload" &&
					(typeof window.chrome === "object") &&
					window.chrome.app) {
					continue;
				}

				d.listen(window, n);
			}
		},
		listen: function(inListener, inEventName, inHandler) {
			var d = enyo.dispatch;
			if (inListener.addEventListener) {
				this.listen = function(inListener, inEventName, inHandler) {
					inListener.addEventListener(inEventName, inHandler || d, false);
				};
			} else {
				//enyo.log("IE8 COMPAT: using 'attachEvent'");
				this.listen = function(inListener, inEvent, inHandler) {
					inListener.attachEvent("on" + inEvent, function(e) {
						e.target = e.srcElement;
						if (!e.preventDefault) {
							e.preventDefault = enyo.iePreventDefault;
						}
						return (inHandler || d)(e);
					});
				};
			}
			this.listen(inListener, inEventName, inHandler);
		},
		stopListening: function(inListener, inEventName, inHandler) {
			var d = enyo.dispatch;
			if (inListener.addEventListener) {
				this.stopListening = function(inListener, inEventName, inHandler) {
					inListener.removeEventListener(inEventName, inHandler || d, false);
				};
			} else {
				//enyo.log("IE8 COMPAT: using 'detachEvent'");
				this.stopListening = function(inListener, inEvent, inHandler) {
					inListener.detachEvent("on" + inEvent, inHandler || d);
				};
			}
			this.stopListening(inListener, inEventName, inHandler);
		},
		//* Fires an event for Enyo to listen for.
		dispatch: function(e) {
			// Find the control who maps to e.target, or the first control that maps to an ancestor of e.target.
			var c = this.findDispatchTarget(e.target) || this.findDefaultTarget();
			// Cache the original target
			e.dispatchTarget = c;
			// support pluggable features return true to abort immediately or set e.preventDispatch to avoid processing.
			for (var i=0, fn; (fn=this.features[i]); i++) {
				if (fn.call(this, e) === true) {
					return;
				}
			}
			if (c && !e.preventDispatch) {
				return this.dispatchBubble(e, c);
			}
		},
		//* Takes an event target and finds the corresponding Enyo control.
		findDispatchTarget: function(inNode) {
			var t, n = inNode;
			// FIXME: Mozilla: try/catch is here to squelch "Permission denied to access property xxx from a non-chrome context"
			// which appears to happen for scrollbar nodes in particular. It's unclear why those nodes are valid targets if
			// it is illegal to interrogate them. Would like to trap the bad nodes explicitly rather than using an exception block.
			try {
				while (n) {
					if ((t = enyo.$[n.id])) {
						// there could be multiple nodes with this id, the relevant node for this event is n
						// we don't push this directly to t.node because sometimes we are just asking what
						// the target 'would be' (aka, calling findDispatchTarget from handleMouseOverOut)
						t.eventNode = n;
						break;
					}
					n = n.parentNode;
				}
			} catch(x) {
				enyo.log(x, n);
			}
			return t;
		},
		//* Returns the default Enyo control for events.
		findDefaultTarget: function() {
			return enyo.master;
		},
		dispatchBubble: function(e, c) {
			var type = e.type;
			type = e.customEvent ? type : "on" + type;
			return c.bubble(type, e, c);
		},
		triggerHandler: function() {
			var args = enyo.cloneArray(arguments),
				control = args.shift();
			return control.dispatchEvent(args);
		}
	};

	// Called in the context of an event
	enyo.iePreventDefault = function() {
		try {
			this.returnValue = false;
		}
		catch(e) {
			// do nothing
		}
	};

	enyo.dispatch = function(inEvent) {
		return enyo.dispatcher.dispatch(inEvent);
	};

	/**
		Triggers the handler for a given event type on the specified control.
		The _inControl_ parameter is the control object, and the _inEventType_ parameter is the event type string.
		The _inEvent_ and _inSender_ parameters are optional and represent event and sender objects, respectively,
		to pass to the handler. For example:

		enyo.triggerHandler(myControl, "ontap");
	*/
	enyo.triggerHandler = function(/* inControl, inEventType, [inEvent], [inSender] */) {
		return enyo.dispatcher.triggerHandler.apply(this, arguments);
	};

	enyo.bubble = function(inEvent) {
		// '|| window.event' clause needed for IE8
		var e = inEvent || window.event;
		if (e) {
			// We depend on e.target existing for event tracking and dispatching.
			if (!e.target) {
				e.target = e.srcElement;
			}
			enyo.dispatch(e);
		}
	};

	// This string is set on event handlers attributes for DOM elements that
	// don't normally bubble (like onscroll) so that they can participate in the
	// Enyo event system.
	enyo.bubbler = "enyo.bubble(arguments[0])";

	// The code below helps make Enyo compatible with Google Packaged Apps
	// Content Security Policy(http://developer.chrome.com/extensions/contentSecurityPolicy.html),
	// which, among other things, forbids the use of inline scripts.
	// We replace online scripting with equivalent means, leaving enyo.bubbler
	// for backward compatibility.
	(function() {
		var bubbleUp = function() {
			enyo.bubble(arguments[0]);
		};

		/**
			Makes given events bubble on a specified Enyo control.
		*/
		enyo.makeBubble = function() {
			var args = Array.prototype.slice.call(arguments, 0),
				control = args.shift();

			if((typeof control === "object") && (typeof control.hasNode === "function")) {
				enyo.forEach(args, function(event) {
					if(this.hasNode()) {
						enyo.dispatcher.listen(this.node, event, bubbleUp);
					}
				}, control);
			}
		};
		/**
			Removes the event listening and bubbling initiated by _enyo.makeBubble()_
			on a specific control.
		 */
		enyo.unmakeBubble = function() {
			var args = Array.prototype.slice.call(arguments, 0),
				control = args.shift();

			if((typeof control === "object") && (typeof control.hasNode === "function")) {
				enyo.forEach(args, function(event) {
					if(this.hasNode()) {
						enyo.dispatcher.stopListening(this.node, event, bubbleUp);
					}
				}, control);
			}
		};
	})();

	// FIXME: we need to create and initialize dispatcher someplace else to allow overrides
	enyo.requiresWindow(enyo.dispatcher.connect);

	// generate a tapped event for a raw-click event
	enyo.dispatcher.features.push(
		function (e) {
			if ("click" === e.type) {
				if (e.clientX === 0 && e.clientY === 0) {
					// this allows the click to dispatch as well
					// but note the tap event will fire first
					var cp = enyo.clone(e);
					cp.type = "tap";
					cp.preventDefault = enyo.nop;
					enyo.dispatch(cp);
				}
			}
		}
	);

	/**
		Instead of having multiple _features_ pushed and handled in separate methods
		for these events we will handle them uniformly here to expose as accurate as
		possible the last known interaction coordinates.
	*/
	var _xy = {};
	enyo.dispatcher.features.push(
		function (e) {
			if (
				(e.type == "mousemove")  ||
				(e.type == "tap")        ||
				(e.type == "click")      ||
				(e.type == "touchmove")
			) {
				_xy.clientX = e.clientX;
				_xy.clientY = e.clientY;
				// note only ie8 does not support pageX/pageY
				_xy.pageX   = e.pageX;
				_xy.pageY   = e.pageY;
				// note ie8 and opera report these values incorrectly
				_xy.screenX = e.screenX;
				_xy.screenY = e.screenY;
			}
		}
	);
	//*@public
	/**
		Retrieve the last known coordinates of the cursor or user-interaction point
		in _touch_ environments. Returns an immutable object with the _clientX, clientY_,
		_pageX, pageY_ and _screenX, screenY_ properties. It is important to note that
		IE 8 and Opera have improper reporting for the _screenX, screenY_
		properties (they both use CSS pixels as opposed to device pixels) and IE8 has no
		support for the _pageX, pageY_ properties so they are facaded.
	*/
	enyo.getPosition = function () {
		var p = enyo.clone(_xy);
		// if we are in ie8 we facade the _pageX, pageY_ properties
		if (enyo.platform.ie < 9) {
			var d = (document.documentElement || document.body.parentNode || document.body);
			p.pageX = (p.clientX + d.scrollLeft);
			p.pageY = (p.clientY + d.scrollTop);
		}
		return p;
	};

})(enyo);
<|endoftext|>


<commit_before>var pulsar = require('../package.json');
var p5 = require('p5');

pulsar.config = pulsarConfig;
pulsarConfig = undefined;

pulsar.sketch = function (p) {

  var Receiver = require('./Receiver.js');
  var receiver = new Receiver();

  var Processor = require('./Processor.js');
  var processor = new Processor();

  var DrawingManager = require('./DrawingManager.js');
  var dM = new DrawingManager(p);

  var versionTag = pulsar.name.toUpperCase() + " - v" + pulsar.version;

  p.setup = function() {
    p.frameRate(30);
    receiver.connect();

    receiver.on('received', function(data) {
      console.log("Pulsar: received: " + data);

      var drawing = processor.createDrawing(data, pulsar.config);

      dM.add(drawing);
      // give to drawing manager
    })

    p.createCanvas(p.windowWidth, p.windowHeight);
  }

  p.draw = function() {
    p.noStroke();
    p.background(0);

    dM.drawAll();

    p.textSize(15);
    p.fill(175);
    var verWidth = p.textWidth(versionTag);
    p.text(versionTag, p.windowWidth - verWidth - 10, p.windowHeight - 10);
  }

  p.windowResized = function() {
    p.resizeCanvas(p.windowWidth, p.windowHeight);
  }
}

new p5(pulsar.sketch);<commit_msg>Pass control pulses to the processor<commit_after>var pulsar = require('../package.json');
var p5 = require('p5');

pulsar.config = pulsarConfig;
pulsarConfig = undefined;

pulsar.sketch = function (p) {

  var Receiver = require('./Receiver.js');
  var receiver = new Receiver();

  var Processor = require('./Processor.js');
  var processor = new Processor();

  var DrawingManager = require('./DrawingManager.js');
  var dM = new DrawingManager(p);

  var versionTag = pulsar.name.toUpperCase() + " - v" + pulsar.version;

  p.setup = function() {
    p.frameRate(30);
    receiver.connect();

    receiver.on('pulse', function(data) {
      console.log("Pulsar: received: " + data);
      var drawing = processor.createDrawing(data, pulsar.config);
      dM.add(drawing);
    });

    receiver.on('pulsar control', function(data) {
      console.log("Pulsar: received: " + data);
      processor.processControl(data, pulsar.config);
    });

    p.createCanvas(p.windowWidth, p.windowHeight);
  }

  p.draw = function() {
    p.noStroke();
    p.background(0);

    dM.drawAll();

    p.textSize(15);
    p.fill(175);
    var verWidth = p.textWidth(versionTag);
    p.text(versionTag, p.windowWidth - verWidth - 10, p.windowHeight - 10);
  }

  p.windowResized = function() {
    p.resizeCanvas(p.windowWidth, p.windowHeight);
  }
}

new p5(pulsar.sketch);<|endoftext|>


<commit_before>$("#char-search").on('change keydown paste input', function(){
    search=document.getElementById('char-search').value.toLowerCase()
    if (search == ''){
        chars=document.getElementsByClassName('char_band');
        for (var c =0; c<chars.length; c++){
            chars[c].style.display = 'block'
        }
    }else{
        chars=document.getElementsByClassName('char_band');
        for (var c =0; c<chars.length; c++){
            if (chars[c].childNodes[0].innerHTML.toLowerCase().match(search)==null){
                chars[c].style.display = 'none'
            }else{
                chars[c].style.display = 'block'
            }
        }
    }

})

$('img').click(function(){
   $('.selected').removeClass('selected'); // removes the previous selected class
   $(this).addClass('selected'); // adds the class to the clicked image
});

function submit(){
    obj = getSessionObject()
    donation = obj['allocationAmounts']
    charities = Object.keys(donation).sort()
    modal_msg = document.getElementById('donation_summary')
    sub_list = document.createElement('ul')
    sub_list.style.listStyleType= 'none'
    total = document.createElement('li')
    total.innerHTML = "TOTAL DONATION: $" + (obj['totalFunds']*obj['percentAllocated'])/100.0.toFixed(2)
    sub_list.appendChild(total)
    for (var i=0; i<charities.length; i++){
        ul = document.createElement('li')
        ul.innerHTML = charities[i] + ": $" + donation[charities[i]]*60/100.0.toFixed(2)
        sub_list.appendChild(ul)
    }
    modal_msg.appendChild(sub_list)

    var modal = document.getElementById("submitModal");
    modal.style.display = "block";

  var modalCloseButton = document.getElementById("subCloseButton");
  modalCloseButton.onclick = function(){
    modal.style.display = "none";
    obj = getSessionObject()
    obj['allocationAmounts'] = {}
    obj['percentAllocated'] = 0
    setSessionObject(obj)
  };

  // Click anywhere outside of modal causes it to close
  window.onclick = function(event){
    if (event.target == modal){
      modal.style.display = "none";
      obj = getSessionObject()
      obj['allocationAmounts'] = {}
      obj['percentAllocated'] = 0
      setSessionObject(obj)
      window.location.href = "./index.html";
    }
  }
}

function makeCharList() {
  var obj = getSessionObject();
  var savedCharities = obj["savedCharities"];
  var charityDetails = getCharityDetails();
  var idx_name_map = {}

  for (var sc = 0; sc < savedCharities.length; sc++){
    text = charityDetails[savedCharities[sc]].name;
    idx_name_map[text] = sc;
    if (!(text in obj['allocationAmounts'])){
        obj['allocationAmounts'][text] = 0;
    }
  }

    my_charities = obj['allocationAmounts']

    // Create the list element:
    charities = Object.keys(my_charities).sort()
    var colorRange = d3.scale.category20();
    var color = d3.scale.ordinal()
    .range(colorRange.range());
    fam_charities = ['Dave Thomas Foundation', 'Friends of Earth', 'Rainforest Alliance', 'Homes For Our Troops']
    for(var c = 0; c < fam_charities.length; c++) {
        color(fam_charities[c])
    }

    list_header = document.createTextNode('My Charities (' + charities.length + ')')
    document.getElementById('My_Charities').appendChild(list_header)

    var list = document.createElement('div');
    document.getElementById('charity_list').appendChild(list);

    var char_num_chars = document.createElement('span');
    char_num_chars.className ='num_chars'
    var no_char_msg = document.createTextNode('You Have Not Selected Any Charities Yet');
    char_num_chars.appendChild(no_char_msg);
    if (charities.length == 0){
        char_num_chars.style.display = 'block'
    }else{
        char_num_chars.style.display = 'none'
    }
    list.appendChild(char_num_chars);

    for(var i = 0; i < charities.length; i++) {
        // Create the list item:
        var char_band = document.createElement('div');
        char_band.className = "char_band";
        char_band.style.background = "linear-gradient(to right,"+color(charities[i])+" 2%,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA)";
        var outer_div = document.createElement('div');
        outer_div.className ='outer_div';
        list.appendChild(outer_div);
        var char_content_name = document.createElement('div');
        char_content_name.className ='char_name';
        var char_name = document.createTextNode(charities[i]);
        char_content_name.appendChild(char_name);
        char_band.appendChild(char_content_name);
        outer_div.appendChild(char_band)

        var slider = document.createElement('div')
        slider.className="sliders"
        slider.setAttribute('id', 'slider_'+i)
        char_band.appendChild(slider);
        init_obj = getSessionObject()['allocationAmounts'][charities[i]]
       $('#slider_'+i).slider({
            step: 1,
            min: 0,
            max: 100,
            value: init_obj,
            slide: function( event, ui ) {
                var amount = ui.value;
                char_name = this.parentNode.childNodes[0].innerHTML
                my_charities[char_name] = amount
                money = 100 - Object.values(my_charities).reduce(function(a,b){return a+b;},0)
                obj_slide = getSessionObject()
                char_error = document.getElementsByClassName('error_msg')[0]
                if (money < 0){
                    char_error.style.visibility = 'visible'
                    my_charities[char_name] += money;
                    obj_slide['allocationAmounts'] = my_charities;
                    obj_slide['percentAllocated'] = 100;
                    setSessionObject(obj_slide);
                    this.parentNode.childNodes[2].innerHTML = my_charities[char_name]+'%';
                    is.parentNode.childNodes[2].innerHTML = my_charities[char_name]+'%';
                    $('#slider_'+i).slider('value', my_charities[char_name]);//$('#'+this.getAttribute('id')).slider('value', my_charities[char_name]);
                    prog_bar = document.getElementsByClassName('progress-bar')[0]
                    prog_bar.setAttribute('aria-valuenow', obj_slide['percentAllocated'])
                    prog_bar.setAttribute('style', "width:"+obj_slide['percentAllocated']+"%")
                    prog_bar.innerHTML = obj_slide['percentAllocated']+'%'
                    return false;
                }else{
                    char_error.style.visibility = 'hidden'
                    my_charities[char_name] = amount
                    obj_slide['allocationAmounts'] = my_charities;
                    obj_slide['percentAllocated'] = Object.values(my_charities).reduce(function(a,b){return a+b;},0);
                    setSessionObject(obj_slide);
                    this.parentNode.childNodes[2].innerHTML = amount+'%';
                    prog_bar = document.getElementsByClassName('progress-bar')[0]
                    prog_bar.setAttribute('aria-valuenow', obj_slide['percentAllocated'])
                    prog_bar.setAttribute('style', "width:"+obj_slide['percentAllocated']+"%")
                    prog_bar.innerHTML = obj_slide['percentAllocated']+'%'
                }
                update_pie(char_name, my_charities[char_name])

                document.getElementById('funds').innerHTML = '$'+(obj_slide['percentAllocated']/100.0*obj_slide['totalFunds']).toFixed(2)+'/$'+obj_slide['totalFunds']+' allocated'
                $("#"+this.getAttribute('id') +" .ui-slider-range").css( "background-color", '#C43E00' );

                $( "#"+this.getAttribute('id') +" .ui-state-default, .ui-widget-content .ui-state-default" ).css( "background-color", '#C43E00' );
            }
        });

        var char_content_value = document.createElement('span');
        char_content_value.className ='alloc_val'
        var char_value = document.createTextNode(my_charities[charities[i]]+'%');
        char_content_value.appendChild(char_value);
        char_band.appendChild(char_content_value);

        //creates trash can remove buttons
        var button = document.createElement("button");
        button.setAttribute('class', 'trash');
        var trash = document.createElement("span");
        trash.setAttribute('class', 'glyphicon glyphicon-trash');
        button.appendChild(trash)
        button.onclick = function(){
            obj_del = getSessionObject()
            obj_del['percentAllocated'] -= my_charities[this.parentNode.childNodes[0].childNodes[0].nodeValue]
            delete my_charities[this.parentNode.childNodes[0].childNodes[0].nodeValue]
            var index = obj_del['savedCharities'].indexOf(idx_name_map[this.parentNode.childNodes[0].childNodes[0].nodeValue])
            if (index > -1) {
                obj_del['savedCharities'].splice(index, 1);
            }
            obj_del['allocationAmounts'] = my_charities;
            setSessionObject(obj_del);
            update_pie(this.parentNode.childNodes[0].childNodes[0].nodeValue, 0);
            this.parentNode.parentNode.removeChild(this.parentNode);
            document.getElementById("My_Charities").childNodes[0].nodeValue = 'My Charities (' + Object.keys(my_charities).length + ')'
            total_alloc = Object.values(my_charities).reduce(function(a,b){return a+b;},0);

            prog_bar = document.getElementsByClassName('progress-bar')[0]
            prog_bar.setAttribute('aria-valuenow', total_alloc)
            prog_bar.setAttribute('style', "width:"+total_alloc+"%")
            prog_bar.innerHTML = total_alloc+'%'
        }
        char_band.appendChild(button);

        // Add it to the list:
        //list.appendChild(char_band);
    }
    /*
    var char_error = document.createElement('span');
    char_error.className ='error_msg'
    var msg = document.createTextNode('You have allocated 100% of your donation budget. Press Donate to proceed with your donation or reallocate your funds by reducing one or more of your current donation amounts.');
    char_error.appendChild(msg);
    char_error.style.visibility = 'hidden'
    char_error.style.color= '#C43E00'
    list.appendChild(char_error);
    */

    // Finally, return the constructed list:
    return list;
}

function makeFamCharList() {
    /*var obj = getSessionObject();
  var savedCharities = obj["savedCharities"];
  var charityDetails = getCharityDetails();

  for (var sc = 0; sc < savedCharities.length; sc++){
    text = charityDetails[savedCharities[sc]].name;
    if (!(text in obj['allocationAmounts'])){
        obj['allocationAmounts'][text] = 0;
    }

    my_charities = obj['allocationAmounts']*/

    // Create the list element:
    charities = ['Dave Thomas Foundation', 'Friends of Earth', 'Rainforest Alliance', 'Homes For Our Troops']

    var colorRange = d3.scale.category20();
    var color = d3.scale.ordinal()
    .range(colorRange.range());

    var list = document.createElement('div');
    document.getElementById('family_charity_list').appendChild(list);

    for(var i = 0; i < charities.length; i++) {
        // Create the list item:
        var char_band = document.createElement('div');
        char_band.className = "char_band";
        char_band.setAttribute('id', 'cb'+i);
        col = color(charities[i]);
        //char_band.style.background = "linear-gradient(to right,"+col+"  0%,#EAEAEA 100%)"
        char_band.style.background = "linear-gradient(to right,"+col+" 2%,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF"
        var outer_div = document.createElement('div');
        outer_div.className ='outer_div';
        list.appendChild(outer_div);
        var legend = document.createElement('div');
        legend.className ='legend';
        legend.style.backgroundImage = color(charities[i]);
        //char_band.appendChild(legend);
        var char_content_name = document.createElement('div');
        char_content_name.className ='char_name';
        var char_name = document.createTextNode(charities[i]);
        char_content_name.appendChild(char_name);
        char_band.appendChild(char_content_name);
        outer_div.appendChild(char_band)
        }
    return list;
}
<commit_msg>testing with session data<commit_after>$("#char-search").on('change keydown paste input', function(){
    search=document.getElementById('char-search').value.toLowerCase()
    if (search == ''){
        chars=document.getElementsByClassName('char_band');
        for (var c =0; c<chars.length; c++){
            chars[c].style.display = 'block'
        }
    }else{
        chars=document.getElementsByClassName('char_band');
        for (var c =0; c<chars.length; c++){
            if (chars[c].childNodes[0].innerHTML.toLowerCase().match(search)==null){
                chars[c].style.display = 'none'
            }else{
                chars[c].style.display = 'block'
            }
        }
    }

})

$('img').click(function(){
   $('.selected').removeClass('selected'); // removes the previous selected class
   $(this).addClass('selected'); // adds the class to the clicked image
});

function submit(){
    obj = getSessionObject()
    donation = obj['allocationAmounts']
    charities = Object.keys(donation).sort()
    modal_msg = document.getElementById('donation_summary')
    sub_list = document.createElement('ul')
    sub_list.style.listStyleType= 'none'
    total = document.createElement('li')
    total.innerHTML = "TOTAL DONATION: $" + (obj['totalFunds']*obj['percentAllocated'])/100.0.toFixed(2)
    sub_list.appendChild(total)
    for (var i=0; i<charities.length; i++){
        ul = document.createElement('li')
        ul.innerHTML = charities[i] + ": $" + donation[charities[i]]*60/100.0.toFixed(2)
        sub_list.appendChild(ul)
    }
    modal_msg.appendChild(sub_list)

    var modal = document.getElementById("submitModal");
    modal.style.display = "block";

  var modalCloseButton = document.getElementById("subCloseButton");
  modalCloseButton.onclick = function(){
    modal.style.display = "none";
    obj = getSessionObject()
    obj['allocationAmounts'] = {}
    obj['percentAllocated'] = 0
    setSessionObject(obj)
  };

  // Click anywhere outside of modal causes it to close
  window.onclick = function(event){
    if (event.target == modal){
      modal.style.display = "none";
      obj = getSessionObject()
      obj['allocationAmounts'] = {}
      obj['percentAllocated'] = 0
      setSessionObject(obj)
      window.location.href = "./index.html";
    }
  }
}

function makeCharList() {
  var obj = getSessionObject();
  var savedCharities = obj["savedCharities"];
  var charityDetails = getCharityDetails();
  var idx_name_map = {}
  var new_list = {}

  for (var sc = 0; sc < savedCharities.length; sc++){
    text = charityDetails[savedCharities[sc]].name;
    idx_name_map[text] = sc;
    if (!(text in obj['allocationAmounts'])){
        new_list[text] = 0;
    }else{
        new_list[text] = obj['allocationAmounts'][text]
    }
  }
  obj['allocationAmounts'] = new_list

    my_charities = obj['allocationAmounts']

    // Create the list element:
    charities = Object.keys(my_charities).sort()
    var colorRange = d3.scale.category20();
    var color = d3.scale.ordinal()
    .range(colorRange.range());
    fam_charities = ['Dave Thomas Foundation', 'Friends of Earth', 'Rainforest Alliance', 'Homes For Our Troops']
    for(var c = 0; c < fam_charities.length; c++) {
        color(fam_charities[c])
    }

    list_header = document.createTextNode('My Charities (' + charities.length + ')')
    document.getElementById('My_Charities').appendChild(list_header)

    var list = document.createElement('div');
    document.getElementById('charity_list').appendChild(list);

    var char_num_chars = document.createElement('span');
    char_num_chars.className ='num_chars'
    var no_char_msg = document.createTextNode('You Have Not Selected Any Charities Yet');
    char_num_chars.appendChild(no_char_msg);
    if (charities.length == 0){
        char_num_chars.style.display = 'block'
    }else{
        char_num_chars.style.display = 'none'
    }
    list.appendChild(char_num_chars);

    for(var i = 0; i < charities.length; i++) {
        // Create the list item:
        var char_band = document.createElement('div');
        char_band.className = "char_band";
        char_band.style.background = "linear-gradient(to right,"+color(charities[i])+" 2%,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA,#EAEAEA)";
        var outer_div = document.createElement('div');
        outer_div.className ='outer_div';
        list.appendChild(outer_div);
        var char_content_name = document.createElement('div');
        char_content_name.className ='char_name';
        var char_name = document.createTextNode(charities[i]);
        char_content_name.appendChild(char_name);
        char_band.appendChild(char_content_name);
        outer_div.appendChild(char_band)

        var slider = document.createElement('div')
        slider.className="sliders"
        slider.setAttribute('id', 'slider_'+i)
        char_band.appendChild(slider);
        init_obj = getSessionObject()['allocationAmounts'][charities[i]]
       $('#slider_'+i).slider({
            step: 1,
            min: 0,
            max: 100,
            value: init_obj,
            slide: function( event, ui ) {
                var amount = ui.value;
                char_name = this.parentNode.childNodes[0].innerHTML
                my_charities[char_name] = amount
                money = 100 - Object.values(my_charities).reduce(function(a,b){return a+b;},0)
                obj_slide = getSessionObject()
                char_error = document.getElementsByClassName('error_msg')[0]
                if (money < 0){
                    char_error.style.visibility = 'visible'
                    my_charities[char_name] += money;
                    obj_slide['allocationAmounts'] = my_charities;
                    obj_slide['percentAllocated'] = 100;
                    setSessionObject(obj_slide);
                    this.parentNode.childNodes[2].innerHTML = my_charities[char_name]+'%';
                    is.parentNode.childNodes[2].innerHTML = my_charities[char_name]+'%';
                    $('#slider_'+i).slider('value', my_charities[char_name]);//$('#'+this.getAttribute('id')).slider('value', my_charities[char_name]);
                    prog_bar = document.getElementsByClassName('progress-bar')[0]
                    prog_bar.setAttribute('aria-valuenow', obj_slide['percentAllocated'])
                    prog_bar.setAttribute('style', "width:"+obj_slide['percentAllocated']+"%")
                    prog_bar.innerHTML = obj_slide['percentAllocated']+'%'
                    return false;
                }else{
                    char_error.style.visibility = 'hidden'
                    my_charities[char_name] = amount
                    obj_slide['allocationAmounts'] = my_charities;
                    obj_slide['percentAllocated'] = Object.values(my_charities).reduce(function(a,b){return a+b;},0);
                    setSessionObject(obj_slide);
                    this.parentNode.childNodes[2].innerHTML = amount+'%';
                    prog_bar = document.getElementsByClassName('progress-bar')[0]
                    prog_bar.setAttribute('aria-valuenow', obj_slide['percentAllocated'])
                    prog_bar.setAttribute('style', "width:"+obj_slide['percentAllocated']+"%")
                    prog_bar.innerHTML = obj_slide['percentAllocated']+'%'
                }
                update_pie(char_name, my_charities[char_name])

                document.getElementById('funds').innerHTML = '$'+(obj_slide['percentAllocated']/100.0*obj_slide['totalFunds']).toFixed(2)+'/$'+obj_slide['totalFunds']+' allocated'
                $("#"+this.getAttribute('id') +" .ui-slider-range").css( "background-color", '#C43E00' );

                $( "#"+this.getAttribute('id') +" .ui-state-default, .ui-widget-content .ui-state-default" ).css( "background-color", '#C43E00' );
            }
        });

        var char_content_value = document.createElement('span');
        char_content_value.className ='alloc_val'
        var char_value = document.createTextNode(my_charities[charities[i]]+'%');
        char_content_value.appendChild(char_value);
        char_band.appendChild(char_content_value);

        //creates trash can remove buttons
        var button = document.createElement("button");
        button.setAttribute('class', 'trash');
        var trash = document.createElement("span");
        trash.setAttribute('class', 'glyphicon glyphicon-trash');
        button.appendChild(trash)
        button.onclick = function(){
            obj_del = getSessionObject()
            obj_del['percentAllocated'] -= my_charities[this.parentNode.childNodes[0].childNodes[0].nodeValue]
            delete my_charities[this.parentNode.childNodes[0].childNodes[0].nodeValue]
            var index = obj_del['savedCharities'].indexOf(idx_name_map[this.parentNode.childNodes[0].childNodes[0].nodeValue])
            if (index > -1) {
                obj_del['savedCharities'].splice(index, 1);
            }
            obj_del['allocationAmounts'] = my_charities;
            setSessionObject(obj_del);
            update_pie(this.parentNode.childNodes[0].childNodes[0].nodeValue, 0);
            this.parentNode.parentNode.removeChild(this.parentNode);
            document.getElementById("My_Charities").childNodes[0].nodeValue = 'My Charities (' + Object.keys(my_charities).length + ')'
            total_alloc = Object.values(my_charities).reduce(function(a,b){return a+b;},0);

            prog_bar = document.getElementsByClassName('progress-bar')[0]
            prog_bar.setAttribute('aria-valuenow', total_alloc)
            prog_bar.setAttribute('style', "width:"+total_alloc+"%")
            prog_bar.innerHTML = total_alloc+'%'
        }
        char_band.appendChild(button);

        // Add it to the list:
        //list.appendChild(char_band);
    }
    /*
    var char_error = document.createElement('span');
    char_error.className ='error_msg'
    var msg = document.createTextNode('You have allocated 100% of your donation budget. Press Donate to proceed with your donation or reallocate your funds by reducing one or more of your current donation amounts.');
    char_error.appendChild(msg);
    char_error.style.visibility = 'hidden'
    char_error.style.color= '#C43E00'
    list.appendChild(char_error);
    */

    // Finally, return the constructed list:
    return list;
}

function makeFamCharList() {
    /*var obj = getSessionObject();
  var savedCharities = obj["savedCharities"];
  var charityDetails = getCharityDetails();

  for (var sc = 0; sc < savedCharities.length; sc++){
    text = charityDetails[savedCharities[sc]].name;
    if (!(text in obj['allocationAmounts'])){
        obj['allocationAmounts'][text] = 0;
    }

    my_charities = obj['allocationAmounts']*/

    // Create the list element:
    charities = ['Dave Thomas Foundation', 'Friends of Earth', 'Rainforest Alliance', 'Homes For Our Troops']

    var colorRange = d3.scale.category20();
    var color = d3.scale.ordinal()
    .range(colorRange.range());

    var list = document.createElement('div');
    document.getElementById('family_charity_list').appendChild(list);

    for(var i = 0; i < charities.length; i++) {
        // Create the list item:
        var char_band = document.createElement('div');
        char_band.className = "char_band";
        char_band.setAttribute('id', 'cb'+i);
        col = color(charities[i]);
        //char_band.style.background = "linear-gradient(to right,"+col+"  0%,#EAEAEA 100%)"
        char_band.style.background = "linear-gradient(to right,"+col+" 2%,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF,#FFFFFF"
        var outer_div = document.createElement('div');
        outer_div.className ='outer_div';
        list.appendChild(outer_div);
        var legend = document.createElement('div');
        legend.className ='legend';
        legend.style.backgroundImage = color(charities[i]);
        //char_band.appendChild(legend);
        var char_content_name = document.createElement('div');
        char_content_name.className ='char_name';
        var char_name = document.createTextNode(charities[i]);
        char_content_name.appendChild(char_name);
        char_band.appendChild(char_content_name);
        outer_div.appendChild(char_band)
        }
    return list;
}
<|endoftext|>


<commit_before>import { Feature } from 'toolkit/core/feature';
import { getEmberView } from 'toolkit/helpers/toolkit';

const DISTRIBUTE_BUTTON_ID = 'auto-distribute-splits-button';
const SPLIT_BUTTON_CLASS =
  'button button-primary modal-account-categories-split-transaction';

function actualNumber(n) {
  return typeof n === 'number' && !isNaN(n);
}

export class AutoDistributeSplits extends Feature {
  constructor() {
    super();
    this.button = document.createElement('button');
    this.button.id = DISTRIBUTE_BUTTON_ID;
    this.button.classList.add('button');
    this.button.classList.add('button-primary');
    this.button.innerHTML = 'Auto-Distribute';
    this.button.onclick = () => {
      this.distribute();
      this.button.blur();
    };
  }

  observe(changedNodes) {
    if (changedNodes.has(SPLIT_BUTTON_CLASS) && !this.buttonPresent()) {
      this.addButton();
    }
  }

  buttonPresent() {
    return !!document.getElementById(DISTRIBUTE_BUTTON_ID);
  }

  addButton() {
    $('.ynab-grid-actions .ynab-grid-cell-subCategoryName').append(this.button);
  }

  distribute() {
    const [[, ...subCells], [total, ...subValues]] = this.getCellsAndValues();
    if (!this.canDistribute(total, subValues)) {
      this.alertCannotDistribute();
      return;
    }

    const remaining = this.getRemainingValue(total, subValues);
    if (remaining < 0 && !this.confirmSubtraction()) {
      return;
    }

    this.adjustValues(
      subCells,
      subValues.map(this.proportionalValue(total, remaining))
    );
  }

  getCellsAndValues() {
    const cells = $('.is-editing .ynab-grid-cell-outflow input').toArray();
    const values = cells.map(node => parseFloat(node.value.trim()));
    return [cells, values];
  }

  canDistribute(total, subValues) {
    return actualNumber(total) && subValues.any(actualNumber);
  }

  alertCannotDistribute() {
    // eslint-disable-next-line no-alert
    alert(
      'Must fill in at least the total and one sub-transaction ' +
        'in order to auto-distribute'
    );
  }

  getRemainingValue(total, subValues) {
    return total - subValues.filter(actualNumber).reduce((a, b) => a + b);
  }

  confirmSubtraction() {
    // eslint-disable-next-line no-alert
    return confirm(
      'Sub-transactions add up to more than the total, ' +
        'are you sure you want to subtract from them?'
    );
  }

  proportionalValue(total, remaining) {
    return value => value + value / (total - remaining) * remaining;
  }

  adjustValues(subCells, newSubValues) {
    subCells.forEach((cell, i) => {
      $(cell).val(
        actualNumber(newSubValues[i])
          ? (Math.round(newSubValues[i] * 100) / 100).toFixed(2)
          : ''
      );
      $(cell).trigger('change');
    });

    getEmberView($('.ynab-grid-add-rows')[0].id).calculateSplitRemaining();
  }
}
<commit_msg>Move auto-distribute button next to the other buttons<commit_after>import { Feature } from 'toolkit/core/feature';
import { getEmberView } from 'toolkit/helpers/toolkit';

const DISTRIBUTE_BUTTON_ID = 'auto-distribute-splits-button';
const SPLIT_BUTTON_CLASS =
  'button button-primary modal-account-categories-split-transaction';

function actualNumber(n) {
  return typeof n === 'number' && !isNaN(n);
}

export class AutoDistributeSplits extends Feature {
  constructor() {
    super();
    this.button = document.createElement('button');
    this.button.id = DISTRIBUTE_BUTTON_ID;
    this.button.classList.add('button');
    this.button.classList.add('button-primary');
    this.button.innerHTML = 'Auto-Distribute';
    this.button.onclick = () => {
      this.distribute();
      this.button.blur();
    };
  }

  observe(changedNodes) {
    if (changedNodes.has(SPLIT_BUTTON_CLASS) && !this.buttonPresent()) {
      this.addButton();
    }
  }

  buttonPresent() {
    return !!document.getElementById(DISTRIBUTE_BUTTON_ID);
  }

  addButton() {
    $('.ynab-grid-actions-buttons .button-cancel').after(this.button);
  }

  distribute() {
    const [[, ...subCells], [total, ...subValues]] = this.getCellsAndValues();
    if (!this.canDistribute(total, subValues)) {
      this.alertCannotDistribute();
      return;
    }

    const remaining = this.getRemainingValue(total, subValues);
    if (remaining < 0 && !this.confirmSubtraction()) {
      return;
    }

    this.adjustValues(
      subCells,
      subValues.map(this.proportionalValue(total, remaining))
    );
  }

  getCellsAndValues() {
    const cells = $('.is-editing .ynab-grid-cell-outflow input').toArray();
    const values = cells.map(node => parseFloat(node.value.trim()));
    return [cells, values];
  }

  canDistribute(total, subValues) {
    return actualNumber(total) && subValues.any(actualNumber);
  }

  alertCannotDistribute() {
    // eslint-disable-next-line no-alert
    alert(
      'Must fill in at least the total and one sub-transaction ' +
        'in order to auto-distribute'
    );
  }

  getRemainingValue(total, subValues) {
    return total - subValues.filter(actualNumber).reduce((a, b) => a + b);
  }

  confirmSubtraction() {
    // eslint-disable-next-line no-alert
    return confirm(
      'Sub-transactions add up to more than the total, ' +
        'are you sure you want to subtract from them?'
    );
  }

  proportionalValue(total, remaining) {
    return value => value + value / (total - remaining) * remaining;
  }

  adjustValues(subCells, newSubValues) {
    subCells.forEach((cell, i) => {
      $(cell).val(
        actualNumber(newSubValues[i])
          ? (Math.round(newSubValues[i] * 100) / 100).toFixed(2)
          : ''
      );
      $(cell).trigger('change');
    });

    getEmberView($('.ynab-grid-add-rows')[0].id).calculateSplitRemaining();
  }
}
<|endoftext|>


<commit_before>const AWS = require("aws-sdk");
const uuidv4 = require("uuid/v4");
const logError = require("./log-error.js");

AWS.config.update({ region: process.env.AWS_REGION });

function uploadToAWS(base64String) {
  const newFileName = uuidv4();
  const s3 = new AWS.S3({ apiVersion: "2006-03-01" });
  const base64Data = Buffer.from(
    base64String.split(";")[1].split(",")[1],
    "base64"
  );

  const contentType = base64String.split(":")[1].split(";")[0];

  const uploadParams = {
    Bucket: process.env.AWS_S3_BUCKET,
    Key: newFileName,
    Body: base64Data,
    ContentEncoding: "base64",
    ContentType: contentType,
    ACL: "public-read",
  };

  s3.upload(uploadParams, (err, data) => {
    if (err) {
      logError(err);
    }
  });

  return `${process.env.AWS_UPLOADS_URL}${newFileName}`;
}

module.exports = uploadToAWS;
<commit_msg>Resize new image before upload<commit_after>const AWS = require("aws-sdk");
const jimp = require('jimp');
const uuidv4 = require("uuid/v4");
const logError = require("./log-error.js");
const s3 = new AWS.S3({
  apiVersion: "2006-03-01",
  region: process.env.AWS_REGION,
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
});

AWS.config.update({ region: process.env.AWS_REGION });

function createBufferFromBase64 (base64String) {
  return Buffer.from(
    base64String.split(";")[1].split(",")[1],
    "base64"
  );
}

function optimizeImage(buffer, contentType, size, cb) {
  jimp.read(buffer, (err, img) => {
    if (err) {
      cb(err);
    } else {
      if (img.bitmap.width > size || img.bitmap.height > size) { // Resize required
        let resizeW = size;
        let resizeH = jimp.AUTO;
        if (img.bitmap.width < img.bitmap.height) {
          resizeW = jimp.AUTO;
          resizeH = size;
        }
        img
          .resize(resizeW, resizeH) // resize
          .quality(60) // set image quality
          .getBuffer(contentType, cb);
      } else { // the image is too small to be resized, just return the same buffer
        cb(null, buffer);
      }
    }
  });
}

function uploadObject(buffer, contentType, filename, isThumbnail, cb) {
  let key = filename;
  if(isThumbnail) {
    key = `thumbnail/${filename}`;
  }
  const uploadParams = {
    Bucket: process.env.AWS_S3_BUCKET,
    Key: key,
    Body: buffer,
    ContentEncoding: "base64",
    ContentType: contentType,
    ACL: "public-read",
  };

  s3.upload(uploadParams, cb);
}

function uploadToAWS(base64String) {
  const newFileName = uuidv4();
  const base64Buffer = createBufferFromBase64(base64String);

  const contentType = base64String.split(":")[1].split(";")[0];

  if (contentType === "image/png" || contentType === "image/jpg" || contentType === "image/jpeg") { // is image
    optimizeImage(base64Buffer, contentType, 600, (err, buffer) => { // Optimize and upload thumbnail
      uploadObject(buffer, contentType, newFileName, true, (err, data) => {
        if (err) {
          logError(err);
        }
      });
    });
    optimizeImage(base64Buffer, contentType, 1600, (err, buffer) => { // Optimize and upload full image
      uploadObject(buffer, contentType, newFileName, false, (err, data) => {
        if (err) {
          logError(err);
        }
      });
    });
  } else {
    uploadObject(buffer, contentType, newFileName, false, (err, data) => {
      if (err) {
        logError(err);
      }
    });
  }

  return `${process.env.AWS_UPLOADS_URL}${newFileName}`;
}

module.exports = uploadToAWS;
<|endoftext|>


<commit_before>'use strict'

const AWS = require('aws-sdk')
const httpAwsEs = require('http-aws-es')
const elasticsearch = require('elasticsearch')
const through2 = require('through2')
const ElasticsearchWritableStream = require('./ElasticSearchWriteableStream')
const logger = require('./logger')

let _esClient
/*
This module is used for connecting to an Elasticsearch instance, writing records,
searching records, and managing the indexes. It looks for the ES_HOST environment
variable which is the URL to the elasticsearch host
*/

// Connect to an Elasticsearch cluster
async function connect() {
  let esConfig
  let client

  // use local client
  if (!process.env.ES_HOST) {
    client = new elasticsearch.Client({ host: 'localhost:9200' })
  } else {
    await new Promise((resolve, reject) => AWS.config.getCredentials((err) => {
      if (err) return reject(err)
      return resolve()
    }))

    AWS.config.update({
      credentials: new AWS.Credentials(process.env.AWS_ACCESS_KEY_ID,
        process.env.AWS_SECRET_ACCESS_KEY),
      region: process.env.AWS_REGION || 'us-east-1'
    })

    esConfig = {
      hosts: [process.env.ES_HOST],
      connectionClass: httpAwsEs,
      awsConfig: new AWS.Config({ region: process.env.AWS_REGION || 'us-east-1' }),
      httpOptions: {},
      // Note that this doesn't abort the query.
      requestTimeout: 120000 // milliseconds
    }
    client = new elasticsearch.Client(esConfig)
  }

  await new Promise((resolve, reject) => client.ping({ requestTimeout: 1000 },
    (err) => {
      if (err) {
        reject('Unable to connect to elasticsearch')
      } else {
        resolve()
      }
    }))
  return client
}

// get existing ES client or create a new one
async function esClient() {
  if (!_esClient) {
    try {
      _esClient = await connect()
    } catch (error) {
      logger.error(error)
    }
    if (_esClient) {
      logger.debug('Connected to Elasticsearch')
    }
  } else {
    logger.debug('Using existing Elasticsearch connection')
  }
  return _esClient
}

// Create STAC mappings
async function prepare(index) {
  // TODO - different mappings for collection and item
  const props = {
    'type': 'object',
    properties: {
      'collection': { type: 'keyword' },
      'datetime': { type: 'date' },
      'eo:cloud_cover': { type: 'float' },
      'eo:gsd': { type: 'float' },
      'eo:constellation': { type: 'keyword' },
      'eo:platform': { type: 'keyword' },
      'eo:instrument': { type: 'keyword' },
      'eo:epsg': { type: 'integer' },
      'eo:off_nadir': { type: 'float' },
      'eo:azimuth': { type: 'float' },
      'eo:sun_azimuth': { type: 'float' },
      'eo:sun_elevation': { type: 'float' }
    }
  }

  const dynamicTemplates = [{
    strings: {
      mapping: {
        type: 'keyword'
      },
      match_mapping_type: 'string'
    }
  }]
  const client = await esClient()
  const indexExists = await client.indices.exists({ index })
  if (!indexExists) {
    const precision = process.env.SATAPI_ES_PRECISION || '5mi'
    const payload = {
      index,
      body: {
        mappings: {
          doc: {
            /*'_all': {
                enabled: true
            },*/
            dynamic_templates: dynamicTemplates,
            properties: {
              'id': { type: 'keyword' },
              'properties': props,
              geometry: {
                type: 'geo_shape',
                tree: 'quadtree',
                precision: precision
              }
            }
          }
        }
      }
    }
    try {
      await client.indices.create(payload)
      logger.info(`Created index: ${JSON.stringify(payload)}`)
    } catch (error) {
      const debugMessage = `Error creating index, already created: ${error}`
      logger.debug(debugMessage)
    }
  }
}

// Given an input stream and a transform, write records to an elasticsearch instance
async function _stream() {
  let esStreams
  try {
    let collections = []
    const client = await esClient()
    const indexExists = await client.indices.exists({ index: 'collections' })
    if (indexExists) {
      const body = { query: { match_all: {} } }
      const searchParams = {
        index: 'collections',
        body
      }
      const resultBody = await client.search(searchParams)
      collections = resultBody.hits.hits.map((r) => (r._source))
    }

    const toEs = through2.obj({ objectMode: true }, (data, encoding, next) => {
      let index = ''
      if (data && data.hasOwnProperty('extent')) {
        index = 'collections'
      } else if (data && data.hasOwnProperty('geometry')) {
        index = 'items'
      } else {
        next()
        return
      }
      // remove any hierarchy links in a non-mutating way
      const hlinks = ['self', 'root', 'parent', 'child', 'collection', 'item']
      const links = data.links.filter((link) => hlinks.includes(link))
      let esDataObject = Object.assign({}, data, { links })
      if (index === 'items') {
        const collectionId = data.properties.collection
        const itemCollection =
          collections.find((collection) => (collectionId === collection.id))
        if (itemCollection) {
          const flatProperties =
            Object.assign({}, itemCollection.properties, data.properties)
          esDataObject = Object.assign({}, esDataObject, { properties: flatProperties })
        } else {
          logger.error(`${data.id} has no collection`)
        }
      }

      // create ES record
      const record = {
        index,
        type: 'doc',
        id: esDataObject.id,
        action: 'update',
        _retry_on_conflict: 3,
        body: {
          doc: esDataObject,
          doc_as_upsert: true
        }
      }
      next(null, record)
    })
    const esStream = new ElasticsearchWritableStream({ client: client }, {
      objectMode: true,
      highWaterMark: process.env.ES_BATCH_SIZE || 500
    })
    esStreams = { toEs, esStream }
  } catch (error) {
    logger.error(error)
  }
  return esStreams
}

function buildRangeQuery(property, operators, operatorsObject) {
  const gt = 'gt'
  const lt = 'lt'
  const gte = 'gte'
  const lte = 'lte'
  const comparisons = [gt, lt, gte, lte]
  let rangeQuery
  if (operators.includes(gt) || operators.includes(lt) ||
         operators.includes(gte) || operators.includes(lte)) {
    const propertyKey = `properties.${property}`
    rangeQuery = {
      range: {
        [propertyKey]: {
        }
      }
    }
    // All operators for a property go in a single range query.
    comparisons.forEach((comparison) => {
      if (operators.includes(comparison)) {
        const exisiting = rangeQuery.range[propertyKey]
        rangeQuery.range[propertyKey] = Object.assign({}, exisiting, {
          [comparison]: operatorsObject[comparison]
        })
      }
    })
  }
  return rangeQuery
}

function buildDatetimeQuery(parameters) {
  let dateQuery
  const { datetime } = parameters
  if (datetime) {
    const dataRange = datetime.split('/')
    if (dataRange.length === 2) {
      dateQuery = {
        range: {
          'properties.datetime': {
            gte: dataRange[0],
            lte: dataRange[1]
          }
        }
      }
    } else {
      dateQuery = {
        term: {
          'properties.datetime': datetime
        }
      }
    }
  }
  return dateQuery
}

function buildQuery(parameters) {
  const eq = 'eq'
  const { query, intersects } = parameters
  let must = []
  if (query) {
    // Using reduce rather than map as we don't currently support all
    // stac query operators.
    must = Object.keys(query).reduce((accumulator, property) => {
      const operatorsObject = query[property]
      const operators = Object.keys(operatorsObject)
      if (operators.includes(eq)) {
        const termQuery = {
          term: {
            [`properties.${property}`]: operatorsObject.eq
          }
        }
        accumulator.push(termQuery)
      }
      const rangeQuery =
        buildRangeQuery(property, operators, operatorsObject)
      if (rangeQuery) {
        accumulator.push(rangeQuery)
      }
      return accumulator
    }, must)
  }

  if (intersects) {
    const { geometry } = intersects
    must.push({
      geo_shape: {
        geometry: { shape: geometry }
      }
    })
  }

  const datetimeQuery = buildDatetimeQuery(parameters)
  if (datetimeQuery) {
    must.push(datetimeQuery)
  }

  const filter = { bool: { must } }
  const queryBody = {
    constant_score: { filter }
  }
  return { query: queryBody }
}

function buildIdQuery(id) {
  return {
    query: {
      constant_score: {
        filter: {
          term: {
            id
          }
        }
      }
    }
  }
}

function buildSort(parameters) {
  const { sort } = parameters
  let sorting
  if (sort && sort.length > 0) {
    sorting = sort.map((sortRule) => {
      const { field, direction } = sortRule
      const propertyKey = `properties.${field}`
      return {
        [propertyKey]: {
          order: direction
        }
      }
    })
  } else {
    // Default item sorting
    sorting = [
      { 'properties.datetime': { order: 'desc' } }
    ]
  }
  return sorting
}

function buildFieldsFilter(parameters) {
  const id = 'id'
  const { fields } = parameters
  const _sourceInclude = []
  const _sourceExclude = []
  if (fields) {
    const { includes, excludes } = fields
    if (includes && includes.length > 0) {
      const propertiesIncludes = includes.map(
        (field) => (`${field}`)
      ).concat(
        [id]
      )
      _sourceInclude.push(...propertiesIncludes)
    }
    if (excludes && excludes.length > 0) {
      const filteredExcludes = excludes.filter((field) =>
        (![id].includes(field)))
      const propertiesExcludes = filteredExcludes.map((field) => (`${field}`))
      _sourceExclude.push(...propertiesExcludes)
    }
  }
  return { _sourceExclude, _sourceInclude }
}

async function search(parameters, index = '*', page = 1, limit = 10) {
  let body
  if (parameters.id) {
    const { id } = parameters
    body = buildIdQuery(id)
  } else {
    body = buildQuery(parameters)
  }
  const sort = buildSort(parameters)
  body.sort = sort

  const searchParams = {
    index,
    body,
    size: limit,
    from: (page - 1) * limit
  }

  const { _sourceExclude, _sourceInclude } = buildFieldsFilter(parameters)
  if (_sourceExclude.length > 0) {
    searchParams._sourceExclude = _sourceExclude
  }
  if (_sourceInclude.length > 0) {
    searchParams._sourceInclude = _sourceInclude
  }
  const client = await esClient()
  const resultBody = await client.search(searchParams)
  const results = resultBody.hits.hits.map((r) => (r._source))
  const response = {
    results,
    meta: {
      page,
      limit,
      found: resultBody.hits.total,
      returned: results.length
    }
  }
  return response
}

module.exports.prepare = prepare
module.exports.stream = _stream
module.exports.search = search
<commit_msg>add in `ids` search parameter<commit_after>'use strict'

const AWS = require('aws-sdk')
const httpAwsEs = require('http-aws-es')
const elasticsearch = require('elasticsearch')
const through2 = require('through2')
const ElasticsearchWritableStream = require('./ElasticSearchWriteableStream')
const logger = require('./logger')

let _esClient
/*
This module is used for connecting to an Elasticsearch instance, writing records,
searching records, and managing the indexes. It looks for the ES_HOST environment
variable which is the URL to the elasticsearch host
*/

// Connect to an Elasticsearch cluster
async function connect() {
  let esConfig
  let client

  // use local client
  if (!process.env.ES_HOST) {
    client = new elasticsearch.Client({ host: 'localhost:9200' })
  } else {
    await new Promise((resolve, reject) => AWS.config.getCredentials((err) => {
      if (err) return reject(err)
      return resolve()
    }))

    AWS.config.update({
      credentials: new AWS.Credentials(process.env.AWS_ACCESS_KEY_ID,
        process.env.AWS_SECRET_ACCESS_KEY),
      region: process.env.AWS_REGION || 'us-east-1'
    })

    esConfig = {
      hosts: [process.env.ES_HOST],
      connectionClass: httpAwsEs,
      awsConfig: new AWS.Config({ region: process.env.AWS_REGION || 'us-east-1' }),
      httpOptions: {},
      // Note that this doesn't abort the query.
      requestTimeout: 120000 // milliseconds
    }
    client = new elasticsearch.Client(esConfig)
  }

  await new Promise((resolve, reject) => client.ping({ requestTimeout: 1000 },
    (err) => {
      if (err) {
        reject('Unable to connect to elasticsearch')
      } else {
        resolve()
      }
    }))
  return client
}

// get existing ES client or create a new one
async function esClient() {
  if (!_esClient) {
    try {
      _esClient = await connect()
    } catch (error) {
      logger.error(error)
    }
    if (_esClient) {
      logger.debug('Connected to Elasticsearch')
    }
  } else {
    logger.debug('Using existing Elasticsearch connection')
  }
  return _esClient
}

// Create STAC mappings
async function prepare(index) {
  // TODO - different mappings for collection and item
  const props = {
    'type': 'object',
    properties: {
      'collection': { type: 'keyword' },
      'datetime': { type: 'date' },
      'eo:cloud_cover': { type: 'float' },
      'eo:gsd': { type: 'float' },
      'eo:constellation': { type: 'keyword' },
      'eo:platform': { type: 'keyword' },
      'eo:instrument': { type: 'keyword' },
      'eo:epsg': { type: 'integer' },
      'eo:off_nadir': { type: 'float' },
      'eo:azimuth': { type: 'float' },
      'eo:sun_azimuth': { type: 'float' },
      'eo:sun_elevation': { type: 'float' }
    }
  }

  const dynamicTemplates = [{
    strings: {
      mapping: {
        type: 'keyword'
      },
      match_mapping_type: 'string'
    }
  }]
  const client = await esClient()
  const indexExists = await client.indices.exists({ index })
  if (!indexExists) {
    const precision = process.env.SATAPI_ES_PRECISION || '5mi'
    const payload = {
      index,
      body: {
        mappings: {
          doc: {
            /*'_all': {
                enabled: true
            },*/
            dynamic_templates: dynamicTemplates,
            properties: {
              'id': { type: 'keyword' },
              'properties': props,
              geometry: {
                type: 'geo_shape',
                tree: 'quadtree',
                precision: precision
              }
            }
          }
        }
      }
    }
    try {
      await client.indices.create(payload)
      logger.info(`Created index: ${JSON.stringify(payload)}`)
    } catch (error) {
      const debugMessage = `Error creating index, already created: ${error}`
      logger.debug(debugMessage)
    }
  }
}

// Given an input stream and a transform, write records to an elasticsearch instance
async function _stream() {
  let esStreams
  try {
    let collections = []
    const client = await esClient()
    const indexExists = await client.indices.exists({ index: 'collections' })
    if (indexExists) {
      const body = { query: { match_all: {} } }
      const searchParams = {
        index: 'collections',
        body
      }
      const resultBody = await client.search(searchParams)
      collections = resultBody.hits.hits.map((r) => (r._source))
    }

    const toEs = through2.obj({ objectMode: true }, (data, encoding, next) => {
      let index = ''
      if (data && data.hasOwnProperty('extent')) {
        index = 'collections'
      } else if (data && data.hasOwnProperty('geometry')) {
        index = 'items'
      } else {
        next()
        return
      }
      // remove any hierarchy links in a non-mutating way
      const hlinks = ['self', 'root', 'parent', 'child', 'collection', 'item']
      const links = data.links.filter((link) => hlinks.includes(link))
      let esDataObject = Object.assign({}, data, { links })
      if (index === 'items') {
        const collectionId = data.properties.collection
        const itemCollection =
          collections.find((collection) => (collectionId === collection.id))
        if (itemCollection) {
          const flatProperties =
            Object.assign({}, itemCollection.properties, data.properties)
          esDataObject = Object.assign({}, esDataObject, { properties: flatProperties })
        } else {
          logger.error(`${data.id} has no collection`)
        }
      }

      // create ES record
      const record = {
        index,
        type: 'doc',
        id: esDataObject.id,
        action: 'update',
        _retry_on_conflict: 3,
        body: {
          doc: esDataObject,
          doc_as_upsert: true
        }
      }
      next(null, record)
    })
    const esStream = new ElasticsearchWritableStream({ client: client }, {
      objectMode: true,
      highWaterMark: process.env.ES_BATCH_SIZE || 500
    })
    esStreams = { toEs, esStream }
  } catch (error) {
    logger.error(error)
  }
  return esStreams
}

function buildRangeQuery(property, operators, operatorsObject) {
  const gt = 'gt'
  const lt = 'lt'
  const gte = 'gte'
  const lte = 'lte'
  const comparisons = [gt, lt, gte, lte]
  let rangeQuery
  if (operators.includes(gt) || operators.includes(lt) ||
         operators.includes(gte) || operators.includes(lte)) {
    const propertyKey = `properties.${property}`
    rangeQuery = {
      range: {
        [propertyKey]: {
        }
      }
    }
    // All operators for a property go in a single range query.
    comparisons.forEach((comparison) => {
      if (operators.includes(comparison)) {
        const exisiting = rangeQuery.range[propertyKey]
        rangeQuery.range[propertyKey] = Object.assign({}, exisiting, {
          [comparison]: operatorsObject[comparison]
        })
      }
    })
  }
  return rangeQuery
}

function buildDatetimeQuery(parameters) {
  let dateQuery
  const { datetime } = parameters
  if (datetime) {
    const dataRange = datetime.split('/')
    if (dataRange.length === 2) {
      dateQuery = {
        range: {
          'properties.datetime': {
            gte: dataRange[0],
            lte: dataRange[1]
          }
        }
      }
    } else {
      dateQuery = {
        term: {
          'properties.datetime': datetime
        }
      }
    }
  }
  return dateQuery
}

function buildQuery(parameters) {
  const eq = 'eq'
  const { query, intersects } = parameters
  let must = []
  if (query) {
    // Using reduce rather than map as we don't currently support all
    // stac query operators.
    must = Object.keys(query).reduce((accumulator, property) => {
      const operatorsObject = query[property]
      const operators = Object.keys(operatorsObject)
      if (operators.includes(eq)) {
        const termQuery = {
          term: {
            [`properties.${property}`]: operatorsObject.eq
          }
        }
        accumulator.push(termQuery)
      }
      const rangeQuery =
        buildRangeQuery(property, operators, operatorsObject)
      if (rangeQuery) {
        accumulator.push(rangeQuery)
      }
      return accumulator
    }, must)
  }

  if (intersects) {
    const { geometry } = intersects
    must.push({
      geo_shape: {
        geometry: { shape: geometry }
      }
    })
  }

  const datetimeQuery = buildDatetimeQuery(parameters)
  if (datetimeQuery) {
    must.push(datetimeQuery)
  }

  const filter = { bool: { must } }
  const queryBody = {
    constant_score: { filter }
  }
  return { query: queryBody }
}

function buildIdQuery(id) {
  return {
    query: {
      constant_score: {
        filter: {
          term: {
            id
          }
        }
      }
    }
  }
}

function buildIdsQuery(ids) {
  return {
    query: {
      ids: {
        values: ids
      }
  }
}

function buildSort(parameters) {
  const { sort } = parameters
  let sorting
  if (sort && sort.length > 0) {
    sorting = sort.map((sortRule) => {
      const { field, direction } = sortRule
      const propertyKey = `properties.${field}`
      return {
        [propertyKey]: {
          order: direction
        }
      }
    })
  } else {
    // Default item sorting
    sorting = [
      { 'properties.datetime': { order: 'desc' } }
    ]
  }
  return sorting
}

function buildFieldsFilter(parameters) {
  const id = 'id'
  const { fields } = parameters
  const _sourceInclude = []
  const _sourceExclude = []
  if (fields) {
    const { includes, excludes } = fields
    if (includes && includes.length > 0) {
      const propertiesIncludes = includes.map(
        (field) => (`${field}`)
      ).concat(
        [id]
      )
      _sourceInclude.push(...propertiesIncludes)
    }
    if (excludes && excludes.length > 0) {
      const filteredExcludes = excludes.filter((field) =>
        (![id].includes(field)))
      const propertiesExcludes = filteredExcludes.map((field) => (`${field}`))
      _sourceExclude.push(...propertiesExcludes)
    }
  }
  return { _sourceExclude, _sourceInclude }
}

async function search(parameters, index = '*', page = 1, limit = 10) {
  let body
  if (parameters.ids) {
    const { ids } = parameters
    body = buildIdsQuery(ids)
  } else if (parameters.id) {
    const { id } = parameters
    body = buildIdQuery(id)
  } else {
    body = buildQuery(parameters)
  }
  const sort = buildSort(parameters)
  body.sort = sort

  const searchParams = {
    index,
    body,
    size: limit,
    from: (page - 1) * limit
  }

  const { _sourceExclude, _sourceInclude } = buildFieldsFilter(parameters)
  if (_sourceExclude.length > 0) {
    searchParams._sourceExclude = _sourceExclude
  }
  if (_sourceInclude.length > 0) {
    searchParams._sourceInclude = _sourceInclude
  }
  const client = await esClient()
  const resultBody = await client.search(searchParams)
  const results = resultBody.hits.hits.map((r) => (r._source))
  const response = {
    results,
    meta: {
      page,
      limit,
      found: resultBody.hits.total,
      returned: results.length
    }
  }
  return response
}

module.exports.prepare = prepare
module.exports.stream = _stream
module.exports.search = search
<|endoftext|>


<commit_before>
/* exported sanitizeChromSelOptions */
function sanitizeChromSelOptions() {

	var chrom = [];
	var chrom_select = document.forms["filters_form"].elements["chrom"];
  var options = chrom_select && chrom_select.options;
  var opt;

  // clear other options if All is selected
	// and take note of otherwise selected chrs
  for (var i=1, iLen=options.length; i<iLen; i++) {
    opt = options[i];
    if (opt.selected) {
    	if (chrom_select.selectedIndex === 0) {
				opt.selected = false
			} else {
    		chrom.push(opt.value);
			}
    }
  }

	console.log("Depopulate cytobands if more than one chrom is selected, all selected ,")
	if (chrom.length > 1) {
		// disable
		for (elem of [cytoStart, cytoEnd]) {
			elem.options.length = 0; //remove previous cytoband select options
			startElem.value = null
			endElem.value = null
		}
		return
	}
}

function getSelectedChromosomes() {
	var chrom_select = document.forms["filters_form"].elements["chrom"];

	var chrom = [];
  var options = chrom_select && chrom_select.options;
  var opt;

  // return empty array if All option is selected
  if (chrom_select.selectedIndex === 0) {
		return chrom
	}
 	// return individually selected chrs
  for (var i=1, iLen=options.length; i<iLen; i++) {
    opt = options[i];
    if (opt.selected) {
			chrom.push(opt.value);
		}
	}
  return chrom
}

/* exported populateCytobands */
function populateCytobands(cytobands) {
  console.log("Populate cytobands")

	var chromosome = "";
	chrom = getSelectedChromosomes();
	if (chrom.length != 1) {
		console.log("More than one chr selected")
		return
	}
	chromosome = chrom[0]

	console.log("Add chr " + chromosome + "("+ typeof (chromosome)+")")
	var chrom_cytobands = cytobands[chromosome]["cytobands"]; // chromosome-specific cytobands

	for (elem of [cytoStart, cytoEnd]) {
		elem.options.length = 0; //remove previous select options

		var emptyStart = document.createElement("option");
		emptyStart.textContent = "";
		emptyStart.value = "";
		elem.appendChild(emptyStart); //Add an empty (blank) option to the select
	}

	for (var i = 0; i < chrom_cytobands.length; i++) {
		var opt = chrom_cytobands[i]

		// populate the cytoband start select
		var interval = ["(start:", opt["start"], ")"].join("");
		var optionText = [chromosome, opt["band"], interval].join(" ");

		// populate the cytoband start select
		var el = document.createElement("option");
		el.textContent = optionText;
		el.value = opt["start"];
		if (startElem.value === el.value) {
			el.selected = true;
		}
		cytoStart.appendChild(el);

		var interval = ["(end:", opt["stop"], ")"].join("")
		var optionText = [chromosome, opt["band"], interval].join(" ");

		var el = document.createElement("option");
		el.textContent = optionText;
		el.value = opt["stop"];
		if (endElem.value === el.value) {
			el.selected = true;
		}
		cytoEnd.appendChild(el);
	}
}

function validateChromPos(){
  // Validate Chromosome position form
  //Expected format: <chr number>:<start>-<end>[+-]?<padding>
  var chrPosPattern = "^(?:chr)?([1-9]|1[0-9]|2[0-2]|X|Y|MT)(?::([0-9]+)-([0-9]+)([+-]{1}[0-9]+)?)?$";
  var chromoPosField = document.forms["filters_form"].elements["chrom_pos"]
  if (chromoPosField && chromoPosField.vaLue) {
    var chrom_pos = chromoPosField.value.replaceAll(',', '')
    if (!RegExp(chrPosPattern).test(chrom_pos)) {
      alert("Invalid format of chromosome position, expected format <chr number>:<start>-<end>[+-]?<padding>");
      return false;
    }
    var chromPosMatch  = chrom_pos.match(chrPosPattern);
    var start = chromPosMatch[2];
    var end = chromPosMatch[3];
    var padding = chromPosMatch[5];
    if (Number(start) < 0 || Number(end) < 0) {
      alert("Invalid coordinates, coordinates must be greater than zero");
      return false;
    } else if (Number(start) > Number(end)) {
      alert("Invalid coordinates, end coordinate must be greater than start");
      return false;
    } else if (Number(padding) < 1) {
      alert("Padding must be greater than zero!")
    }
  }
}

/* exported validateForm */
// ValidateForm()
// Control user input fields (start, end) in variant filter.
// Verify the format of Chromosome position
//
function validateForm(){
  var start = document.forms["filters_form"].elements["start"].value
  var end = document.forms["filters_form"].elements["end"].value
  if(start || end){
    if(!chrom){
      alert("Chromosome field is required");
      return false;
    }
    else if (chrom.length > 1) {
    	alert("Start and end coordinates are only applicable with one selected chromosome");
		}
    else if( !start || !end){
      alert("Both start and end coordinates are required");
      return false;
    }
    else if( (isNaN(start) || isNaN(end)) || Number(end)<Number(start) ){
      alert("Coordinate field not valid");
      return false;
    }
  }
  validateChromPos();
  // Avoid page spinner being stuck on Filter and export variants option
  $(window).unbind('beforeunload');
  return true;
}

/* exported initSearchConstraints */
// syncSearchConstraints(selectorId:HTML-selector, textId:HTML-textfield)
//
// Initialize and synchronize 'startelem' and 'cyto_start', used for setting
// constraints when searching variants in cytoband.
function initSearchConstraints(selectorId, textId){
    console.log("init cytoband search: selector and text")
    selectorId.addEventListener("change", function() {
        if(selectorId.options[selectorId.selectedIndex].value === ""){
            textId.value = "";
            return
        }
        // populate textfield
        textId.value = selectorId.options[selectorId.selectedIndex].value
    });
}

/* exported enableDismiss */
function enableDismiss(){
  // before enabling the variant dismiss button
  var selectElem = document.getElementById("dismiss_choices");
  // make sure that user selects at least one dismiss reason
  var selectedOptions = false;
  for (var i = 0; i < selectElem.length; i++) {
      if (selectElem.options[i].selected){
        selectedOptions = true;
        break;
      }
  }
  // make sure that at least one checkbox corresponding to a variant is checked
  var variantCheckboxes = document.getElementsByName("dismiss");
  var checkedVars=false
  for (var i = 0; i < variantCheckboxes.length; i++) {
      if (variantCheckboxes[i].checked){
        checkedVars = true;
        break;
      }
  }
  var btnElem = document.getElementById("dismiss_submit");
  if (selectedOptions & checkedVars) {
    btnElem.disabled = false;
  }
  else{
    btnElem.disabled = true;
  }
}

/* exported eraseChromPosString() */
function eraseChromPosString() {
  // Erase content of chrom_pos field
  document.forms["filters_form"].elements["chrom_pos"].value = "";
}

/* exported updatedChromPosInput */
// Link chromosome position input field with chromosome and cytoband dropdowns.
// Changes in chrom_pos input are reflected in chrom, start and end fields
function updatedChromPosInput() {
	console.log('Update to chrom pos field detected!')
	var chromSel = document.forms["filters_form"].elements["chrom"];
	var chromPos = document.forms["filters_form"].elements["chrom_pos"];
	var chromPosPattern = "^(?:chr)?([1-9]|1[0-9]|2[0-2]|X|Y|MT)(?::([0-9]+)-([0-9]+)([+-]{1})?([0-9]+)?)?$";
	// parse chromosome position info
	var chrName, startPos, endPos, sign, padding;
	try {
		[_, chrName, startPos, endPos, sign, padding] = chromPos.value.replaceAll(',', '').match(chromPosPattern);
		console.log(`Parsing ChrPos: ${chrName}, coord: ${startPos}-${endPos}, padding: ${sign}${padding}`)
	} catch (err) {
		console.log('ChrPos empty')
		return
	}

	console.log(chrName)
	// set default padding and sign
	padding = padding != null ? padding : 0
	sign = sign != null ? sign : '+'
	// Update start and end input fields
	if (startPos != null) {
		// invert sign expand before starting position
		var newStartPos = eval(`${startPos} ${sign == '+' ? '-' : '+' } ${padding}`);
		newStartPos = newStartPos < 0 ? 0 : newStartPos
		document.forms["filters_form"].elements["start"].value = newStartPos;
	}
	if (endPos != null) {
		var newEndPos = eval(`${endPos} ${sign} ${padding}`);
		newEndPos = newEndPos < 0 ? 0 : newEndPos
		document.forms["filters_form"].elements["end"].value = newEndPos;
	}
	if (chrName != null) {
		// select this chromosome ONLY in chromSel dropdown
  	for (var i = 0; i < chromSel.length; i++) {
  		if (chromSel.options[i].value !== chrName) {
				chromSel.options[i].selected = false;
			} else {
  			chromSel.options[i].selected = true;
			}
		}
	} else {
		console.log(`ChrPos regexp not matching ${chromPos.value} - chrName empty`)
		// select the all chromosomes option ONLY in chromSel dropdown
  	for (var i = 0; i < chromSel.length; i++) {
  		if (chromSel.options[i].value !== "") {
				chromSel.options[i].selected = false;
			} else {
  			chromSel.options[i].selected = true;
			}
		}
	}
}

<commit_msg>attempt to second guess codefactor..<commit_after>/* exported sanitizeChromSelOptions() */
function sanitizeChromSelOptions() {
	var chrom = [];
	var chrom_select = document.forms["filters_form"].elements["chrom"];
  var options = chrom_select && chrom_select.options;
  var opt;

  // clear other options if All is selected
	// and take note of otherwise selected chrs
  for (var i=1, iLen=options.length; i<iLen; i++) {
    opt = options[i];
    if (opt.selected) {
    	if (chrom_select.selectedIndex === 0) {
				opt.selected = false
			} else {
    		chrom.push(opt.value);
			}
    }
  }

	console.log("Depopulate cytobands if more than one chrom is selected, all selected ,")
	if (chrom.length > 1) {
		// disable
		for (elem of [cytoStart, cytoEnd]) {
			elem.options.length = 0; //remove previous cytoband select options
			startElem.value = null
			endElem.value = null
		}
		return
	}
}

function getSelectedChromosomes() {
	var chrom_select = document.forms["filters_form"].elements["chrom"];

	var chrom = [];
  var options = chrom_select && chrom_select.options;
  var opt;

  // return empty array if All option is selected
  if (chrom_select.selectedIndex === 0) {
		return chrom
	}
 	// return individually selected chrs
  for (var i=1, iLen=options.length; i<iLen; i++) {
    opt = options[i];
    if (opt.selected) {
			chrom.push(opt.value);
		}
	}
  return chrom
}

/* exported populateCytobands */
function populateCytobands(cytobands) {
  console.log("Populate cytobands")

	var chromosome = "";
	chrom = getSelectedChromosomes();
	if (chrom.length != 1) {
		console.log("More than one chr selected")
		return
	}
	chromosome = chrom[0]

	console.log("Add chr " + chromosome + "("+ typeof (chromosome)+")")
	var chrom_cytobands = cytobands[chromosome]["cytobands"]; // chromosome-specific cytobands

	for (elem of [cytoStart, cytoEnd]) {
		elem.options.length = 0; //remove previous select options

		var emptyStart = document.createElement("option");
		emptyStart.textContent = "";
		emptyStart.value = "";
		elem.appendChild(emptyStart); //Add an empty (blank) option to the select
	}

	for (var i = 0; i < chrom_cytobands.length; i++) {
		var opt = chrom_cytobands[i]

		// populate the cytoband start select
		var interval = ["(start:", opt["start"], ")"].join("");
		var optionText = [chromosome, opt["band"], interval].join(" ");

		// populate the cytoband start select
		var el = document.createElement("option");
		el.textContent = optionText;
		el.value = opt["start"];
		if (startElem.value === el.value) {
			el.selected = true;
		}
		cytoStart.appendChild(el);

		var interval = ["(end:", opt["stop"], ")"].join("")
		var optionText = [chromosome, opt["band"], interval].join(" ");

		var el = document.createElement("option");
		el.textContent = optionText;
		el.value = opt["stop"];
		if (endElem.value === el.value) {
			el.selected = true;
		}
		cytoEnd.appendChild(el);
	}
}

function validateChromPos(){
  // Validate Chromosome position form
  //Expected format: <chr number>:<start>-<end>[+-]?<padding>
  var chrPosPattern = "^(?:chr)?([1-9]|1[0-9]|2[0-2]|X|Y|MT)(?::([0-9]+)-([0-9]+)([+-]{1}[0-9]+)?)?$";
  var chromoPosField = document.forms["filters_form"].elements["chrom_pos"]
  if (chromoPosField && chromoPosField.vaLue) {
    var chrom_pos = chromoPosField.value.replaceAll(',', '')
    if (!RegExp(chrPosPattern).test(chrom_pos)) {
      alert("Invalid format of chromosome position, expected format <chr number>:<start>-<end>[+-]?<padding>");
      return false;
    }
    var chromPosMatch  = chrom_pos.match(chrPosPattern);
    var start = chromPosMatch[2];
    var end = chromPosMatch[3];
    var padding = chromPosMatch[5];
    if (Number(start) < 0 || Number(end) < 0) {
      alert("Invalid coordinates, coordinates must be greater than zero");
      return false;
    } else if (Number(start) > Number(end)) {
      alert("Invalid coordinates, end coordinate must be greater than start");
      return false;
    } else if (Number(padding) < 1) {
      alert("Padding must be greater than zero!")
    }
  }
}

/* exported validateForm */
// ValidateForm()
// Control user input fields (start, end) in variant filter.
// Verify the format of Chromosome position
//
function validateForm(){
  var start = document.forms["filters_form"].elements["start"].value
  var end = document.forms["filters_form"].elements["end"].value
  if(start || end){
    if(!chrom){
      alert("Chromosome field is required");
      return false;
    }
    else if (chrom.length > 1) {
    	alert("Start and end coordinates are only applicable with one selected chromosome");
		}
    else if( !start || !end){
      alert("Both start and end coordinates are required");
      return false;
    }
    else if( (isNaN(start) || isNaN(end)) || Number(end)<Number(start) ){
      alert("Coordinate field not valid");
      return false;
    }
  }
  validateChromPos();
  // Avoid page spinner being stuck on Filter and export variants option
  $(window).unbind('beforeunload');
  return true;
}

/* exported initSearchConstraints */
// syncSearchConstraints(selectorId:HTML-selector, textId:HTML-textfield)
//
// Initialize and synchronize 'startelem' and 'cyto_start', used for setting
// constraints when searching variants in cytoband.
function initSearchConstraints(selectorId, textId){
    console.log("init cytoband search: selector and text")
    selectorId.addEventListener("change", function() {
        if(selectorId.options[selectorId.selectedIndex].value === ""){
            textId.value = "";
            return
        }
        // populate textfield
        textId.value = selectorId.options[selectorId.selectedIndex].value
    });
}

/* exported enableDismiss */
function enableDismiss(){
  // before enabling the variant dismiss button
  var selectElem = document.getElementById("dismiss_choices");
  // make sure that user selects at least one dismiss reason
  var selectedOptions = false;
  for (var i = 0; i < selectElem.length; i++) {
      if (selectElem.options[i].selected){
        selectedOptions = true;
        break;
      }
  }
  // make sure that at least one checkbox corresponding to a variant is checked
  var variantCheckboxes = document.getElementsByName("dismiss");
  var checkedVars=false
  for (var i = 0; i < variantCheckboxes.length; i++) {
      if (variantCheckboxes[i].checked){
        checkedVars = true;
        break;
      }
  }
  var btnElem = document.getElementById("dismiss_submit");
  if (selectedOptions & checkedVars) {
    btnElem.disabled = false;
  }
  else{
    btnElem.disabled = true;
  }
}

/* exported eraseChromPosString() */
function eraseChromPosString() {
  // Erase content of chrom_pos field
  document.forms["filters_form"].elements["chrom_pos"].value = "";
}

/* exported updatedChromPosInput() */
// Link chromosome position input field with chromosome and cytoband dropdowns.
// Changes in chrom_pos input are reflected in chrom, start and end fields
function updatedChromPosInput() {
	console.log('Update to chrom pos field detected!')
	var chromSel = document.forms["filters_form"].elements["chrom"];
	var chromPos = document.forms["filters_form"].elements["chrom_pos"];
	var chromPosPattern = "^(?:chr)?([1-9]|1[0-9]|2[0-2]|X|Y|MT)(?::([0-9]+)-([0-9]+)([+-]{1})?([0-9]+)?)?$";
	// parse chromosome position info
	var chrName, startPos, endPos, sign, padding;
	try {
		[_, chrName, startPos, endPos, sign, padding] = chromPos.value.replaceAll(',', '').match(chromPosPattern);
		console.log(`Parsing ChrPos: ${chrName}, coord: ${startPos}-${endPos}, padding: ${sign}${padding}`)
	} catch (err) {
		console.log('ChrPos empty')
		return
	}

	console.log(chrName)
	// set default padding and sign
	padding = padding != null ? padding : 0
	sign = sign != null ? sign : '+'
	// Update start and end input fields
	if (startPos != null) {
		// invert sign expand before starting position
		var newStartPos = eval(`${startPos} ${sign == '+' ? '-' : '+' } ${padding}`);
		newStartPos = newStartPos < 0 ? 0 : newStartPos
		document.forms["filters_form"].elements["start"].value = newStartPos;
	}
	if (endPos != null) {
		var newEndPos = eval(`${endPos} ${sign} ${padding}`);
		newEndPos = newEndPos < 0 ? 0 : newEndPos
		document.forms["filters_form"].elements["end"].value = newEndPos;
	}
	if (chrName != null) {
		// select this chromosome ONLY in chromSel dropdown
  	for (var i = 0; i < chromSel.length; i++) {
  		if (chromSel.options[i].value !== chrName) {
				chromSel.options[i].selected = false;
			} else {
  			chromSel.options[i].selected = true;
			}
		}
	} else {
		console.log(`ChrPos regexp not matching ${chromPos.value} - chrName empty`)
		// select the all chromosomes option ONLY in chromSel dropdown
  	for (var i = 0; i < chromSel.length; i++) {
  		if (chromSel.options[i].value !== "") {
				chromSel.options[i].selected = false;
			} else {
  			chromSel.options[i].selected = true;
			}
		}
	}
}

<|endoftext|>