<commit_before>EditableListItem = ListItem.extend({
	_types: 'list',

	getEditForm: function(object, id, width, force) {
		var form = EditForm.get(object, force);
		// Update the edit form's variablePrefix to group by this
		// edit item.
		// TODO: Since we're using cached forms, this means we cannot use
		// the same form elsewhere at the same time.
		if (id == null)
			id = object._id;
		form.entryId = id;
		// Only shrink if it does not fit. It might be the user has already
		// taken care of it.
		if (form.getWidth() >= width)
			form.setWidth(this.form.getInnerWidth(width, this.padding));
		form.variablePrefix = this.getEditName() + '_' + id + '_';
		return form;
	},

	getAddPrototypeButton: function(baseForm, name, param) {
		if (!this.chooser) {
			// In order to avoid endless recursion, we are first setting
			// the chooser object to the valid name.
			// Since getPrototypeChooserList calls getAddPrototypeButton through
			// renderEntry again, this would lead to an endless recursion otherwise.
			this.chooser = { name: name + '_chooser' };
			this.chooser.list = this.getPrototypeChooserList(baseForm, function(ctor) {
				// Create an empty instance in order to render small edit form:
				// TODO: Cache created instances and produced html somehow?
				// Can we use bootstrap's internal version number?
				// Should this be exposed through a method on Function even?
				// Or simpy caching within 'this'...
				var html = this.renderEntry(baseForm, name, new ctor(), {
					add: param.add, width: param.width,
					id: '{%' + name + '_id%}'
				});
				return baseForm.renderHandle('list_add', name, html,
					'{%' + name + '_entry_id%}');
			});
			/*
			var t = Date().now();
			User.log(Date().now() - t);
			*/
		}
		return this.getPrototypeChooserButton(baseForm, {
			name: name + (param.entryId ? '_' + param.entryId : ''),
			value: param.value
		}, this.chooser);
	},

	renderEntry: function(baseForm, name, object, param, out) {
		// Force a newly created form each time we're rendering
		var form = this.getEditForm(object, param.id, param.width, true);
		return baseForm.renderTemplate('listItem#entry', {
			id: name + '_' + form.entryId,
			name: name,
			proto: object._prototype,
			hide: object.visible !== undefined
					// Default for new items is visible.
					? object.visible == null || object.visible ? 0 : 1
					: null,
			width: param.width,
			create: object.isTransient(),
			sortable: this.sortable,
			items: form.renderItems(baseForm, {
				itemsOnly: true
			}),
			addHandler: this.addEntries && this.getAddPrototypeButton(baseForm, name, {
				width: param.width,
				entryId: form.entryId
			}).onClick
		}, out);
	},

	render: function(baseForm, name, value, param, out) {
		// Add the button only once to the form!
		// TODO: Rename addButton to better name?
		if (this.addButton && !this.initialized) {
			var button = this.getAddPrototypeButton(baseForm, name, {
				width: param.calculatedWidth,
				value: this.addButton
			});
			baseForm.addButtons(button);
			this.initialized = true;
		}
		var entries = [];
		var ids = [];
		var list = this.collection.list();
		for (var i = 0; i < list.length; i++) {
			var obj = list[i];
			entries.push(this.renderEntry(baseForm, name, obj, {
				width: param.calculatedWidth
			}));
			ids.push(obj._id);
		}
		baseForm.renderTemplate('listItem#list', {
			name: name,
			addEntries: this.addEntries,
			addHandler: this.addEntries && this.getAddPrototypeButton(baseForm, name, {
				width: param.calculatedWidth
			}).onClick,
			chooser: this.chooser, // needs to come after getAddPrototypeButton!
			entries: entries.join(''),
			ids: ids
		}, out);
	},

	convert: function(value) {
		// Make sure apply gets called
		return value;
	},

	apply: function(value) {
		var changed = false;
		var name = this.getEditName();
		// Scan through all values and group by id
		var create = {};
		var applied = {};
		// Link ids to entries, also for newly created ones (n*)
		var entries = {};
		for (var key in req.data) {
			if (key.startsWith(name)) {
				var rest = key.substring(name.length + 1);
				var pos = rest.indexOf('_');
				var id = rest.substring(0, pos);
				if (/^n/.test(id)) { // Create
					User.log('Create', id);
					// Group values and process later.
					// This is needed by the onCreate handler that
					// can produce an object based on e.g. file type
					var variable = rest.substring(pos + 1);
					var values = create[id];
					if (!values)
						values = create[id] = {};
					values[variable] = req.data[key];
				} else if (!applied[id]) { // Apply
					// Mark this object as applied once one field was found,
					// since form handles the rest.
					applied[id] = true;
					// Just pass this through the form. No further
					// grouping is needed.
					var obj = this.collection.getById(id);
					if (obj) {
						var prefix = name + '_' + id + '_';
						if (req.data[prefix + 'remove'] == 1) {
							changed = obj.remove();
						} else {
							var form = this.getEditForm(obj);
							if (form)
								changed = form.applyItems() || changed;
							// Set the object's visibility and position
							var visible = Base.pick(req.data[prefix + 'hide'], 0) == 0;
							entries[id] = { object: obj, visible: visible };
						}
					}
				}
			}
		}
		// Now produce the new items
		// Determine prototype in case onCreate does not produce the item.
		var prototypes = this.getPrototypes();
		var ctor = prototypes[0];
		for (var id in create) {
			var values = create[id];
			if (values.remove != 1) {
				// Pass prototype value extra, as ctor
				var proto = values.proto;
				delete values.proto;
				var ctor = global[proto];
				if (!prototypes.contains(ctor))
					throw 'Unsupported prototype: ' + proto;
				// Support an onCreate handler that can produce special types
				// e.g. based on the file type. That's also the only reason
				// why we collect all values above, so that onCreate can analyse them.
				var obj = this.onCreate && this.onCreate(proto, values);
				if (!obj)
					obj = new ctor(this); // Pass the edit item for editing parent stuff.
				if (obj) {
					// Just like in the rest of edit lib, apply first, persist after
					var form = this.getEditForm(obj, id);
					if (form) {
						form.applyItems();
						changed = this.store(obj) || changed;
						entries[id] = { object: obj, visible: true };
					}
				}
			}
		}
		// Now set positions. It is important that this happens in proper sequence,
		// in case we're modifying memory-only HopObjects through addAt...
		(value || '').split(',').each(function(id, index) {
			var entry = entries[id];
			if (entry)
				changed = this.setPosition(entry.object, index, entry.visible) || changed;
		}, this);
		return changed;
	}
});
<commit_msg>Create temporary objects correctly by passing the item to them, so they know about their editing context. This is part one of making more complex elements such as the CropButton to work in editable lists.<commit_after>EditableListItem = ListItem.extend({
	_types: 'list',

	getEditForm: function(object, id, width, force) {
		var form = EditForm.get(object, force);
		// Update the edit form's variablePrefix to group by this
		// edit item.
		// TODO: Since we're using cached forms, this means we cannot use
		// the same form elsewhere at the same time.
		if (id == null)
			id = object._id;
		form.entryId = id;
		// Only shrink if it does not fit. It might be the user has already
		// taken care of it.
		if (form.getWidth() >= width)
			form.setWidth(this.form.getInnerWidth(width, this.padding));
		form.variablePrefix = this.getEditName() + '_' + id + '_';
		return form;
	},

	getAddPrototypeButton: function(baseForm, name, param) {
		if (!this.chooser) {
			// In order to avoid endless recursion, we are first setting
			// the chooser object to the valid name.
			// Since getPrototypeChooserList calls getAddPrototypeButton through
			// renderEntry again, this would lead to an endless recursion otherwise.
			this.chooser = { name: name + '_chooser' };
			this.chooser.list = this.getPrototypeChooserList(baseForm, function(ctor) {
				// Create an empty instance in order to render small edit form:
				// TODO: Cache created instances and produced html somehow?
				// Can we use bootstrap's internal version number?
				// Should this be exposed through a method on Function even?
				// Or simpy caching within 'this'...
				var html = this.renderEntry(baseForm, name, new ctor(this), {
					add: param.add, width: param.width,
					id: '{%' + name + '_id%}'
				});
				return baseForm.renderHandle('list_add', name, html,
					'{%' + name + '_entry_id%}');
			});
			/*
			var t = Date().now();
			User.log(Date().now() - t);
			*/
		}
		return this.getPrototypeChooserButton(baseForm, {
			name: name + (param.entryId ? '_' + param.entryId : ''),
			value: param.value
		}, this.chooser);
	},

	renderEntry: function(baseForm, name, object, param, out) {
		// Force a newly created form each time we're rendering
		var form = this.getEditForm(object, param.id, param.width, true);
		return baseForm.renderTemplate('listItem#entry', {
			id: name + '_' + form.entryId,
			name: name,
			proto: object._prototype,
			hide: object.visible !== undefined
					// Default for new items is visible.
					? object.visible == null || object.visible ? 0 : 1
					: null,
			width: param.width,
			create: object.isTransient(),
			sortable: this.sortable,
			items: form.renderItems(baseForm, {
				itemsOnly: true
			}),
			addHandler: this.addEntries && this.getAddPrototypeButton(baseForm, name, {
				width: param.width,
				entryId: form.entryId
			}).onClick
		}, out);
	},

	render: function(baseForm, name, value, param, out) {
		// Add the button only once to the form!
		// TODO: Rename addButton to better name?
		if (this.addButton && !this.initialized) {
			var button = this.getAddPrototypeButton(baseForm, name, {
				width: param.calculatedWidth,
				value: this.addButton
			});
			baseForm.addButtons(button);
			this.initialized = true;
		}
		var entries = [];
		var ids = [];
		var list = this.collection.list();
		for (var i = 0; i < list.length; i++) {
			var obj = list[i];
			entries.push(this.renderEntry(baseForm, name, obj, {
				width: param.calculatedWidth
			}));
			ids.push(obj._id);
		}
		baseForm.renderTemplate('listItem#list', {
			name: name,
			addEntries: this.addEntries,
			addHandler: this.addEntries && this.getAddPrototypeButton(baseForm, name, {
				width: param.calculatedWidth
			}).onClick,
			chooser: this.chooser, // needs to come after getAddPrototypeButton!
			entries: entries.join(''),
			ids: ids
		}, out);
	},

	convert: function(value) {
		// Make sure apply gets called
		return value;
	},

	apply: function(value) {
		var changed = false;
		var name = this.getEditName();
		// Scan through all values and group by id
		var create = {};
		var applied = {};
		// Link ids to entries, also for newly created ones (n*)
		var entries = {};
		for (var key in req.data) {
			if (key.startsWith(name)) {
				var rest = key.substring(name.length + 1);
				var pos = rest.indexOf('_');
				var id = rest.substring(0, pos);
				if (/^n/.test(id)) { // Create
					User.log('Create', id);
					// Group values and process later.
					// This is needed by the onCreate handler that
					// can produce an object based on e.g. file type
					var variable = rest.substring(pos + 1);
					var values = create[id];
					if (!values)
						values = create[id] = {};
					values[variable] = req.data[key];
				} else if (!applied[id]) { // Apply
					// Mark this object as applied once one field was found,
					// since form handles the rest.
					applied[id] = true;
					// Just pass this through the form. No further
					// grouping is needed.
					var obj = this.collection.getById(id);
					if (obj) {
						var prefix = name + '_' + id + '_';
						if (req.data[prefix + 'remove'] == 1) {
							changed = obj.remove();
						} else {
							var form = this.getEditForm(obj);
							if (form)
								changed = form.applyItems() || changed;
							// Set the object's visibility and position
							var visible = Base.pick(req.data[prefix + 'hide'], 0) == 0;
							entries[id] = { object: obj, visible: visible };
						}
					}
				}
			}
		}
		// Now produce the new items
		// Determine prototype in case onCreate does not produce the item.
		var prototypes = this.getPrototypes();
		var ctor = prototypes[0];
		for (var id in create) {
			var values = create[id];
			if (values.remove != 1) {
				// Pass prototype value extra, as ctor
				var proto = values.proto;
				delete values.proto;
				var ctor = global[proto];
				if (!prototypes.contains(ctor))
					throw 'Unsupported prototype: ' + proto;
				// Support an onCreate handler that can produce special types
				// e.g. based on the file type. That's also the only reason
				// why we collect all values above, so that onCreate can analyse them.
				var obj = this.onCreate && this.onCreate(proto, values);
				if (!obj)
					obj = new ctor(this); // Pass the edit item for editing parent stuff.
				if (obj) {
					// Just like in the rest of edit lib, apply first, persist after
					var form = this.getEditForm(obj, id);
					if (form) {
						form.applyItems();
						changed = this.store(obj) || changed;
						entries[id] = { object: obj, visible: true };
					}
				}
			}
		}
		// Now set positions. It is important that this happens in proper sequence,
		// in case we're modifying memory-only HopObjects through addAt...
		(value || '').split(',').each(function(id, index) {
			var entry = entries[id];
			if (entry)
				changed = this.setPosition(entry.object, index, entry.visible) || changed;
		}, this);
		return changed;
	}
});
<|endoftext|>


<commit_before>/* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */

/* jshint devel: true, browser: true, jquery: true, shadow: true,
          unused: true, undef: true, strict: true, esversion: 6 */

var EB_Template = (function () {

    "use strict"; // use latest ECMAScript standard
    var jQ = jQuery.noConflict(); // '$' released, jQ + jQuery can be used
    // jQuery in EB_Template needed only for "offset().top"  TODO: make vanillaJS version

    var monsterHeaderType = {
        desktop: false, // height 159px
        mobile: false,  // height 62px
        landingpage: false  // height 37px
    };
    var //timeoutIDwindowScroll = null,
        timeoutIDwindowResize = null;

    window.requestAnimFrame = (function() {
        // SHIM for RAF, paulirish.com/2011/requestanimationframe-for-smart-animating/
        return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        function(callback){ window.setTimeout(callback, 1000 / 60); };
    })();

    function scrollToId(css_id, correction, easingMethod) {
        var easingMethod = easingMethod || "easeOutSine";
        var correction = correction || 0;
        var el = document.getElementById(css_id);

        var scrollToY = function(scrollTargetY, speed, easing) {
        /*  Source: http://stackoverflow.com/a/26808520/5986007
            speed: time in pixels per second */
            var scrollY = window.scrollY,
                scrollTargetY = scrollTargetY || 0,
                speed = speed || 2000,
                easing = easing || 'easeOutSine',
                currentTime = 0;
            // min time .1, max time .8 seconds
            var time = Math.max(0.1, Math.min(Math.abs(scrollY - scrollTargetY) / speed, 0.8));
            // easing equations from https://github.com/danro/easing-js/blob/master/easing.js
            var easingEquations = {
                easeOutSine: function(pos) { return Math.sin(pos * (Math.PI / 2)); },
                easeInOutSine: function(pos) { return (-0.5 * (Math.cos(Math.PI * pos) - 1)); },
                easeInOutQuint: function(pos) {
                    if ((pos /= 0.5) < 1) { return 0.5 * Math.pow(pos, 5); }
                    return 0.5 * (Math.pow((pos - 2), 5) + 2);
                }
            };
            function tick() { // add animation loop
                currentTime += 1 / 60;
                var p = currentTime / time;
                var t = easingEquations[easing](p);
                if (p < 1) {
                    window.requestAnimFrame(tick);
                    window.scrollTo(0, scrollY + ((scrollTargetY - scrollY) * t));
                } else {
                    window.scrollTo(0, scrollTargetY);
                    //console.info('scrollToY done');
                }
            }
            tick(); // call it once to get started
        }; // end scrollToY
/*
        var getOffset = function(elem) {
            // http://stackoverflow.com/a/442474/5986007 + https://www.kirupa.com/html5/get_element_position_using_javascript.htm
            var _x = 0;
            var _y = 0;
            while (elem && !isNaN(elem.offsetLeft) && !isNaN(elem.offsetTop)) {
                _x += elem.offsetLeft - elem.scrollLeft; // + elem.clientLeft
                _y += elem.offsetTop - elem.scrollTop; // + elem.clientTop
                elem = elem.offsetParent;
            }
            return {top: _y, left: _x};
        }; // TODO: getOffset bugfix - DIFFERENT VALUES IN FF vs Chrome!
*/
        if (el) {
            //scrollToY( getOffset(el).top + correction, 500, easingMethod);
            scrollToY( jQ(el).offset().top + correction, 500, easingMethod);
            // TODO: remove the need for jQuery here!
        }
    } // end scrollToId

    function idGetCss(s_elementId, s_styleProp) {
        var element = document.getElementById(s_elementId);
        return window.getComputedStyle(element, null).getPropertyValue(s_styleProp);
    }

    function idSetCss(s_elementId, s_styleName, s_styleProp) {
    /* styleNames are CSS2Properties:  backgroundColor, fontSize ... */
        document.getElementById(s_elementId).style[s_styleName] = s_styleProp;
    }

    function addClassAll(myQuery, className) {
        var nodeList = document.querySelectorAll(myQuery);
        // classList in IE 10+, nodeList as result from querySelectorAll etc.
        for (var i = 0; i < nodeList.length; i++) { nodeList[i].classList.add(className); }
    }

    function removeClassAll(myQuery, className) {
        var nodeList = document.querySelectorAll(myQuery);
        for (var i = 0; i < nodeList.length; i++) { nodeList[i].classList.remove(className); }
    }

    function toggleClassAll(myQuery, className) {
        var nodeList = document.querySelectorAll(myQuery);
        for (var i = 0; i < nodeList.length; i++) { nodeList[i].classList.toggle(className); }
    }

    function toggleMobileMenu() {
        if (idGetCss("navigation", "display") === "none") {
            idSetCss("navigation", "display", "block");
            idSetCss("screenShade", "display", "block");
            addClassAll("#mobileButton", "pressed");
        } else {
            idSetCss("navigation", "display", "none");
            idSetCss("screenShade", "display", "none");
            removeClassAll("#mobileButton", "pressed");
        }
    }

    function navButtonClick(buttonIndex) {
        var i;
        var tabContent = document.querySelectorAll(".containerIA .tabContent");
        var yCorrection = monsterHeaderType.mobile ? -60 : 0;

        removeClassAll("#navigation button", "active");
        document.querySelectorAll("#navigation button")[buttonIndex].classList.add("active");

        for (i=0; i < tabContent.length; i++) {
            tabContent[i].style.display = "none";
        }
        tabContent[buttonIndex].style.display = "block";

        if (idGetCss("mobileButtonWrapper", "display") === "block") {
            toggleMobileMenu();
            scrollToId("hook", yCorrection);
        }
    } // navButtonClick()

    function contentClick() {
        if (idGetCss("mobileButtonWrapper", "display") === "block") {
            idSetCss("navigation", "display", "none");
            idSetCss("screenShade", "display", "none");
            removeClassAll("#mobileButton", "pressed");
        }
    }

    function onWindowResize() {

        function windowResizeAction() {
            if (idGetCss("mobileButtonWrapper", "display") === "block") {
                idSetCss("navigation", "display", "none");
            } else {
                idSetCss("navigation", "display", "block");
            }
        }

        if (timeoutIDwindowResize) { window.clearTimeout(timeoutIDwindowResize); }
        timeoutIDwindowResize = window.setTimeout(windowResizeAction, 50);
    }

/*
    function onWindowScroll() {
        function windowScrollAction() {
            // executed only 1x every 30ms, but only the very last timeout fires this function, because all previous timeouts are being discarded with every new scroll event
            if (window.pageYOffset > 37) {
                // do something ...
            }
        }
        if (timeoutIDwindowScroll) { window.clearTimeout(timeoutIDwindowScroll); }
        timeoutIDwindowScroll = window.setTimeout(windowScrollAction, 30);
    } // onWindowScroll()
*/

    function urlParameterSwitchTab() {
    /* switches to specific tab directly, if URL parameter "tab" is found */
        var params = {}, // pairs of  key - value (name - value)
            i;

        if (location.search) {
            var a_parts = location.search.substring(1).split('&');
            // "a_parts" will be eg. ["tab=2", "bla=text", "val="]
            for (i = 0; i < a_parts.length; i++) {
                var nv = a_parts[i].split('=');
                console.log(nv);
                if (!nv[0]) { continue; } // skip cases like "=25" with no 'key'
                params[nv[0]] = nv[1] || true; // 'true' if no value was provided
            }
        }
        if (params.tab) {
            document.querySelectorAll("#navigation button")[params.tab].click();
        }
    }

    function addEvents() {
        var i;
        var navItems = document.querySelectorAll("#navigation button");

        for (i=0; i < navItems.length; i++) {
            // stackoverflow.com/questions/35775562/get-index-of-child-with-event-currenttarget
            navItems[i].addEventListener("click", navButtonClick.bind(null, i));
        }
        document.getElementById("mobileButtonWrapper").addEventListener("click", toggleMobileMenu);
        document.querySelector(".containerIA .content").addEventListener("click", contentClick);
        document.getElementById("screenShade").addEventListener("click", contentClick);
        window.addEventListener("resize", onWindowResize);
    }

    function monsterBugFixes() {
        /* if (monsterHeaderType.desktop) {
            // Monster Redux bug fix (EB page scrolling with Javascript)
            document.getElementsByTagName("html")[0].setAttribute(
                "style", "height:auto !important; overflow:auto !important");
            document.getElementsByTagName("body")[0].setAttribute(
                "style", "height:auto !important; overflow:hidden !important");
            // following corrects the position of desktop-redux footer
            var afh = document.querySelector(".AppliesFooterHolder");
            if (afh) { afh.setAttribute('style', 'margin-top: 0 !important'); }
        } */
        if (monsterHeaderType.mobile) {
            // Monster mobile-Redux padding 0 for correct mobile navigation alignment
            var jvb = document.getElementById("jobViewBody");
            var uibox = document.querySelector("#jobViewBody .ui-box");
            if (jvb) { jvb.setAttribute("style", "padding: 0"); }
            if (uibox) { uibox.setAttribute("style", "padding: 0"); }
        }
    }

    function getHeaderType() {
        monsterHeaderType.desktop = !!document.getElementById("monsterAppliesPageWrapper");
        monsterHeaderType.mobile = !!document.getElementById("jobViewBody");
        monsterHeaderType.landingpage = !!document.getElementById("myheader_content");
    }

    function initAllTabs() {
        var i;
        var tabContents = document.querySelectorAll(".containerIA .tabContent");
        for (i = 0; i < tabContents.length; i++) {
            tabContents[i].style.display = "none";
        }
        if (monsterHeaderType.mobile) {
            idSetCss("mobileButtonWrapper", "top", "62px");
            idSetCss("navigation", "top", "62px");
        }
        if (monsterHeaderType.landingpage && idGetCss("navigation", "position") === "fixed") {
            // if we have a fixed menu, we need a 'sticky' emulation for landingpages
            // css3 "position: sticky" currently works only in Firefox (44)
            idSetCss("navigation", "top", "38px");
        }
        //document.querySelector("#navigation button").click(); // directly click on 1st item
        tabContents[0].style.display = "block";
        document.querySelector("#navigation button").classList.add("active"); // activate first button
    }

    function startTemplate() {
        getHeaderType();
        onWindowResize(); // show #navigation if #mobileButtonWrapper is hidden
        addEvents();
        initAllTabs();
        monsterBugFixes();
        urlParameterSwitchTab();
    }

    document.addEventListener("DOMContentLoaded", startTemplate);

    return {
        /*publicProperty: "test",
        publicMethod: function() {},*/
        monsterHeaderType: monsterHeaderType,
        idGetCss: idGetCss,
        idSetCss: idSetCss,
        navButtonClick: navButtonClick,
        version: "1.1"
    };

})(); // end EB_Template

/* @license-end */
<commit_msg>Update EB-template.fnp.js<commit_after>/* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later */

/* jshint devel: true, browser: true, jquery: true, shadow: true,
          unused: true, undef: true, strict: true, esversion: 6 */

var EB_Template = (function () {

    "use strict"; // use latest ECMAScript standard
    var jQ = jQuery.noConflict(); // '$' released, jQ + jQuery can be used
    // jQuery in EB_Template needed only for "offset().top"  TODO: make vanillaJS version

    var monsterHeaderType = {
        desktop: false, // height 159px
        mobile: false,  // height 62px
        landingpage: false  // height 37px
    };
    var //timeoutIDwindowScroll = null,
        timeoutIDwindowResize = null;

    window.requestAnimFrame = (function() {
        // SHIM for RAF, paulirish.com/2011/requestanimationframe-for-smart-animating/
        return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        function(callback){ window.setTimeout(callback, 1000 / 60); };
    })();

    function scrollToId(css_id, correction, easingMethod) {
        var easingMethod = easingMethod || "easeOutSine";
        var correction = correction || 0;
        var el = document.getElementById(css_id);

        var scrollToY = function(scrollTargetY, speed, easing) {
        /*  Source: http://stackoverflow.com/a/26808520/5986007
            speed: time in pixels per second */
            var scrollY = window.scrollY,
                scrollTargetY = scrollTargetY || 0,
                speed = speed || 2000,
                easing = easing || 'easeOutSine',
                currentTime = 0;
            // min time .1, max time .8 seconds
            var time = Math.max(0.1, Math.min(Math.abs(scrollY - scrollTargetY) / speed, 0.8));
            // easing equations from https://github.com/danro/easing-js/blob/master/easing.js
            var easingEquations = {
                easeOutSine: function(pos) { return Math.sin(pos * (Math.PI / 2)); },
                easeInOutSine: function(pos) { return (-0.5 * (Math.cos(Math.PI * pos) - 1)); },
                easeInOutQuint: function(pos) {
                    if ((pos /= 0.5) < 1) { return 0.5 * Math.pow(pos, 5); }
                    return 0.5 * (Math.pow((pos - 2), 5) + 2);
                }
            };
            function tick() { // add animation loop
                currentTime += 1 / 60;
                var p = currentTime / time;
                var t = easingEquations[easing](p);
                if (p < 1) {
                    window.requestAnimFrame(tick);
                    window.scrollTo(0, scrollY + ((scrollTargetY - scrollY) * t));
                } else {
                    window.scrollTo(0, scrollTargetY);
                    //console.info('scrollToY done');
                }
            }
            tick(); // call it once to get started
        }; // end scrollToY
/*
        var getOffset = function(elem) {
            // http://stackoverflow.com/a/442474/5986007 + https://www.kirupa.com/html5/get_element_position_using_javascript.htm
            var _x = 0;
            var _y = 0;
            while (elem && !isNaN(elem.offsetLeft) && !isNaN(elem.offsetTop)) {
                _x += elem.offsetLeft - elem.scrollLeft; // + elem.clientLeft
                _y += elem.offsetTop - elem.scrollTop; // + elem.clientTop
                elem = elem.offsetParent;
            }
            return {top: _y, left: _x};
        }; // TODO: getOffset bugfix - DIFFERENT VALUES IN FF vs Chrome!
*/
        if (el) {
            //scrollToY( getOffset(el).top + correction, 500, easingMethod);
            scrollToY( jQ(el).offset().top + correction, 500, easingMethod);
            // TODO: remove the need for jQuery here!
        }
    } // end scrollToId

    function idGetCss(s_elementId, s_styleProp) {
        var element = document.getElementById(s_elementId);
        return window.getComputedStyle(element, null).getPropertyValue(s_styleProp);
    }

    function idSetCss(s_elementId, s_styleName, s_styleProp) {
    /* styleNames are CSS2Properties:  backgroundColor, fontSize ... */
        document.getElementById(s_elementId).style[s_styleName] = s_styleProp;
    }

    function addClassAll(myQuery, className) {
        var nodeList = document.querySelectorAll(myQuery);
        // classList in IE 10+, nodeList as result from querySelectorAll etc.
        for (var i = 0; i < nodeList.length; i++) { nodeList[i].classList.add(className); }
    }

    function removeClassAll(myQuery, className) {
        var nodeList = document.querySelectorAll(myQuery);
        for (var i = 0; i < nodeList.length; i++) { nodeList[i].classList.remove(className); }
    }

    function toggleClassAll(myQuery, className) {
        var nodeList = document.querySelectorAll(myQuery);
        for (var i = 0; i < nodeList.length; i++) { nodeList[i].classList.toggle(className); }
    }

    function toggleMobileMenu() {
        if (idGetCss("navigation", "display") === "none") {
            idSetCss("navigation", "display", "block");
            idSetCss("screenShade", "display", "block");
            addClassAll("#mobileButton", "pressed");
        } else {
            idSetCss("navigation", "display", "none");
            idSetCss("screenShade", "display", "none");
            removeClassAll("#mobileButton", "pressed");
        }
    }

    function navButtonClick(buttonIndex, expandMobileMenu /* default true (MouseEvent click) */) {
        var i;
        var tabContent = document.querySelectorAll(".containerIA .tabContent");
        var yCorrection = monsterHeaderType.mobile ? -60 : 0;
        var expandMobileMenu = !!expandMobileMenu;

        removeClassAll("#navigation button", "active");
        document.querySelectorAll("#navigation button")[buttonIndex].classList.add("active");

        for (i=0; i < tabContent.length; i++) {
            tabContent[i].style.display = "none";
        }
        tabContent[buttonIndex].style.display = "block";

        if (idGetCss("mobileButtonWrapper", "display") === "block") {
            if (expandMobileMenu) { toggleMobileMenu(); }
            scrollToId("hook", yCorrection);
        }
    } // navButtonClick()

    function contentClick() {
        if (idGetCss("mobileButtonWrapper", "display") === "block") {
            idSetCss("navigation", "display", "none");
            idSetCss("screenShade", "display", "none");
            removeClassAll("#mobileButton", "pressed");
        }
    }

    function onWindowResize() {

        function windowResizeAction() {
            if (idGetCss("mobileButtonWrapper", "display") === "block") {
                idSetCss("navigation", "display", "none");
            } else {
                idSetCss("navigation", "display", "block");
            }
        }

        if (timeoutIDwindowResize) { window.clearTimeout(timeoutIDwindowResize); }
        timeoutIDwindowResize = window.setTimeout(windowResizeAction, 50);
    }

/*
    function onWindowScroll() {
        function windowScrollAction() {
            // executed only 1x every 30ms, but only the very last timeout fires this function, because all previous timeouts are being discarded with every new scroll event
            if (window.pageYOffset > 37) {
                // do something ...
            }
        }
        if (timeoutIDwindowScroll) { window.clearTimeout(timeoutIDwindowScroll); }
        timeoutIDwindowScroll = window.setTimeout(windowScrollAction, 30);
    } // onWindowScroll()
*/

    function urlParameterSwitchTab() {
    /* switches to specific tab directly, if URL parameter "tab" is found */
        var params = {}, // pairs of  key - value (name - value)
            i;

        if (location.search) {
            var a_parts = location.search.substring(1).split('&');
            // "a_parts" will be eg. ["tab=2", "bla=text", "val="]
            for (i = 0; i < a_parts.length; i++) {
                var nv = a_parts[i].split('=');
                console.log(nv);
                if (!nv[0]) { continue; } // skip cases like "=25" with no 'key'
                params[nv[0]] = nv[1] || true; // 'true' if no value was provided
            }
        }
        if (params.tab) {
            document.querySelectorAll("#navigation button")[params.tab].click();
        }
    }

    function addEvents() {
        var i;
        var navItems = document.querySelectorAll("#navigation button");

        for (i=0; i < navItems.length; i++) {
            // stackoverflow.com/questions/35775562/get-index-of-child-with-event-currenttarget
            navItems[i].addEventListener("click", navButtonClick.bind(null, i));
        }
        document.getElementById("mobileButtonWrapper").addEventListener("click", toggleMobileMenu);
        document.querySelector(".containerIA .content").addEventListener("click", contentClick);
        document.getElementById("screenShade").addEventListener("click", contentClick);
        window.addEventListener("resize", onWindowResize);
    }

    function monsterBugFixes() {
        /* if (monsterHeaderType.desktop) {
            // Monster Redux bug fix (EB page scrolling with Javascript)
            document.getElementsByTagName("html")[0].setAttribute(
                "style", "height:auto !important; overflow:auto !important");
            document.getElementsByTagName("body")[0].setAttribute(
                "style", "height:auto !important; overflow:hidden !important");
            // following corrects the position of desktop-redux footer
            var afh = document.querySelector(".AppliesFooterHolder");
            if (afh) { afh.setAttribute('style', 'margin-top: 0 !important'); }
        } */
        if (monsterHeaderType.mobile) {
            // Monster mobile-Redux padding 0 for correct mobile navigation alignment
            var jvb = document.getElementById("jobViewBody");
            var uibox = document.querySelector("#jobViewBody .ui-box");
            if (jvb) { jvb.setAttribute("style", "padding: 0"); }
            if (uibox) { uibox.setAttribute("style", "padding: 0"); }
        }
    }

    function getHeaderType() {
        monsterHeaderType.desktop = !!document.getElementById("monsterAppliesPageWrapper");
        monsterHeaderType.mobile = !!document.getElementById("jobViewBody");
        monsterHeaderType.landingpage = !!document.getElementById("myheader_content");
    }

    function initAllTabs() {
        var i;
        var tabContents = document.querySelectorAll(".containerIA .tabContent");
        for (i = 0; i < tabContents.length; i++) {
            tabContents[i].style.display = "none";
        }
        if (monsterHeaderType.mobile) {
            idSetCss("mobileButtonWrapper", "top", "62px");
            idSetCss("navigation", "top", "62px");
        }
        if (monsterHeaderType.landingpage && idGetCss("navigation", "position") === "fixed") {
            // if we have a fixed menu, we need a 'sticky' emulation for landingpages
            // css3 "position: sticky" currently works only in Firefox (44)
            idSetCss("navigation", "top", "38px");
        }
        //document.querySelector("#navigation button").click(); // directly click on 1st item
        tabContents[0].style.display = "block";
        document.querySelector("#navigation button").classList.add("active"); // activate first button
    }

    function startTemplate() {
        getHeaderType();
        onWindowResize(); // show #navigation if #mobileButtonWrapper is hidden
        addEvents();
        initAllTabs();
        monsterBugFixes();
        urlParameterSwitchTab();
    }

    document.addEventListener("DOMContentLoaded", startTemplate);

    return {
        /*publicProperty: "test",
        publicMethod: function() {},*/
        monsterHeaderType: monsterHeaderType,
        idGetCss: idGetCss,
        idSetCss: idSetCss,
        navButtonClick: navButtonClick,
        version: "1.1"
    };

})(); // end EB_Template

/* @license-end */
<|endoftext|>


<commit_before><commit_msg>Gracefully handle a user declining OAuth consent<commit_after><|endoftext|>


<commit_before><commit_msg>Remove Hidden Type from Kappa Cup 3 Meta<commit_after><|endoftext|>


<commit_before>(function($) {

  Drupal.behaviors.culturefeed_calendar_facebook_share = {
    attach: function(context, settings) {
      $('a.facebook-share', context).bind('click', function(e) {
        e.preventDefault();

        // Get default (legacy) sharer url
        var default_share_url = this.href;

        // Remove the "https://www.facebook.com/sharer/sharer.php?u="
        var dialog_share_url = default_share_url.replace('https://www.facebook.com/sharer/sharer.php?u=','');
        var dialog_share_url = decodeURIComponent(dialog_share_url);

        FB.ui(
         {
          method: 'share',
          href: dialog_share_url,
        }, function(response){});
      });
    }
  };

})(jQuery);

  window.fbAsyncInit = function() {
    //console.log('timeout finished');
    FB.init({
      appId: Drupal.settings.culturefeed.fbAppId,
      xfbml: true,
      version: 'v2.1'
    });
  };

(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) {
    return;
  }
  js = d.createElement(s);
  js.id = id;
  js.src = "//connect.facebook.net/nl_BE/sdk.js";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
<commit_msg>UIV-1947 update Facebook api to v2.9<commit_after>(function($) {

  Drupal.behaviors.culturefeed_calendar_facebook_share = {
    attach: function(context, settings) {
      $('a.facebook-share', context).bind('click', function(e) {
        e.preventDefault();

        // Get default (legacy) sharer url
        var default_share_url = this.href;

        // Remove the "https://www.facebook.com/sharer/sharer.php?u="
        var dialog_share_url = default_share_url.replace('https://www.facebook.com/sharer/sharer.php?u=','');
        var dialog_share_url = decodeURIComponent(dialog_share_url);

        FB.ui(
         {
          method: 'share',
          href: dialog_share_url,
        }, function(response){});
      });
    }
  };

})(jQuery);

  window.fbAsyncInit = function() {
    //console.log('timeout finished');
    FB.init({
      appId: Drupal.settings.culturefeed.fbAppId,
      xfbml: true,
      version: 'v2.9'
    });
  };

(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) {
    return;
  }
  js = d.createElement(s);
  js.id = id;
  js.src = "//connect.facebook.net/nl_BE/sdk.js";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
<|endoftext|>


<commit_before>var priorityMap = { 2 : 'critical', 4: 'warning', 6: 'info' };

jsonLogs = jQuery.parseJSON($('div#jsonLogs').html());

initLogs();
$('table#listLogs').tablesorter({widgets: ['zebra']});

function initLogs()
{
  $('table#listLogs').hide();
  $('.logsLoading').show();
  $('table#listLogs tr.logSum').remove();
  $('table#listLogs tr.logDetail').remove();
  $.each(jsonLogs, function(index, value) {
    var html='';
    html+='
';
    html+=' '+value.datetime+'	';
    html+=' '+priorityMap[value.priority]+'	';
    html+=' '+value.module+'	';
    html+=' '+value.shortMessage+'	';
    html+='
';
    html+='
';
    html+='
'+value.message+'
	';
    html+='
';
    $('table#listLogs').append(html);
    });

  $('table#listLogs').show();
  $('table#listLogs').trigger('update');

  $('.logsLoading').hide();

  $('table#listLogs tr.logSum').click(function() {
    showBigDialogWithContent('Log', $(this).next().html(), true);
    });
}

var dates = $("#startlog, #endlog").datepicker({
  defaultDate: "-1w",
  changeMonth: true,
  numberOfMonths: 1,
  onSelect: function(selectedDate) {
    var option = this.id == "startlog" ? "minDate" : "maxDate";
    var instance = $(this).data("datepicker");
    var date = $.datepicker.parseDate(
      instance.settings.dateFormat || $.datepicker._defaults.dateFormat,
      selectedDate, instance.settings);
    dates.not( this ).datepicker("option", option, date);
    },
  dayNamesMin: ["S", "M", "T", "W", "T", "F", "S"]
  });

$('#logSelector').ajaxForm( {beforeSubmit: validateShowlog, success: successShowlog} );

function validateShowlog(formData, jqForm, options)
{
  $('table#listLogs').hide();
  $('.logsLoading').show();
}

function successShowlog(responseText, statusText, xhr, form)
{
  try
    {
    var resp = jQuery.parseJSON(responseText);
    jsonLogs = resp.logs;
    $('#currentFilterStart').html(resp.currentFilter.start);
    $('#currentFilterEnd').html(resp.currentFilter.end);
    $('#currentFilterModule').html(resp.currentFilter.module);
    $('#currentFilterPriority').html(priorityMap[resp.currentFilter.priority]);
    }
  catch(e)
    {
    console.log(e);
    alert("An error occured.");
    return false;
    }
  initLogs();
}
<commit_msg>ENH: refs #0325. Enable row striping on log table update<commit_after>var priorityMap = { 2 : 'critical', 4: 'warning', 6: 'info' };

jsonLogs = jQuery.parseJSON($('div#jsonLogs').html());

initLogs();
$('table#listLogs').tablesorter({widgets: ['zebra']});

function initLogs()
{
  $('table#listLogs').hide();
  $('.logsLoading').show();
  $('table#listLogs tr.logSum').remove();
  $('table#listLogs tr.logDetail').remove();
  var i = 1;
  $.each(jsonLogs, function(index, value) {
    var stripeClass = i % 2 ? 'odd' : 'even';
    i++;
    var html='';
    html+='
';
    html+=' '+value.datetime+'	';
    html+=' '+priorityMap[value.priority]+'	';
    html+=' '+value.module+'	';
    html+=' '+value.shortMessage+'	';
    html+='
';
    html+='
';
    html+='
'+value.message+'
	';
    html+='
';
    $('table#listLogs').append(html);
    });
  $('table#listLogs').show();
  $('table#listLogs').trigger('update');

  $('.logsLoading').hide();

  $('table#listLogs tr.logSum').click(function() {
    showBigDialogWithContent('Log', $(this).next().html(), true);
    });
}

var dates = $("#startlog, #endlog").datepicker({
  defaultDate: "-1w",
  changeMonth: true,
  numberOfMonths: 1,
  onSelect: function(selectedDate) {
    var option = this.id == "startlog" ? "minDate" : "maxDate";
    var instance = $(this).data("datepicker");
    var date = $.datepicker.parseDate(
      instance.settings.dateFormat || $.datepicker._defaults.dateFormat,
      selectedDate, instance.settings);
    dates.not( this ).datepicker("option", option, date);
    },
  dayNamesMin: ["S", "M", "T", "W", "T", "F", "S"]
  });

$('#logSelector').ajaxForm( {beforeSubmit: validateShowlog, success: successShowlog} );

function validateShowlog(formData, jqForm, options)
{
  $('table#listLogs').hide();
  $('.logsLoading').show();
}

function successShowlog(responseText, statusText, xhr, form)
{
  try
    {
    var resp = jQuery.parseJSON(responseText);
    jsonLogs = resp.logs;
    $('#currentFilterStart').html(resp.currentFilter.start);
    $('#currentFilterEnd').html(resp.currentFilter.end);
    $('#currentFilterModule').html(resp.currentFilter.module);
    $('#currentFilterPriority').html(priorityMap[resp.currentFilter.priority]);
    }
  catch(e)
    {
    console.log(e);
    alert("An error occured.");
    return false;
    }
  initLogs();
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'foam.ui',

  name: 'Window',

  exports: [
    '$$',
    '$',
    'addStyle',
    'animate',
    'cancelAnimationFrame',
    'clearInterval',
    'clearTimeout',
    'console',
    'document',
    'dynamic',
    'error',
    'info',
    'installedModels',
    'log',
    'requestAnimationFrame',
    'setInterval',
    'setTimeout',
    'warn',
    'window',
    'as FOAMWindow'
  ],

  properties: [
    {
      name: 'registeredModels',
      factory: function() { return {}; }
    },
    {
      model_: 'StringProperty',
      name: 'name',
      defaultValue: 'window'
    },
    {
      name: 'window',
      postSet: function(_, w) {
        // TODO: This would be better if ChromeApp.js added this behaviour
        // in a SubModel of Window, ie. ChromeAppWindow
        if ( this.X.subDocument ) this.X.subDocument(w.document);

        w.X = this.Y;
        this.document = w.document;
      }
    },
    {
      name: 'document'
      // postSet to reset installedModels?
    },
    {
      name: 'installedModels',
      documentation: "Each new Window context introduces a new document and resets installedModels so models will install again in the new document.",
      factory: function() { return {}; }
    },
    {
      model_: 'BooleanProperty',
      name: 'isBackground',
      defaultValue: false
    },
    {
      name: 'console',
      lazyFactory: function() { return this.window.console; }
    },
  ],

  methods: {
    addStyle: function(css) {
      if ( ! this.document || ! this.document.createElement ) return;
      var s = this.document.createElement('style');
      s.innerHTML = css;
      this.document.head.insertBefore(
        s,
        this.document.head.firstElementChild);
    },
    log:   function() { this.console.log.apply(this.console, arguments); },
    warn:  function() { this.console.warn.apply(this.console, arguments); },
    info:  function() { this.console.info.apply(this.console, arguments); },
    error: function() { this.console.error.apply(this.console, arguments); },
    $: function(id) {
      return ( this.document.FOAM_OBJECTS && this.document.FOAM_OBJECTS[id] ) ?
        this.document.FOAM_OBJECTS[id] :
        this.document.getElementById(id);
    },
    $$: function(cls) {
      return this.document.getElementsByClassName(cls);
    },
    dynamic: function(fn, opt_fn) {
      return Events.dynamic(fn, opt_fn, this.Y);
    },
    animate: function(duration, fn, opt_interp, opt_onEnd) {
      return Movement.animate(duration, fn, opt_interp, opt_onEnd, this.Y);
    },
    setTimeout: function(f, t) {
      return this.window.setTimeout.apply(this.window, arguments);
    },
    clearTimeout: function(id) { this.window.clearTimeout(id); },
    setInterval: function(f, t) {
      return this.window.setInterval.apply(this.window, arguments);
    },
    clearInterval: function(id) { this.window.clearInterval(id); },
    requestAnimationFrame: function(f) {
      if ( this.isBackground ) return this.setTimeout(f, 16);

      console.assert(
        this.window.requestAnimationFrame,
        'requestAnimationFrame not defined');
      return this.window.requestAnimationFrame(f);
    },
    cancelAnimationFrame: function(id) {
      if ( this.isBackground ) {
        this.clearTimeout(id);
        return;
      }

      this.window.cancelAnimationFrame && this.window.cancelAnimationFrame(id);
    }
  }
});

// Using the existence of 'process' to determine that we're running in Node.
(function() {
  var w = foam.ui.Window.create(
    {
      window: window,
      name: 'DEFAULT WINDOW',
      isBackground: typeof process === 'object'
    }, X
  );
  FObject.X = X = w.Y;
})();
<commit_msg>Added 'framed' and new experimental versions of 'dynamic' to foam.ui.Window.<commit_after>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'foam.ui',

  name: 'Window',

  exports: [
    '$$',
    '$',
    'addStyle',
    'animate',
    'cancelAnimationFrame',
    'clearInterval',
    'clearTimeout',
    'console',
    'document',
    'framed',
    'dynamic',
    'dynamic2',
    'dynamic2',
    'error',
    'info',
    'installedModels',
    'log',
    'requestAnimationFrame',
    'setInterval',
    'setTimeout',
    'warn',
    'window',
    'as FOAMWindow'
  ],

  properties: [
    {
      name: 'registeredModels',
      factory: function() { return {}; }
    },
    {
      model_: 'StringProperty',
      name: 'name',
      defaultValue: 'window'
    },
    {
      name: 'window',
      postSet: function(_, w) {
        // TODO: This would be better if ChromeApp.js added this behaviour
        // in a SubModel of Window, ie. ChromeAppWindow
        if ( this.X.subDocument ) this.X.subDocument(w.document);

        w.X = this.Y;
        this.document = w.document;
      }
    },
    {
      name: 'document'
      // postSet to reset installedModels?
    },
    {
      name: 'installedModels',
      documentation: "Each new Window context introduces a new document and resets installedModels so models will install again in the new document.",
      factory: function() { return {}; }
    },
    {
      model_: 'BooleanProperty',
      name: 'isBackground',
      defaultValue: false
    },
    {
      name: 'console',
      lazyFactory: function() { return this.window.console; }
    },
  ],

  methods: {
    addStyle: function(css) {
      if ( ! this.document || ! this.document.createElement ) return;
      var s = this.document.createElement('style');
      s.innerHTML = css;
      this.document.head.insertBefore(
        s,
        this.document.head.firstElementChild);
    },
    log:   function() { this.console.log.apply(this.console, arguments); },
    warn:  function() { this.console.warn.apply(this.console, arguments); },
    info:  function() { this.console.info.apply(this.console, arguments); },
    error: function() { this.console.error.apply(this.console, arguments); },
    $: function(id) {
      return ( this.document.FOAM_OBJECTS && this.document.FOAM_OBJECTS[id] ) ?
        this.document.FOAM_OBJECTS[id] :
        this.document.getElementById(id);
    },
    $$: function(cls) {
      return this.document.getElementsByClassName(cls);
    },
    framed: function(listener) {
      return Events.dynamic(listener, this);
    },
    dynamic: function(fn, opt_fn) {
      return Events.dynamic(fn, opt_fn, this.Y);
    },
    // TODO(kgr): experimental, remove if never used
    dynamic2: function(fn) {
      var listener = this.framed(fn);
      var propertyValues = [];
      fn(); // Call once before capture to pre-latch lazy values
      Events.onGet.push(function(obj, name, value) {
        if ( arguments.callee.caller.caller !== fn ) {
          console.log('false alarm ', fn.toString());
          return;
        }
        var value = obj.propertyValue(name);
        value.addListener(listener);
        propertyValues.push(value);
      });
      var ret = fn();
      Events.onGet.pop();
      var f = function() {
        propertyValues.forEach(function(p) {
          p.removeListener(listener);
        });
      };
      f.destroy = f;
      return f;
    },
    // TODO(kgr): experimental, remove if never used
    dynamic3: function(obj, fn, opt_ret) {
      var str = fn.toString();
      var values = str.
        match(/^function[ _$\w]*\(([ ,\w]*)/)[1].
        split(',').
        map(function(name) { return obj.propertyValue(name.trim()); });

      var listener = this.framed(function() {
        var args = [];
        for ( var i = 0 ; i < values.length ; i++ )
          args.push(values[i].get());
        var ret = fn.apply(obj, args);
        opt_ret && opt_ret(ret);
      });

      for ( var i = 0 ; i < values.length ; i++ )
        values[i].addListener(listener);

      var f = function() {
        for ( var i = 0 ; i < values.length ; i++ )
          values[i].removeListener(listener);
      };
      f.destroy = f;
      return f;
    },
    animate: function(duration, fn, opt_interp, opt_onEnd) {
      return Movement.animate(duration, fn, opt_interp, opt_onEnd, this.Y);
    },
    setTimeout: function(f, t) {
      return this.window.setTimeout.apply(this.window, arguments);
    },
    clearTimeout: function(id) { this.window.clearTimeout(id); },
    setInterval: function(f, t) {
      return this.window.setInterval.apply(this.window, arguments);
    },
    clearInterval: function(id) { this.window.clearInterval(id); },
    requestAnimationFrame: function(f) {
      if ( this.isBackground ) return this.setTimeout(f, 16);

      console.assert(
        this.window.requestAnimationFrame,
        'requestAnimationFrame not defined');
      return this.window.requestAnimationFrame(f);
    },
    cancelAnimationFrame: function(id) {
      if ( this.isBackground ) {
        this.clearTimeout(id);
        return;
      }

      this.window.cancelAnimationFrame && this.window.cancelAnimationFrame(id);
    }
  }
});

// Using the existence of 'process' to determine that we're running in Node.
(function() {
  var w = foam.ui.Window.create(
    {
      window: window,
      name: 'DEFAULT WINDOW',
      isBackground: typeof process === 'object'
    }, X
  );
  FObject.X = X = w.Y;
})();
<|endoftext|>


<commit_before>"use strict";
const db = require("./db-module.js");
const {dialog} = require('electron');
const path = require('path');
const fs = require("fs");
const child_process = require('child_process');

module.exports = {
	playCharacter: function (id, response) {
		if (require('os').platform() != 'win32') {
			console.log("Per ora il modulo gamedll-module funziona solo su windows :(");
			return response.send();
		}
		db.settingDatastore.findOne({_id:"2"}, function(err, doc) {//cerco l'user.dat
			if (!fs.existsSync(doc["value"])) {
				dialog.showErrorBox("error", "User.dat not found!\n Please Edit the location from Setting section!");
				return response.send();
			}
			let userdat = doc;
			db.settingDatastore.findOne({_id:"1"}, function(err, doc) {
				if (doc == null) {
					dialog.showErrorBox("error", "Cannot find setting!");
					return response.send();
				}
				if (!fs.existsSync(doc["value"])) {
					dialog.showErrorBox("error", "game.dll not found!\n Please Edit the location from Setting section!");
					return response.send();
				}
				let gamedll = doc;
				db.characterDatastore.findOne({_id:id}, function(err, doc) {
					if (doc == null) {
						dialog.showErrorBox("error", "Cannot find setting!");
						return response.send();
					}
					let character = doc;
					db.accountDatastore.findOne({name:character["account"]}, function(err, doc) {
						if (doc == null) {
							dialog.showErrorBox("error", "Cannot find account!");
							return response.send();
						}
						let account = doc;
						db.serverDatastore.findOne({name:character["server"]}, function(err, doc) {
							if (doc == null) {
								dialog.showErrorBox("error", "Cannot find server!");
								return response.send();
							}
							let server = doc;
							db.classDatastore.findOne({name:character["class"]}, function(err, doc) {
								if (doc == null) {
									dialog.showErrorBox("error", "Cannot find class!");
									return response.send();
								}
								let classe = doc;
								db.realmDatastore.findOne({name:classe["realm"]}, function(err, doc) {
									if (doc == null) {
										dialog.showErrorBox("error", "Cannot find realm!");
										return response.send();
									}
									let realm = doc;

									//settare user.dat // all'inizio ci inserisci questo C:\Users\Simone\AppData\Roaming\Electronic Arts\Dark Age of Camelot
									let ini = require('ini');
									let config = ini.parse(fs.readFileSync(userdat["value"], 'utf-8'));
									let xy = character["resolution"].split("x");
									let windowed = character["windowed"] ? 1 : 0;
									config.main.screen_width = xy[0];
									config.main.screen_height = xy[1];
									config.main.windowed = windowed;
									fs.writeFileSync(path.dirname(userdat["value"]) + "\\user.dat", ini.stringify(config, {}));

									let spawn = child_process.spawn;
									let prc = spawn(gamedll["value"], [server["ip"], server["port"], server["n"], character["account"], account["password"], character["name"], realm["n"]], {
										cwd:path.dirname(gamedll["value"]),
										setsid:false,
										detached:true
									});
									console.log('Spawned child pid: ' + prc.pid);

									const moment = require('moment');
									const now = moment(Date.now()).format('DD/MM/YY HH:mm');

									//aggiorna timestamp last login
									db.characterDatastore.update({_id:id}, {$set:{lastlogin:now}} , function(err, numAffected, affectedDocuments) {
										require("./handle-module.js").killMutants();
										return response.send(now);
									});

									//da prc.pid fai partire l'exe che aggiorna
									const os = require('os');
									if (undefined != character['title'] && "" != character['title'] && prc.pid > 0) {
										let exec = require('child_process').exec;
										exec(os.tmpdir() + "\\titlerenamer.exe " + prc.pid + " " + character['title'], function(err, so, se) {
										});
									}
								});
							});
						});
					});
				});
			});
		});
	},
	killCharacter: function (id, response) {
		console.log("killCharacter called")
		db.characterDatastore.findOne({_id:id}, function(err, character) {
			let ps = require('ps-node');
			ps.lookup({
				command: 'game.dll',
				psargs: 'ux'
			}, function(err, resultList) {
				if (err) {
					throw new Error(err);
				}
				resultList.forEach(function(process){
					if (process){
						if (process.arguments[3] == character["account"] && process.arguments[5] == character["name"]) {
							ps.kill(process.pid, function(err) {
								if (err) {
									throw new Error (err);
								}
							});
						}
					}
				});
			});
			return response.send();
		});
	}
}
<commit_msg>appunto performance kill<commit_after>"use strict";
const db = require("./db-module.js");
const {dialog} = require('electron');
const path = require('path');
const fs = require("fs");
const child_process = require('child_process');

module.exports = {
	playCharacter: function (id, response) {
		if (require('os').platform() != 'win32') {
			console.log("Per ora il modulo gamedll-module funziona solo su windows :(");
			return response.send();
		}
		db.settingDatastore.findOne({_id:"2"}, function(err, doc) {//cerco l'user.dat
			if (!fs.existsSync(doc["value"])) {
				dialog.showErrorBox("error", "User.dat not found!\n Please Edit the location from Setting section!");
				return response.send();
			}
			let userdat = doc;
			db.settingDatastore.findOne({_id:"1"}, function(err, doc) {
				if (doc == null) {
					dialog.showErrorBox("error", "Cannot find setting!");
					return response.send();
				}
				if (!fs.existsSync(doc["value"])) {
					dialog.showErrorBox("error", "game.dll not found!\n Please Edit the location from Setting section!");
					return response.send();
				}
				let gamedll = doc;
				db.characterDatastore.findOne({_id:id}, function(err, doc) {
					if (doc == null) {
						dialog.showErrorBox("error", "Cannot find setting!");
						return response.send();
					}
					let character = doc;
					db.accountDatastore.findOne({name:character["account"]}, function(err, doc) {
						if (doc == null) {
							dialog.showErrorBox("error", "Cannot find account!");
							return response.send();
						}
						let account = doc;
						db.serverDatastore.findOne({name:character["server"]}, function(err, doc) {
							if (doc == null) {
								dialog.showErrorBox("error", "Cannot find server!");
								return response.send();
							}
							let server = doc;
							db.classDatastore.findOne({name:character["class"]}, function(err, doc) {
								if (doc == null) {
									dialog.showErrorBox("error", "Cannot find class!");
									return response.send();
								}
								let classe = doc;
								db.realmDatastore.findOne({name:classe["realm"]}, function(err, doc) {
									if (doc == null) {
										dialog.showErrorBox("error", "Cannot find realm!");
										return response.send();
									}
									let realm = doc;

									//settare user.dat // all'inizio ci inserisci questo C:\Users\Simone\AppData\Roaming\Electronic Arts\Dark Age of Camelot
									let ini = require('ini');
									let config = ini.parse(fs.readFileSync(userdat["value"], 'utf-8'));
									let xy = character["resolution"].split("x");
									let windowed = character["windowed"] ? 1 : 0;
									config.main.screen_width = xy[0];
									config.main.screen_height = xy[1];
									config.main.windowed = windowed;
									fs.writeFileSync(path.dirname(userdat["value"]) + "\\user.dat", ini.stringify(config, {}));

									let spawn = child_process.spawn;
									let prc = spawn(gamedll["value"], [server["ip"], server["port"], server["n"], character["account"], account["password"], character["name"], realm["n"]], {
										cwd:path.dirname(gamedll["value"]),
										setsid:false,
										detached:true
									});
									console.log('Spawned child pid: ' + prc.pid);

									const moment = require('moment');
									const now = moment(Date.now()).format('DD/MM/YY HH:mm');

									//aggiorna timestamp last login
									db.characterDatastore.update({_id:id}, {$set:{lastlogin:now}} , function(err, numAffected, affectedDocuments) {
										require("./handle-module.js").killMutants();
										return response.send(now);
									});

									//da prc.pid fai partire l'exe che aggiorna
									const os = require('os');
									if (undefined != character['title'] && "" != character['title'] && prc.pid > 0) {
										let exec = require('child_process').exec;
										exec(os.tmpdir() + "\\titlerenamer.exe " + prc.pid + " " + character['title'], function(err, so, se) {
										});
									}
								});
							});
						});
					});
				});
			});
		});
	},
	killCharacter: function (id, response) {//appunto performance:https://github.com/sindresorhus/tasklist
		console.log("killCharacter called")
		db.characterDatastore.findOne({_id:id}, function(err, character) {
			let ps = require('ps-node');
			ps.lookup({
				command: 'game.dll',
				psargs: 'ux'
			}, function(err, resultList) {
				if (err) {
					throw new Error(err);
				}
				resultList.forEach(function(process){
					if (process){
						if (process.arguments[3] == character["account"] && process.arguments[5] == character["name"]) {
							ps.kill(process.pid, function(err) {
								if (err) {
									throw new Error (err);
								}
							});
						}
					}
				});
			});
			return response.send();
		});
	}
}
<|endoftext|>


<commit_before>// Copyright 2020, University of Colorado Boulder

/**
 * Generates JS modules from resources such as images/strings/audio/etc.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Jonathan Olson (PhET Interactive Simulations)
 */
'use strict';

const _ = require( 'lodash' ); // eslint-disable-line require-statement-match
const createMipmap = require( './createMipmap' );
const fs = require( 'fs' );
const grunt = require( 'grunt' );
const loadFileAsDataURI = require( '../common/loadFileAsDataURI' );
const os = require( 'os' );
const updateCopyrightForGeneratedFile = require( './updateCopyrightForGeneratedFile' );

// disable lint in compiled files, because it increases the linting time
const HEADER = '/* eslint-disable */';

// supported image types, not case-sensitive
const IMAGE_SUFFIXES = [ '.png', '.jpg', '.cur' ];

// supported sound file types, not case-sensitive
const SOUND_SUFFIXES = [ '.mp3', '.wav' ];

/**
 * String replacement
 * @param {string} string - the string which will be searched
 * @param {string} search - the text to be replaced
 * @param {string} replacement - the new text
 * @returns {string}
 */
const replace = ( string, search, replacement ) => string.split( search ).join( replacement );

/**
 * Gets the relative path to the root based on the depth of a resource
 *
 * @returns {string}
 */
const expandDots = abspath => {

  // Finds the depths of a directory relative to the root of where grunt.recurse was called from (a repo root)
  const depth = abspath.split( '/' ).length - 2;
  let parentDirectory = '';
  for ( let i = 0; i < depth; i++ ) {
    parentDirectory = parentDirectory + '../';
  }
  return parentDirectory;
};

/**
 * Output with an OS-specific EOL sequence, see https://github.com/phetsims/chipper/issues/908
 * @param string
 * @returns {string}
 */
const fixEOL = string => replace( string, '\n', os.EOL );

/**
 * Transform an image file to a JS file that loads the image.
 * @param {string} abspath - the absolute path of the image
 */
const modulifyImage = async abspath => {

  const dataURI = loadFileAsDataURI( abspath );

  const contents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';
const image = new Image();
const unlock = simLauncher.createLock( image );
image.onload = unlock;
image.src = '${dataURI}';
export default image;`;

  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( contents ) );
};

/**
 * Transform an image file to a JS file that loads the image as a mipmap.
 * @param {string} abspath - the absolute path of the image
 */
const modulifyMipmap = async abspath => {

  // Defaults. NOTE: using the default settings because we have not run into a need, see
  // https://github.com/phetsims/chipper/issues/820 and https://github.com/phetsims/chipper/issues/945
  const config = {
    level: 4, // maximum level
    quality: 98
  };

  const mipmaps = await createMipmap( abspath, config.level, config.quality );
  const entry = mipmaps.map( ( { width, height, url } ) => ( { width: width, height: height, url: url } ) );

  const mipmapContents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';

const mipmaps = ${JSON.stringify( entry, null, 2 )};
mipmaps.forEach( mipmap => {
  mipmap.img = new Image();
  const unlock = simLauncher.createLock( mipmap.img );
  mipmap.img.onload = unlock;
  mipmap.img.src = mipmap.url; // trigger the loading of the image for its level
  mipmap.canvas = document.createElement( 'canvas' );
  mipmap.canvas.width = mipmap.width;
  mipmap.canvas.height = mipmap.height;
  const context = mipmap.canvas.getContext( '2d' );
  mipmap.updateCanvas = () => {
    if ( mipmap.img.complete && ( typeof mipmap.img.naturalWidth === 'undefined' || mipmap.img.naturalWidth > 0 ) ) {
      context.drawImage( mipmap.img, 0, 0 );
      delete mipmap.updateCanvas;
    }
  };
} );
export default mipmaps;`;
  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( mipmapContents ) );
};

/**
 * Decode a sound file into a Web Audio AudioBuffer.
 * @param {string} abspath - the absolute path of the sound file
 */
const modulifySound = abspath => {

  // load the sound file
  const dataURI = loadFileAsDataURI( abspath );

  // output the contents of the file that will define the sound in JS format
  const contents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';
import base64SoundToByteArray from '${expandDots( abspath )}tambo/js/base64SoundToByteArray.js';
import WrappedAudioBuffer from '${expandDots( abspath )}tambo/js/WrappedAudioBuffer.js';
import phetAudioContext from '${expandDots( abspath )}tambo/js/phetAudioContext.js';

const soundURI = '${dataURI}';
const soundByteArray = base64SoundToByteArray( phetAudioContext, soundURI );
const unlock = simLauncher.createLock( soundURI );
const wrappedAudioBuffer = new WrappedAudioBuffer();
const onDecodeSuccess = decodedAudio => {
  wrappedAudioBuffer.audioBufferProperty.set( decodedAudio );
  unlock();
};
const onDecodeError = decodeError => {
  console.warn( 'decode of audio data failed, using stubbed sound, error: ' + decodeError );
  wrappedAudioBuffer.audioBufferProperty.set( phetAudioContext.createBuffer( 1, 0, phetAudioContext.sampleRate ) );
  unlock();
};
phetAudioContext.decodeAudioData( soundByteArray.buffer, onDecodeSuccess, onDecodeError );
export default wrappedAudioBuffer;`;

  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( contents ) );
};

/**
 * Convert .png => _png_mipmap.js, etc.
 *
 * @param {string} abspath - the absolute path
 * @param {string} suffix - the new suffix, such as '.js'
 * @returns {string}
 */
const convertSuffix = ( abspath, suffix ) => {
  const lastDotIndex = abspath.lastIndexOf( '.' );
  return abspath.substring( 0, lastDotIndex ) + '_' + abspath.substring( lastDotIndex + 1 ) + suffix;
};

/**
 * Determines the suffix from a filename, everything after the final '.'
 *
 * @param {string} filename
 * @returns {string}
 */
const getSuffix = filename => {
  const index = filename.lastIndexOf( '.' );
  return filename.substring( index );
};

/**
 * Creates a *.js file corresponding to matching resources such as images or sounds.
 * @param {string} abspath
 * @param {string} rootdir
 * @param {string} subdir
 * @param {string} filename
 */
const modulifyFile = async ( abspath, rootdir, subdir, filename ) => {

  if ( subdir && ( subdir.startsWith( 'images' ) ||

                   // for brand
                   subdir.startsWith( 'phet/images' ) ||
                   subdir.startsWith( 'phet-io/images' ) ||
                   subdir.startsWith( 'adapted-from-phet/images' ) )
       && IMAGE_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifyImage( abspath );
  }

  if ( subdir && ( subdir.startsWith( 'mipmaps' ) ||

                   // for brand
                   subdir.startsWith( 'phet/mipmaps' ) ||
                   subdir.startsWith( 'phet-io/mipmaps' ) ||
                   subdir.startsWith( 'adapted-from-phet/mipmaps' ) )
       && IMAGE_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifyMipmap( abspath );
  }

  if ( subdir && subdir.startsWith( 'sounds' ) && SOUND_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifySound( abspath );
  }
};

/**
 * Creates the string module at js/${_.camelCase( repo )}Strings.js for repos that need it.
 * @public
 *
 * @param {string} repo
 */
const createStringModule = async repo => {
  const packageObject = grunt.file.readJSON( `../${repo}/package.json` );
  const stringModuleFile = `../${repo}/js/${_.camelCase( repo )}Strings.js`;
  const namespace = _.camelCase( repo );
  fs.writeFileSync( stringModuleFile, fixEOL( `// Copyright ${new Date().getFullYear()}, University of Colorado Boulder

/**
 * Auto-generated from modulify, DO NOT manually modify.
 */

import getStringModule from '../../chipper/js/getStringModule.js';
import ${namespace} from './${namespace}.js';

const ${namespace}Strings = getStringModule( '${packageObject.phet.requirejsNamespace}' );

${namespace}.register( '${namespace}Strings', ${namespace}Strings );

export default ${namespace}Strings;
` ) );
  await updateCopyrightForGeneratedFile( repo, stringModuleFile );
};

/**
 * Creates the namespace module at js/${_.camelCase( repo )}.js for repos that need it.
 * @public
 *
 * @param {string} repo
 */
const createNamespaceModule = async repo => {
  const namespace = _.camelCase( repo );
  const namespaceFile = `../${repo}/js/${namespace}.js`;
  fs.writeFileSync( namespaceFile, fixEOL( `// Copyright ${new Date().getFullYear()}, University of Colorado Boulder

/**
 * Creates the namespace for this simulation.
 */

// modules
import Namespace from '../../phet-core/js/Namespace.js';

export default new Namespace( '${namespace}' );
` ) );
  await updateCopyrightForGeneratedFile( repo, namespaceFile );
};

/**
 * Entry point for modulify, which transforms all of the resources in a repo to *.js files.
 * @param {string} repo - the name of a repo, such as 'joist'
 */
const modulify = async repo => {
  console.log( `modulifying ${repo}` );
  const relativeFiles = [];
  grunt.file.recurse( `../${repo}`, async ( abspath, rootdir, subdir, filename ) => {
    relativeFiles.push( { abspath: abspath, rootdir: rootdir, subdir: subdir, filename: filename } );
  } );

  for ( let i = 0; i < relativeFiles.length; i++ ) {
    const entry = relativeFiles[ i ];
    await modulifyFile( entry.abspath, entry.rootdir, entry.subdir, entry.filename );
  }

  // Create the namespace file, if it did not already exist
  const packageObject = grunt.file.readJSON( `../${repo}/package.json` );
  if ( fs.existsSync( `../${repo}/${repo}-strings_en.json` ) && packageObject.phet && packageObject.phet.requirejsNamespace ) {
    await createStringModule( repo );

    const namespace = _.camelCase( repo );
    const namespaceFile = `../${repo}/js/${namespace}.js`;
    if ( !fs.existsSync( namespaceFile ) ) {
      await createNamespaceModule( repo );
    }
  }
};

module.exports = modulify;<commit_msg>Add a newline after import to match our formatter, see https://github.com/phetsims/chipper/issues/982<commit_after>// Copyright 2020, University of Colorado Boulder

/**
 * Generates JS modules from resources such as images/strings/audio/etc.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Jonathan Olson (PhET Interactive Simulations)
 */
'use strict';

const _ = require( 'lodash' ); // eslint-disable-line require-statement-match
const createMipmap = require( './createMipmap' );
const fs = require( 'fs' );
const grunt = require( 'grunt' );
const loadFileAsDataURI = require( '../common/loadFileAsDataURI' );
const os = require( 'os' );
const updateCopyrightForGeneratedFile = require( './updateCopyrightForGeneratedFile' );

// disable lint in compiled files, because it increases the linting time
const HEADER = '/* eslint-disable */';

// supported image types, not case-sensitive
const IMAGE_SUFFIXES = [ '.png', '.jpg', '.cur' ];

// supported sound file types, not case-sensitive
const SOUND_SUFFIXES = [ '.mp3', '.wav' ];

/**
 * String replacement
 * @param {string} string - the string which will be searched
 * @param {string} search - the text to be replaced
 * @param {string} replacement - the new text
 * @returns {string}
 */
const replace = ( string, search, replacement ) => string.split( search ).join( replacement );

/**
 * Gets the relative path to the root based on the depth of a resource
 *
 * @returns {string}
 */
const expandDots = abspath => {

  // Finds the depths of a directory relative to the root of where grunt.recurse was called from (a repo root)
  const depth = abspath.split( '/' ).length - 2;
  let parentDirectory = '';
  for ( let i = 0; i < depth; i++ ) {
    parentDirectory = parentDirectory + '../';
  }
  return parentDirectory;
};

/**
 * Output with an OS-specific EOL sequence, see https://github.com/phetsims/chipper/issues/908
 * @param string
 * @returns {string}
 */
const fixEOL = string => replace( string, '\n', os.EOL );

/**
 * Transform an image file to a JS file that loads the image.
 * @param {string} abspath - the absolute path of the image
 */
const modulifyImage = async abspath => {

  const dataURI = loadFileAsDataURI( abspath );

  const contents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';

const image = new Image();
const unlock = simLauncher.createLock( image );
image.onload = unlock;
image.src = '${dataURI}';
export default image;`;

  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( contents ) );
};

/**
 * Transform an image file to a JS file that loads the image as a mipmap.
 * @param {string} abspath - the absolute path of the image
 */
const modulifyMipmap = async abspath => {

  // Defaults. NOTE: using the default settings because we have not run into a need, see
  // https://github.com/phetsims/chipper/issues/820 and https://github.com/phetsims/chipper/issues/945
  const config = {
    level: 4, // maximum level
    quality: 98
  };

  const mipmaps = await createMipmap( abspath, config.level, config.quality );
  const entry = mipmaps.map( ( { width, height, url } ) => ( { width: width, height: height, url: url } ) );

  const mipmapContents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';

const mipmaps = ${JSON.stringify( entry, null, 2 )};
mipmaps.forEach( mipmap => {
  mipmap.img = new Image();
  const unlock = simLauncher.createLock( mipmap.img );
  mipmap.img.onload = unlock;
  mipmap.img.src = mipmap.url; // trigger the loading of the image for its level
  mipmap.canvas = document.createElement( 'canvas' );
  mipmap.canvas.width = mipmap.width;
  mipmap.canvas.height = mipmap.height;
  const context = mipmap.canvas.getContext( '2d' );
  mipmap.updateCanvas = () => {
    if ( mipmap.img.complete && ( typeof mipmap.img.naturalWidth === 'undefined' || mipmap.img.naturalWidth > 0 ) ) {
      context.drawImage( mipmap.img, 0, 0 );
      delete mipmap.updateCanvas;
    }
  };
} );
export default mipmaps;`;
  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( mipmapContents ) );
};

/**
 * Decode a sound file into a Web Audio AudioBuffer.
 * @param {string} abspath - the absolute path of the sound file
 */
const modulifySound = abspath => {

  // load the sound file
  const dataURI = loadFileAsDataURI( abspath );

  // output the contents of the file that will define the sound in JS format
  const contents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';
import base64SoundToByteArray from '${expandDots( abspath )}tambo/js/base64SoundToByteArray.js';
import WrappedAudioBuffer from '${expandDots( abspath )}tambo/js/WrappedAudioBuffer.js';
import phetAudioContext from '${expandDots( abspath )}tambo/js/phetAudioContext.js';

const soundURI = '${dataURI}';
const soundByteArray = base64SoundToByteArray( phetAudioContext, soundURI );
const unlock = simLauncher.createLock( soundURI );
const wrappedAudioBuffer = new WrappedAudioBuffer();
const onDecodeSuccess = decodedAudio => {
  wrappedAudioBuffer.audioBufferProperty.set( decodedAudio );
  unlock();
};
const onDecodeError = decodeError => {
  console.warn( 'decode of audio data failed, using stubbed sound, error: ' + decodeError );
  wrappedAudioBuffer.audioBufferProperty.set( phetAudioContext.createBuffer( 1, 0, phetAudioContext.sampleRate ) );
  unlock();
};
phetAudioContext.decodeAudioData( soundByteArray.buffer, onDecodeSuccess, onDecodeError );
export default wrappedAudioBuffer;`;

  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( contents ) );
};

/**
 * Convert .png => _png_mipmap.js, etc.
 *
 * @param {string} abspath - the absolute path
 * @param {string} suffix - the new suffix, such as '.js'
 * @returns {string}
 */
const convertSuffix = ( abspath, suffix ) => {
  const lastDotIndex = abspath.lastIndexOf( '.' );
  return abspath.substring( 0, lastDotIndex ) + '_' + abspath.substring( lastDotIndex + 1 ) + suffix;
};

/**
 * Determines the suffix from a filename, everything after the final '.'
 *
 * @param {string} filename
 * @returns {string}
 */
const getSuffix = filename => {
  const index = filename.lastIndexOf( '.' );
  return filename.substring( index );
};

/**
 * Creates a *.js file corresponding to matching resources such as images or sounds.
 * @param {string} abspath
 * @param {string} rootdir
 * @param {string} subdir
 * @param {string} filename
 */
const modulifyFile = async ( abspath, rootdir, subdir, filename ) => {

  if ( subdir && ( subdir.startsWith( 'images' ) ||

                   // for brand
                   subdir.startsWith( 'phet/images' ) ||
                   subdir.startsWith( 'phet-io/images' ) ||
                   subdir.startsWith( 'adapted-from-phet/images' ) )
       && IMAGE_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifyImage( abspath );
  }

  if ( subdir && ( subdir.startsWith( 'mipmaps' ) ||

                   // for brand
                   subdir.startsWith( 'phet/mipmaps' ) ||
                   subdir.startsWith( 'phet-io/mipmaps' ) ||
                   subdir.startsWith( 'adapted-from-phet/mipmaps' ) )
       && IMAGE_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifyMipmap( abspath );
  }

  if ( subdir && subdir.startsWith( 'sounds' ) && SOUND_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifySound( abspath );
  }
};

/**
 * Creates the string module at js/${_.camelCase( repo )}Strings.js for repos that need it.
 * @public
 *
 * @param {string} repo
 */
const createStringModule = async repo => {
  const packageObject = grunt.file.readJSON( `../${repo}/package.json` );
  const stringModuleFile = `../${repo}/js/${_.camelCase( repo )}Strings.js`;
  const namespace = _.camelCase( repo );
  fs.writeFileSync( stringModuleFile, fixEOL( `// Copyright ${new Date().getFullYear()}, University of Colorado Boulder

/**
 * Auto-generated from modulify, DO NOT manually modify.
 */

import getStringModule from '../../chipper/js/getStringModule.js';
import ${namespace} from './${namespace}.js';

const ${namespace}Strings = getStringModule( '${packageObject.phet.requirejsNamespace}' );

${namespace}.register( '${namespace}Strings', ${namespace}Strings );

export default ${namespace}Strings;
` ) );
  await updateCopyrightForGeneratedFile( repo, stringModuleFile );
};

/**
 * Creates the namespace module at js/${_.camelCase( repo )}.js for repos that need it.
 * @public
 *
 * @param {string} repo
 */
const createNamespaceModule = async repo => {
  const namespace = _.camelCase( repo );
  const namespaceFile = `../${repo}/js/${namespace}.js`;
  fs.writeFileSync( namespaceFile, fixEOL( `// Copyright ${new Date().getFullYear()}, University of Colorado Boulder

/**
 * Creates the namespace for this simulation.
 */

// modules
import Namespace from '../../phet-core/js/Namespace.js';

export default new Namespace( '${namespace}' );
` ) );
  await updateCopyrightForGeneratedFile( repo, namespaceFile );
};

/**
 * Entry point for modulify, which transforms all of the resources in a repo to *.js files.
 * @param {string} repo - the name of a repo, such as 'joist'
 */
const modulify = async repo => {
  console.log( `modulifying ${repo}` );
  const relativeFiles = [];
  grunt.file.recurse( `../${repo}`, async ( abspath, rootdir, subdir, filename ) => {
    relativeFiles.push( { abspath: abspath, rootdir: rootdir, subdir: subdir, filename: filename } );
  } );

  for ( let i = 0; i < relativeFiles.length; i++ ) {
    const entry = relativeFiles[ i ];
    await modulifyFile( entry.abspath, entry.rootdir, entry.subdir, entry.filename );
  }

  // Create the namespace file, if it did not already exist
  const packageObject = grunt.file.readJSON( `../${repo}/package.json` );
  if ( fs.existsSync( `../${repo}/${repo}-strings_en.json` ) && packageObject.phet && packageObject.phet.requirejsNamespace ) {
    await createStringModule( repo );

    const namespace = _.camelCase( repo );
    const namespaceFile = `../${repo}/js/${namespace}.js`;
    if ( !fs.existsSync( namespaceFile ) ) {
      await createNamespaceModule( repo );
    }
  }
};

module.exports = modulify;<|endoftext|>