<commit_before>/*
 * SafeGuard.js is a small JS plugin for jQuery allowing
 * to save/load the datas of a form in/from the local storage
 *
 * code booted by robin tissot for libération (www.liberation.fr)
 * and hosted here : https://github.com/lauxley/jquery.safegard.js
 */

var safeguard_tinymce = {
    isConcerned : function(el) {
        return (tinyMCE.get(el.attr("id")) !== undefined);
    },
    getVal : function(el) {
        return tinyMCE.get(el.attr("id")).getContent();
    },
    setVal : function(el, val) {
        tinyMCE.get(el.attr("id")).setContent(val);
    },
    onChange : function(el, cb) {
        // NOTE : for this to work you need to call the init method of safeguard AFTER the initialization of tinyMCE
        tinyMCE.get(el.attr("id")).onChange.add(cb);
   }
};

(function($) {
    var defaults = {
        local_key : "safeguard-",
        editor_plugin : null,
        save_mode : "change",
        save_timer : 0,
        selector : null,
        recover_mode : "silent",
        confirm_label : "You have unsaved datas, do you want to retrieve them ?",
        max_age : 60*60*24, // set the expiration timer (in seconds), default is 1 day
        time : Math.round(new Date().getTime() / 1000), // in case you would want to pass a server time
        history : 1,
        flush_on_submit : true,
        flush_on_reset : true
    };
    var settings = {};
    var items = [];
    var has_changed = false;

    var getHKey = function(url) {
        return settings.local_key + (url!==undefined?url:document.location.pathname);
    };

    var getStoreKey = function() {
        return settings.local_key + "index_store";
    };

    var getStore = function(url) {
        try {
            return JSON.parse(localStorage[getStoreKey()])[getHKey(url)];
        } catch (TypeError) {
            return null;
        }
    };

    var setStore = function(data, url) {
        var store = JSON.parse(localStorage[getStoreKey()]?localStorage[getStoreKey()]:"{}");
        store[getHKey(url)] = data;
        localStorage[getStoreKey()] = JSON.stringify(store);
    };

    var methods = {
        getItems : function() {
            if (!items.length) {
                this.each(function() {
                    $.each( $("textarea,input,select", this), function(i, e) {
                        if ($(e).attr("id") !== undefined && (!settings.selector || (settings.selector && $(e).is(settings.selector)))) {
                            items.push(e);
                        }
                    });
                });
            }
            return $(items);
        },

        getDatas : function(index) {
            var state_list = JSON.parse(localStorage[getHKey()]);
            if (index) {
                return state_list.slice(index, index + 1)[0];
            } else {
                return state_list.shift();
            }
        },

        init : function(config) {
            var self = this;

            function _init_mode_silent() {
                if (self.safeguard('hasItems')) {
                    self.safeguard('load');
                }
            }

            function _init_mode_alert() {
                if (self.safeguard('hasItems')) {
                    if (confirm(settings.confirm_label)) {
                        self.safeguard('load');
                    }
                }
            }

            function _save() {
                if (self.has_changed) {
                    self.safeguard('save');
                    self.has_changed = false;
                }
            }

            function _has_changed() {
                self.has_changed = true;
                if (settings.save_mode == "change") {
                    _save();
                }
            }

            function _set_timer() {
                self.data('timer', setInterval(_save, settings.save_timer*1000));
            }

            if (config === undefined) config = {};
            settings = $.extend({}, defaults);
            if (config) { $.extend(settings, config); }

            $.each(self.safeguard('getItems'), function(i, e) {
                if (settings.editor_plugin && settings.editor_plugin.isConcerned($(e))) {
                    settings.editor_plugin.onChange($(e), _has_changed);
                } else {
                    $(e).bind("change", _has_changed);
                }
            });

            if (settings.save_mode == "timer") {
                if (settings.save_timer) {
                    _set_timer();
                }
            }

            self.bind("submit", function() {
                clearInterval(self.data("timer"));
                if (settings.flush_on_submit) {
                    self.safeguard('flush');
                } else {
                    self.safeguard('invalidate');
                }
            });
            self.bind("reset", function() {
                clearInterval(self.data("timer"));
                if (settings.flush_on_reset) {
                    self.safeguard('flush');
                } else {
                    self.safeguard('invalidate');
                }
            });

            // because the onchange event is not fired in some cases (for instance when pushing the back button in chromium)
            $(window).bind("unload", _save);

            switch (settings.recover_mode) {
                case "silent":
                    _init_mode_silent();
                    break;
                case "alert":
                    _init_mode_alert();
                    break;
                case "custom":
                    break;
                default:
                    $.error("Not Implemented recover mode : "+settings.recover_mode);
            }

            self.safeguard('clean');
            return this;
        },

        save : function() {
            function _add(key, val) {
                var state_list = JSON.parse(localStorage[key]?localStorage[key]:"[]");
                state_list.unshift(val);
                while (state_list.length > settings.history) {
                    state_list.pop();
                }
                localStorage[key] = JSON.stringify(state_list);
                setStore({'time':settings.time, 'valid':true});
            }
            vals = {};
            var l = $.each(this.safeguard('getItems'), function(i, e) {
                if (settings.editor_plugin && settings.editor_plugin.isConcerned($(this))) {
                    val = settings.editor_plugin.getVal($(e));
                } else {
                    val = $(this).val();
                }
                vals[$(e).attr("id")] = val;
            });
            _add(getHKey(), vals);
            return l;  // keep the chainability
        },

        load : function(index) {
            var l = $([]);
            var o = this.safeguard('getDatas', index);
            var l = $.each(this.safeguard('getItems'), function(i, e) {
                if (settings.editor_plugin && settings.editor_plugin.isConcerned($(e))) {
                    settings.editor_plugin.setVal($(e), o[$(e).attr("id")]);
                } else {
                    $(e).val(o[$(e).attr("id")]);
                }
            });
            return l; // keep the chainability
        },

        flush : function(url) {
            var key = getHKey(url);
            var index_store = JSON.parse(localStorage[getStoreKey()]?localStorage[getStoreKey()]:"{}");
            if (index_store) {
                delete index_store[key];
                localStorage[getStoreKey()] = JSON.stringify(index_store);
            }
            localStorage.removeItem(key);
        },

        setValidity : function(val, url) {
            var store = getStore(url);
            if (store) {
                store["valid"] = val;
                setStore(store);
            }
        },

        invalidate : function(url) {
            this.safeguard('setValidity', false, url);
        },

        validate : function(url) {
            this.safeguard('setValidity', true, url);
        },

        isValid : function(url) {
            var store = getStore(url);
            if (store) {
                return store['valid'];
            } else {
                return false;
            }
        },

        hasItems : function() {
            return (localStorage[getHKey()] !== undefined && this.safeguard('isValid'));
        },

        clean : function() {
            // look for expired keys
            var d = settings.time;
            var index_store = JSON.parse(localStorage[getStoreKey()]?localStorage[getStoreKey()]:"{}");
            if (index_store) {
                for (key in index_store) {
                    if (parseInt(d) - parseInt(index_store[key]['time']) > settings.max_age) {
                        localStorage.removeItem(key);
                        delete index_store[key];
                    }
                }
                localStorage[getStoreKey()] = JSON.stringify(index_store);
            }
        },

        getTime : function() {
            var store = getStore();
            if (store) {
                return parseInt(store['time']);
            } else {
                return null;
            }
        }
    };

    $.fn.safeguard = function(method) {
        if (localStorage) {
            if ( methods[method] ) {
                return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
            } else if ( typeof method === 'object' || ! method ) {
                return methods.init.apply( this, arguments );
            } else {
                $.error( 'Method ' +  method + ' does not exist on jQuery.safeguard' );
            }
        }
   };

})(jQuery);
<commit_msg>fix tinyMCE being unloaded before safeguard get the chance to save the datas, throwing a JS error.<commit_after>/*
 * SafeGuard.js is a small JS plugin for jQuery allowing
 * to save/load the datas of a form in/from the local storage
 *
 * code booted by robin tissot for libération (www.liberation.fr)
 * and hosted here : https://github.com/lauxley/jquery.safegard.js
 */

var safeguard_tinymce = {
    isConcerned : function(el) {
        return (tinyMCE.get(el.attr("id")) !== undefined);
    },
    getVal : function(el) {
        try {
            return tinyMCE.get(el.attr("id")).getContent();
        } catch(err) {
            return el.val();
        }
    },
    setVal : function(el, val) {
        tinyMCE.get(el.attr("id")).setContent(val);
    },
    onChange : function(el, cb) {
        // NOTE : for this to work you need to call the init method of safeguard AFTER the initialization of tinyMCE
        tinyMCE.get(el.attr("id")).onChange.add(cb);
   }
};

(function($) {
    var defaults = {
        local_key : "safeguard-",
        editor_plugin : null,
        save_mode : "change",
        save_timer : 0,
        selector : null,
        recover_mode : "silent",
        confirm_label : "You have unsaved datas, do you want to retrieve them ?",
        max_age : 60*60*24, // set the expiration timer (in seconds), default is 1 day
        time : Math.round(new Date().getTime() / 1000), // in case you would want to pass a server time
        history : 1,
        flush_on_submit : true,
        flush_on_reset : true
    };
    var settings = {};
    var items = [];
    var has_changed = false;

    var getHKey = function(url) {
        return settings.local_key + (url!==undefined?url:document.location.pathname);
    };

    var getStoreKey = function() {
        return settings.local_key + "index_store";
    };

    var getStore = function(url) {
        try {
            return JSON.parse(localStorage[getStoreKey()])[getHKey(url)];
        } catch (TypeError) {
            return null;
        }
    };

    var setStore = function(data, url) {
        var store = JSON.parse(localStorage[getStoreKey()]?localStorage[getStoreKey()]:"{}");
        store[getHKey(url)] = data;
        localStorage[getStoreKey()] = JSON.stringify(store);
    };

    var methods = {
        getItems : function() {
            if (!items.length) {
                this.each(function() {
                    $.each( $("textarea,input,select", this), function(i, e) {
                        if ($(e).attr("id") !== undefined && (!settings.selector || (settings.selector && $(e).is(settings.selector)))) {
                            items.push(e);
                        }
                    });
                });
            }
            return $(items);
        },

        getDatas : function(index) {
            var state_list = JSON.parse(localStorage[getHKey()]);
            if (index) {
                return state_list.slice(index, index + 1)[0];
            } else {
                return state_list.shift();
            }
        },

        init : function(config) {
            var self = this;

            function _init_mode_silent() {
                if (self.safeguard('hasItems')) {
                    self.safeguard('load');
                }
            }

            function _init_mode_alert() {
                if (self.safeguard('hasItems')) {
                    if (confirm(settings.confirm_label)) {
                        self.safeguard('load');
                    }
                }
            }

            function _save() {
                if (self.has_changed) {
                    self.safeguard('save');
                    self.has_changed = false;
                }
            }

            function _has_changed() {
                self.has_changed = true;
                if (settings.save_mode == "change") {
                    _save();
                }
            }

            function _set_timer() {
                self.data('timer', setInterval(_save, settings.save_timer*1000));
            }

            if (config === undefined) config = {};
            settings = $.extend({}, defaults);
            if (config) { $.extend(settings, config); }

            $.each(self.safeguard('getItems'), function(i, e) {
                if (settings.editor_plugin && settings.editor_plugin.isConcerned($(e))) {
                    settings.editor_plugin.onChange($(e), _has_changed);
                } else {
                    $(e).bind("change", _has_changed);
                }
            });

            if (settings.save_mode == "timer") {
                if (settings.save_timer) {
                    _set_timer();
                }
            }

            self.bind("submit", function() {
                clearInterval(self.data("timer"));
                if (settings.flush_on_submit) {
                    self.safeguard('flush');
                } else {
                    self.safeguard('invalidate');
                }
            });
            self.bind("reset", function() {
                clearInterval(self.data("timer"));
                if (settings.flush_on_reset) {
                    self.safeguard('flush');
                } else {
                    self.safeguard('invalidate');
                }
            });

            // because the onchange event is not fired in some cases (for instance when pushing the back button in chromium)
            $(window).bind("unload", _save);

            switch (settings.recover_mode) {
                case "silent":
                    _init_mode_silent();
                    break;
                case "alert":
                    _init_mode_alert();
                    break;
                case "custom":
                    break;
                default:
                    $.error("Not Implemented recover mode : "+settings.recover_mode);
            }

            self.safeguard('clean');
            return this;
        },

        save : function() {
            function _add(key, val) {
                var state_list = JSON.parse(localStorage[key]?localStorage[key]:"[]");
                state_list.unshift(val);
                while (state_list.length > settings.history) {
                    state_list.pop();
                }
                localStorage[key] = JSON.stringify(state_list);
                setStore({'time':settings.time, 'valid':true});
            }
            vals = {};
            var l = $.each(this.safeguard('getItems'), function(i, e) {
                if (settings.editor_plugin && settings.editor_plugin.isConcerned($(this))) {
                    val = settings.editor_plugin.getVal($(e));
                } else {
                    val = $(this).val();
                }
                vals[$(e).attr("id")] = val;
            });
            _add(getHKey(), vals);
            return l;  // keep the chainability
        },

        load : function(index) {
            var l = $([]);
            var o = this.safeguard('getDatas', index);
            var l = $.each(this.safeguard('getItems'), function(i, e) {
                if (settings.editor_plugin && settings.editor_plugin.isConcerned($(e))) {
                    settings.editor_plugin.setVal($(e), o[$(e).attr("id")]);
                } else {
                    $(e).val(o[$(e).attr("id")]);
                }
            });
            return l; // keep the chainability
        },

        flush : function(url) {
            var key = getHKey(url);
            var index_store = JSON.parse(localStorage[getStoreKey()]?localStorage[getStoreKey()]:"{}");
            if (index_store) {
                delete index_store[key];
                localStorage[getStoreKey()] = JSON.stringify(index_store);
            }
            localStorage.removeItem(key);
        },

        setValidity : function(val, url) {
            var store = getStore(url);
            if (store) {
                store["valid"] = val;
                setStore(store);
            }
        },

        invalidate : function(url) {
            this.safeguard('setValidity', false, url);
        },

        validate : function(url) {
            this.safeguard('setValidity', true, url);
        },

        isValid : function(url) {
            var store = getStore(url);
            if (store) {
                return store['valid'];
            } else {
                return false;
            }
        },

        hasItems : function() {
            return (localStorage[getHKey()] !== undefined && this.safeguard('isValid'));
        },

        clean : function() {
            // look for expired keys
            var d = settings.time;
            var index_store = JSON.parse(localStorage[getStoreKey()]?localStorage[getStoreKey()]:"{}");
            if (index_store) {
                for (key in index_store) {
                    if (parseInt(d) - parseInt(index_store[key]['time']) > settings.max_age) {
                        localStorage.removeItem(key);
                        delete index_store[key];
                    }
                }
                localStorage[getStoreKey()] = JSON.stringify(index_store);
            }
        },

        getTime : function() {
            var store = getStore();
            if (store) {
                return parseInt(store['time']);
            } else {
                return null;
            }
        }
    };

    $.fn.safeguard = function(method) {
        if (localStorage) {
            if ( methods[method] ) {
                return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
            } else if ( typeof method === 'object' || ! method ) {
                return methods.init.apply( this, arguments );
            } else {
                $.error( 'Method ' +  method + ' does not exist on jQuery.safeguard' );
            }
        }
   };

})(jQuery);
<|endoftext|>


<commit_before>// Wheelzoom 0.1.0
// (c) 2012 jacklmoore.com | license: www.opensource.org/licenses/mit-license.php
!function($){
	var transparentPNG = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==";
	var defaults = {
		zoom: 0.10
	};
	var wheel;

	if ( document.onmousewheel !== undefined ) { // Webkit/Opera/IE
		wheel = 'onmousewheel';
	}
	else if ( document.onwheel !== undefined) { // FireFox 17+
		wheel = 'onwheel';
	}

	$.fn.wheelzoom = function(options){
		var settings = $.extend({}, defaults, options);

		if (!wheel || !('backgroundSize' in this[0].style)) { // IE8-
			return this;
		}

		return this.each(function(){
			var img = this;
			var $img = $(img);

			function loaded() {
				var width = $img.width();
				var height = $img.height();
				var bgWidth = width;
				var bgHeight = height;
				var bgPosX = 0;
				var bgPosY = 0;

				var offsetBorderX = parseInt($img.css('border-left-width'),10);
				var offsetBorderY = parseInt($img.css('border-top-width'),10);

				var offsetPaddingX = parseInt($img.css('padding-left'),10);
				var offsetPaddingY = parseInt($img.css('padding-top'),10);

				$img.css({
					background: "url("+img.src+") 0 0 no-repeat",
					backgroundSize: width+'px '+height+'px',
					backgroundPosition: offsetPaddingX+'px '+offsetPaddingY+'px'
				});

				// Explicitly set the size to the current dimensions,
				// as the src is about to be changed to a 1x1 transparent png.
				img.width = img.width || img.naturalWidth;
				img.height = img.height || img.naturalHeight;
				img.src = transparentPNG;

				img[wheel] = function (e) {
					var offsetX;
					var offsetY;
					var deltaY = 0;
					var offsetParent = $img.offset();

					e.preventDefault();

					if (e.deltaY) { // FireFox 17+
						deltaY = e.deltaY;
					} else if (e.wheelDelta) {
						deltaY = -e.wheelDelta;
					}

					// IE isn't reporting the e.clientX/Y value for e.pageX/Y,
					// requiring the use of scrollTop/Left to calculate the actual e.pageX/Y values.
					offsetX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft - offsetParent.left - offsetBorderX - offsetPaddingX;
					offsetY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop - offsetParent.top - offsetBorderY - offsetPaddingY;

					// Record the offset between the bg edge and cursor:
					var bgCursorX = offsetX - bgPosX;
					var bgCursorY = offsetY - bgPosY;

					// Use the previous offset to get the percent offset between the bg edge and cursor:
					var bgRatioX = bgCursorX/bgWidth;
					var bgRatioY = bgCursorY/bgHeight;

					// Update the bg size:
					if (deltaY < 0) {
						bgWidth += bgWidth*settings.zoom;
						bgHeight += bgHeight*settings.zoom;
					} else {
						bgWidth -= bgWidth*settings.zoom;
						bgHeight -= bgHeight*settings.zoom;
					}

					// Take the percent offset and apply it to the new size:
					bgPosX = offsetX - (bgWidth * bgRatioX);
					bgPosY = offsetY - (bgHeight * bgRatioY);

					// Prevent zooming out beyond the starting size
					if (bgWidth <= width || bgHeight <= height) {
						bgWidth = width;
						bgHeight = height;
						bgPosX = bgPosY = 0;
					}

					if (bgPosX > 0) {
						bgPosX = 0;
					} else if (bgPosX < width - bgWidth) {
						bgPosX = width - bgWidth;
					}

					if (bgPosY > 0) {
						bgPosY = 0;
					} else if (bgPosY < height - bgHeight) {
						bgPosY = height - bgHeight;
					}

					img.style.backgroundSize = bgWidth + 'px ' + bgHeight + 'px';
					img.style.backgroundPosition = (bgPosX+offsetPaddingX) + 'px ' + (bgPosY+offsetPaddingY) + 'px';
				};

			}

			if (img.complete) {
				loaded();
			} else {
				$img.on('load.zoom', function(){
					$img.off('load.zoom');
					loaded();
				});
			}

		});
	};

	$.fn.wheelzoom.defaults = defaults;

}(window.jQuery);<commit_msg>documented cursor positioning calculations.<commit_after>// Wheelzoom 0.1.0
// (c) 2012 jacklmoore.com | license: www.opensource.org/licenses/mit-license.php
!function($){
	var transparentPNG = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==";
	var defaults = {
		zoom: 0.10
	};
	var wheel;

	if ( document.onmousewheel !== undefined ) { // Webkit/Opera/IE
		wheel = 'onmousewheel';
	}
	else if ( document.onwheel !== undefined) { // FireFox 17+
		wheel = 'onwheel';
	}

	$.fn.wheelzoom = function(options){
		var settings = $.extend({}, defaults, options);

		if (!wheel || !('backgroundSize' in this[0].style)) { // IE8-
			return this;
		}

		return this.each(function(){
			var img = this;
			var $img = $(img);

			function loaded() {
				var width = $img.width();
				var height = $img.height();
				var bgWidth = width;
				var bgHeight = height;
				var bgPosX = 0;
				var bgPosY = 0;

				var offsetBorderX = parseInt($img.css('border-left-width'),10);
				var offsetBorderY = parseInt($img.css('border-top-width'),10);

				var offsetPaddingX = parseInt($img.css('padding-left'),10);
				var offsetPaddingY = parseInt($img.css('padding-top'),10);

				$img.css({
					background: "url("+img.src+") 0 0 no-repeat",
					backgroundSize: width+'px '+height+'px',
					backgroundPosition: offsetPaddingX+'px '+offsetPaddingY+'px'
				});

				// Explicitly set the size to the current dimensions,
				// as the src is about to be changed to a 1x1 transparent png.
				img.width = img.width || img.naturalWidth;
				img.height = img.height || img.naturalHeight;
				img.src = transparentPNG;

				img[wheel] = function (e) {
					var offsetX;
					var offsetY;
					var deltaY = 0;
					var offsetParent = $img.offset();

					e.preventDefault();

					if (e.deltaY) { // FireFox 17+
						deltaY = e.deltaY;
					} else if (e.wheelDelta) {
						deltaY = -e.wheelDelta;
					}

					/*
						As far as I know, there is no good cross-browser way to get the cursor relative to the event target.
						To get the relative cursor position we have to calculate the position relative to the document
						for both the cursor and target element.

						For e.pageX/Y, IE reports the e.clientX/Y value.
						scrollTop/Left and e.clientX/Y are used to calculate the actual e.pageX/Y value.
					*/
					offsetX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft - offsetParent.left - offsetBorderX - offsetPaddingX;
					offsetY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop - offsetParent.top - offsetBorderY - offsetPaddingY;

					// Record the offset between the bg edge and cursor:
					var bgCursorX = offsetX - bgPosX;
					var bgCursorY = offsetY - bgPosY;

					// Use the previous offset to get the percent offset between the bg edge and cursor:
					var bgRatioX = bgCursorX/bgWidth;
					var bgRatioY = bgCursorY/bgHeight;

					// Update the bg size:
					if (deltaY < 0) {
						bgWidth += bgWidth*settings.zoom;
						bgHeight += bgHeight*settings.zoom;
					} else {
						bgWidth -= bgWidth*settings.zoom;
						bgHeight -= bgHeight*settings.zoom;
					}

					// Take the percent offset and apply it to the new size:
					bgPosX = offsetX - (bgWidth * bgRatioX);
					bgPosY = offsetY - (bgHeight * bgRatioY);

					// Prevent zooming out beyond the starting size
					if (bgWidth <= width || bgHeight <= height) {
						bgWidth = width;
						bgHeight = height;
						bgPosX = bgPosY = 0;
					}

					if (bgPosX > 0) {
						bgPosX = 0;
					} else if (bgPosX < width - bgWidth) {
						bgPosX = width - bgWidth;
					}

					if (bgPosY > 0) {
						bgPosY = 0;
					} else if (bgPosY < height - bgHeight) {
						bgPosY = height - bgHeight;
					}

					img.style.backgroundSize = bgWidth + 'px ' + bgHeight + 'px';
					img.style.backgroundPosition = (bgPosX+offsetPaddingX) + 'px ' + (bgPosY+offsetPaddingY) + 'px';
				};

			}

			if (img.complete) {
				loaded();
			} else {
				$img.on('load.zoom', function(){
					$img.off('load.zoom');
					loaded();
				});
			}

		});
	};

	$.fn.wheelzoom.defaults = defaults;

}(window.jQuery);<|endoftext|>


<commit_before>/* global Tile */

var BOOM = BOOM || {};

'use strict';

$(document).ready(function () {

    BOOM.GameBoardView = function (hTiles, vTiles) {
        this.hTiles = hTiles;
        this.vTiles = vTiles;
        this.tileSize = 25;
        this.gameBoard;
        this.gameBoardWidth = this.hTiles * this.tileSize;
        this.gameBoardContainer = $('#gameboardcontainer');
    };

    BOOM.GameBoardView.prototype = {

        constructor: BOOM.GameBoardView,

        createGameBoard: function() {
            this.gameBoard = $('
')
                .attr('id', 'gameboard')
                .width(this.hTiles * this.tileSize);
            for (var i = 0; i < this.vTiles; i++) {
                for (var j = 0; j < this.hTiles; j++) {
                    var tile = $('
').attr({
                        class: 'tile tile-covered',
                        'data-vPos': i,
                        'data-hPos': j
                    }).css('width', this.tileSize).css('height', this.tileSize);

                    tile.hover(
                        function () {
                            $(this).fadeTo(250, 0.5);
                        },
                        function () {
                            $(this).fadeTo(250, 1);
                        }
                    ).click(
                        function (event) {
                            BOOM.gbController.handleTileClick(event);
                        }
                    );

                    this.gameBoard.append(tile);
                }
            }
            this.gameBoardContainer.width(this.gameBoardWidth)
                .css('marginLeft', -this.gameBoardWidth / 2)
                .append(this.gameBoard)
                .fadeIn(250);
        },

        toggleFlag: function (vPos, hPos) {
            var tile = this.locateTile(vPos, hPos);
            if (tile.hasClass('tile-flagged')) {
                tile.removeClass('tile-flagged')
                    .addClass('tile-covered')
                    .hover(
                        function () {
                            $(this).fadeTo(250, 0.5);
                        },
                        function () {
                            $(this).fadeTo(250, 1);
                        });
            } else {
                tile.removeClass('tile-covered')
                    .addClass('tile-flagged')
                    .unbind('mouseenter mouseleave')
                    .fadeTo(250, 1);
            }
        },

        uncoverTile: function (vPos, hPos) {
            var tile = this.locateTile(vPos, hPos),
                tileClassName = 'tile-' + BOOM.gbModel.tileArray[vPos][hPos].tileType,
                tileText = BOOM.gbModel.tileArray[vPos][hPos].tileType === 9 ? '*' : BOOM.gbModel.tileArray[vPos][hPos].tileType;
            tile.unbind()
                .removeClass('tile-covered')
                .addClass(tileClassName)
                .html('
' + tileText + '
')
                .fadeTo(250, 1);
        },

        locateTile: function (vPos, hPos) {
            var index = Number(vPos) * this.hTiles + Number(hPos);
            return this.gameBoard.children().eq(index);
        },

    };

});

<commit_msg>view changes<commit_after>'use strict';

/* global Tile */

var BOOM = BOOM || {};

$(document).ready(function () {

    BOOM.GameBoardView = function (hTiles, vTiles, tileSize) {
        this.hTiles = hTiles;
        this.vTiles = vTiles;
        this.tileSize = tileSize;
        this.gameBoard;
        this.gameBoardWidth = this.hTiles * this.tileSize;
        this.gameBoardContainer = $('#gameboardcontainer');
        this.gameBoardShaker = $('#gameboardshaker');
    };

    BOOM.GameBoardView.prototype = {

        constructor: BOOM.GameBoardView,

        createGameBoard: function() {
            this.gameBoard = $('
')
                .attr('id', 'gameboard')
                .width(this.hTiles * this.tileSize);
            for (var i = 0; i < this.vTiles; i++) {
                for (var j = 0; j < this.hTiles; j++) {
                    var tile = $('
').attr({
                        class: 'tile tile-covered',
                        'data-vPos': i,
                        'data-hPos': j
                    }).css('width', this.tileSize).css('height', this.tileSize);

                    tile.hover(
                        function () {
                            $(this).fadeTo(250, 0.5);
                        },
                        function () {
                            $(this).fadeTo(250, 1);
                        }
                    ).click(
                        function (event) {
                            BOOM.gbController.handleTileClick(event);
                        }
                    );

                    this.gameBoard.append(tile);
                }
            }
            this.gameBoardContainer.width(this.gameBoardWidth)
                .css('marginLeft', -this.gameBoardWidth / 2)
                .append(this.gameBoard)
                .fadeIn(250);
        },

        toggleFlag: function (vPos, hPos) {
            var tile = this.locateTile(vPos, hPos);
            if (tile.hasClass('tile-flagged')) {
                tile.removeClass('tile-flagged')
                    .addClass('tile-covered')
                    .hover(
                        function () {
                            $(this).fadeTo(250, 0.5);
                        },
                        function () {
                            $(this).fadeTo(250, 1);
                        });
            } else {
                tile.removeClass('tile-covered')
                    .addClass('tile-flagged')
                    .unbind('mouseenter mouseleave')
                    .fadeTo(250, 1);
            }
        },

        uncoverTile: function (vPos, hPos) {
            var tile = this.locateTile(vPos, hPos);
            var tileType = BOOM.gbModel.tileArray[vPos][hPos].tileType;
            var tileClassName = 'tile-' + tileType;
            var tileText = tileType === 9 ? '*' : tileType;
            if (tileText === 0 || tileText === 12) {
                tileText = '';
            }
            tile.removeClass('tile-covered')
                .html('
' + tileText + '
')
                .fadeTo(250, 1)
                .addClass('tile-uncovered')
                .addClass(tileClassName)
                .unbind();
        },

        // finds position of tile in 1-dimenstional dom gameboard array
        // from its location in the model's 2-dimensional array
        locateTile: function (vPos, hPos) {
            var index = Number(vPos) * this.hTiles + Number(hPos);
            return this.gameBoard.children().eq(index);
        },

        gameOverLose: function () {
            this.gameBoardShaker.addClass('game-over-lose');
            setTimeout(function () {
                $('body').fadeOut(750, function () {
                    location.reload();
                });
            }, 5000);
        }

    };

});

<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Highlight node for navigation bar screen buttons, phet button, etc.
 *
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var HBox = require( 'SCENERY/nodes/HBox' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );

  /**
   * @param {number} width
   * @param {number} height
   * @param {Object} [options]
   * @constructor
   */
  function HighlightNode( width, height, options ) {

    options = _.extend( {
      whiteHighlight: true,
      hightlightWidth: 1,
      pickable: false
    }, options );

    //TODO joist#222 this logic should be replaced with options.fill
    var innerColor = options.whiteHighlight ? 'white' : 'black';
    var outerColor = options.whiteHighlight ? 'black' : 'white';

    var barOptions = {
      fill: new LinearGradient( 0, 0, 0, height ).addColorStop( 0, outerColor ).addColorStop( 0.5, innerColor ).addColorStop( 1, outerColor )
    };
    var leftBar = new Rectangle( 0, 0, options.hightlightWidth, height, barOptions );
    var rightBar = new Rectangle( 0, 0, options.hightlightWidth, height, barOptions );

    options.children = [ leftBar, rightBar ];
    options.spacing = width;
    HBox.call( this, options );
  }

  return inherit( HBox, HighlightNode );
} );
<commit_msg>#222 make not in HighlightNode about removing 'black'/'white' logic<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Highlight node for navigation bar screen buttons, phet button, etc.
 *
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var HBox = require( 'SCENERY/nodes/HBox' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );

  /**
   * @param {number} width
   * @param {number} height
   * @param {Object} [options]
   * @constructor
   */
  function HighlightNode( width, height, options ) {

    options = _.extend( {
      whiteHighlight: true,
      hightlightWidth: 1,
      pickable: false
    }, options );

    //TODO joist#222 innerColor should be options.fill, outerColor should be transparent
    var innerColor = options.whiteHighlight ? 'white' : 'black';
    var outerColor = options.whiteHighlight ? 'black' : 'white';

    var barOptions = {
      fill: new LinearGradient( 0, 0, 0, height ).addColorStop( 0, outerColor ).addColorStop( 0.5, innerColor ).addColorStop( 1, outerColor )
    };
    var leftBar = new Rectangle( 0, 0, options.hightlightWidth, height, barOptions );
    var rightBar = new Rectangle( 0, 0, options.hightlightWidth, height, barOptions );

    options.children = [ leftBar, rightBar ];
    options.spacing = width;
    HBox.call( this, options );
  }

  return inherit( HBox, HighlightNode );
} );
<|endoftext|>


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

/**
 * Control for changing a Property of type {number}.
 * Consists of a labeled value, slider and arrow buttons.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var Color = require( 'SCENERY/util/Color' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var HSlider = require( 'SUN/HSlider' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var NumberDisplay = require( 'SCENERY_PHET/NumberDisplay' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Tandem = require( 'TANDEM/Tandem' );
  var TandemText = require( 'TANDEM/scenery/nodes/TandemText' );
  var Text = require( 'SCENERY/nodes/Text' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var Util = require( 'DOT/Util' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  // phet-io modules
  var TNumberControl = require( 'ifphetio!PHET_IO/types/scenery-phet/TNumberControl' );

  /**
   * @param {string} title
   * @param {Property.<number>} numberProperty
   * @param {Range} numberRange
   * @param {Object} [options]
   * @constructor
   */
  function NumberControl( title, numberProperty, numberRange, options ) {

    options = _.extend( {

      startCallback: function() {}, // called when interaction begins
      endCallback: function() {}, // called when interaction ends
      enabledProperty: new Property( true ), // {Property.<boolean>} is this control enabled?
      disabledOpacity: 0.5, // {number} opacity used to make the control look disabled

      // title
      titleFont: new PhetFont( 12 ),
      titleMaxWidth: null, // {null|string} maxWidth to use for title, to constrain width for i18n

      // value
      valueFont: new PhetFont( 12 ),
      valueMaxWidth: null, // {null|string} maxWidth to use for value display, to constrain width for i18n
      decimalPlaces: 0,

      // {string} Pattern used to format the value. Must contain '{0}'.
      // If you want units or other verbiage, add them to the pattern, e.g. '{0} L'
      valuePattern: '{0}',

      // arrow buttons
      delta: 1,

      // slider
      majorTicks: [], // array of objects with these fields: { value: {number}, label: {Node} }
      minorTickSpacing: 0, // zero indicates no minor ticks
      trackSize: new Dimension2( 180, 3 ),
      thumbSize: new Dimension2( 17, 34 ),
      majorTickLength: 20,
      minorTickStroke: 'rgba( 0, 0, 0, 0.3 )',
      thumbFillEnabled: 'green',

      // A {function} that handles layout of subcomponents.
      // It has signature function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton )
      // and returns a Node. If you want to customize the layout, use one of the predefined creators
      // (see CREATE_LAYOUT_FUNCTION_*) or create your own function.
      layoutFunction: NumberControl.CREATE_LAYOUT_FUNCTION_1(),

      // {Object} options passed to layoutFunction. These are specific to the function used, see your
      // desired layout function for the set of options.
      // layoutOptions: {},

      tandem: null

    }, options );

    // highlight color for thumb defaults to a brighter version of the thumb color
    options.thumbFillHighlighted = options.thumbFillHighlighted || Color.toColor( options.thumbFillEnabled ).brighterColor();

    // constrain the slider value to the provided range and the same delta as the arrow buttons
    options.constrainValue = options.constrainValue || function( value ) {
        var newValue = Util.roundSymmetric( value / options.delta ) * options.delta;
        return numberRange.constrainValue( newValue );
      };

    // validate options
    assert && assert( options.disabledOpacity > 0 && options.disabledOpacity < 1, 'invalid disabledOpacity: ' + options.disabledOpacity );
    Tandem.validateOptions( options ); // The tandem is required when brand==='phet-io'

    var thisNode = this;

    var delta = options.delta; // to improve readability

    var titleNode = new TandemText( title, {
      font: options.titleFont,
      maxWidth: options.titleMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'titleNode' )
    } );

    var numberDisplay = new NumberDisplay( numberProperty, numberRange, {
      valuePattern: options.valuePattern,
      font: options.valueFont,
      decimalPlaces: options.decimalPlaces,
      maxWidth: options.valueMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'numberDisplay' )
    } );

    var arrowButtonOptions = {
      delta: options.delta,
      startCallback: options.startCallback,
      endCallback: options.endCallback,
      scale: 0.85
    };

    var leftArrowButton = new ArrowButton( 'left', function() {
      var value = numberProperty.get() - delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.max( value, numberRange.min ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'leftArrowButton' )
    }, arrowButtonOptions ) );

    var rightArrowButton = new ArrowButton( 'right', function() {
      var value = numberProperty.get() + delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.min( value, numberRange.max ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'rightArrowButton' )
    }, arrowButtonOptions ) );

    var arrowEnabledListener = function( value ) {
      leftArrowButton.enabled = ( value > numberRange.min );
      rightArrowButton.enabled = ( value < numberRange.max );
    };
    numberProperty.link( arrowEnabledListener );

    //TODO pass only options that are relevant to HSlider, see https://github.com/phetsims/scenery-phet/issues/255
    var slider = new HSlider( numberProperty, numberRange, _.extend( {
      startDrag: options.startCallback,
      endDrag: options.endCallback,
      constrainValue: options.constrainValue
    }, options, {

      // This uses a 3-arg extend so that the tandem is overriden properly
      tandem: options.tandem && options.tandem.createTandem( 'slider' )
    } ) );

    // major ticks
    var majorTicks = options.majorTicks;
    for ( var i = 0; i < majorTicks.length; i++ ) {
      slider.addMajorTick( majorTicks[ i ].value, majorTicks[ i ].label );
    }

    // minor ticks, exclude values where we already have major ticks
    if ( options.minorTickSpacing > 0 ) {
      for ( var minorTickValue = numberRange.min; minorTickValue <= numberRange.max; ) {
        if ( !_.find( majorTicks, function( majorTick ) { return majorTick.value === minorTickValue; } ) ) {
          slider.addMinorTick( minorTickValue );
        }
        minorTickValue += options.minorTickSpacing;
      }
    }

    options.children = [
      options.layoutFunction( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton )
    ];
    Node.call( this, options );

    // enabled/disable this control
    this.enabledProperty = options.enabledProperty; // @public
    var enabledObserver = function( enabled ) {
      thisNode.pickable = enabled;
      thisNode.opacity = enabled ? 1.0 : options.disabledOpacity;
      //TODO if !enabled, cancel any interaction that is in progress, see scenery#218
    };
    this.enabledProperty.link( enabledObserver );

    // @private
    this.disposeNumberControl = function() {
      numberDisplay.dispose();
      numberProperty.unlink( arrowEnabledListener );
      thisNode.enabledProperty.unlink( enabledObserver );
      slider.dispose();
      options.tandem && options.tandem.removeInstance( thisNode );
    };

    options.tandem && options.tandem.addInstance( this, TNumberControl );
  }

  sceneryPhet.register( 'NumberControl', NumberControl );

  return inherit( VBox, NumberControl, {

    // @public
    dispose: function() {
      this.disposeNumberControl();
      VBox.prototype.dispose.call( this );
    },

    // @public
    setEnabled: function( enabled ) { this.enabledProperty.set( enabled ); },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled: function() { return this.enabledProperty.get(); },
    get enabled() { return this.getEnabled(); }

  }, {

    /**
     * Creates a NumberControl with default tick marks for min and max values.
     * @param {string} label
     * @param {Property.<number>} property
     * @param {Range} range
     * @param {Object} [options]
     * @returns {NumberControl}
     * @static
     * @public
     */
    withMinMaxTicks: function( label, property, range, options ) {

      options = _.extend( {
        tickLabelFont: new PhetFont( 12 )
      }, options );

      options.majorTicks = [
        { value: range.min, label: new Text( range.min, { font: options.tickLabelFont } ) },
        { value: range.max, label: new Text( range.max, { font: options.tickLabelFont } ) }
      ];

      return new NumberControl( label, property, range, options );
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title number
     *  < ------|------ >
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    CREATE_LAYOUT_FUNCTION_1: function( options ) {

      options = _.extend( {
        align: 'center', // {string} horizontal alignment of rows, 'left'|'right'|'center'
        titleXSpacing: 5, // {number} horizontal spacing between title and number
        arrowButtonsXSpacing: 15, // {number} horizontal spacing between arrow buttons and slider
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          align: options.align,
          spacing: options.ySpacing,
          children: [
            new HBox( {
              spacing: options.titleXSpacing,
              children: [ titleNode, numberDisplay ]
            } ),
            new HBox( {
              spacing: options.arrowButtonsXSpacing,
              resize: false, // workaround for slider
              children: [ leftArrowButton, slider, rightArrowButton ]
            } )
          ]
        } );
      };
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title < number >
     *  ------|------
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    CREATE_LAYOUT_FUNCTION_2: function( options ) {

      options = _.extend( {
        align: 'center', // {string} horizontal alignment of rows, 'left'|'right'|'center'
        xSpacing: 5, // {number} horizontal spacing in top row
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          align: options.align,
          spacing: options.ySpacing,
          resize: false, // workaround for slider
          children: [
            new HBox( {
              spacing: options.xSpacing,
              children: [ titleNode, leftArrowButton, numberDisplay, rightArrowButton ]
            } ),
            slider
          ]
        } );
      };
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title
     *  < number >
     *  -------|-------
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    CREATE_LAYOUT_FUNCTION_3: function( options ) {

      options = _.extend( {
        alignTitle: 'center', // {string} horizontal alignment of title, relative to slider, 'left'|'right'|'center'
        alignNumber: 'center', // {string} horizontal alignment of number display, relative to slider, 'left'|'right'|'center'
        xSpacing: 5, // {number} horizontal spacing between arrow buttons and slider
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          spacing: options.ySpacing,
          align: options.alignTitle,
          children: [
            titleNode,
            new VBox( {
              spacing: options.ySpacing,
              resize: false, // workaround for slider
              align: options.alignNumber,
              children: [
                new HBox( {
                  spacing: options.xSpacing,
                  children: [ leftArrowButton, numberDisplay, rightArrowButton ]
                } ),
                slider
              ]
            } )
          ]
        } );
      };
    }
  } );
} );
<commit_msg>delete dead code in options, #217<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Control for changing a Property of type {number}.
 * Consists of a labeled value, slider and arrow buttons.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var Color = require( 'SCENERY/util/Color' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var HSlider = require( 'SUN/HSlider' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var NumberDisplay = require( 'SCENERY_PHET/NumberDisplay' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Tandem = require( 'TANDEM/Tandem' );
  var TandemText = require( 'TANDEM/scenery/nodes/TandemText' );
  var Text = require( 'SCENERY/nodes/Text' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var Util = require( 'DOT/Util' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  // phet-io modules
  var TNumberControl = require( 'ifphetio!PHET_IO/types/scenery-phet/TNumberControl' );

  /**
   * @param {string} title
   * @param {Property.<number>} numberProperty
   * @param {Range} numberRange
   * @param {Object} [options]
   * @constructor
   */
  function NumberControl( title, numberProperty, numberRange, options ) {

    options = _.extend( {

      startCallback: function() {}, // called when interaction begins
      endCallback: function() {}, // called when interaction ends
      enabledProperty: new Property( true ), // {Property.<boolean>} is this control enabled?
      disabledOpacity: 0.5, // {number} opacity used to make the control look disabled

      // title
      titleFont: new PhetFont( 12 ),
      titleMaxWidth: null, // {null|string} maxWidth to use for title, to constrain width for i18n

      // value
      valueFont: new PhetFont( 12 ),
      valueMaxWidth: null, // {null|string} maxWidth to use for value display, to constrain width for i18n
      decimalPlaces: 0,

      // {string} Pattern used to format the value. Must contain '{0}'.
      // If you want units or other verbiage, add them to the pattern, e.g. '{0} L'
      valuePattern: '{0}',

      // arrow buttons
      delta: 1,

      // slider
      majorTicks: [], // array of objects with these fields: { value: {number}, label: {Node} }
      minorTickSpacing: 0, // zero indicates no minor ticks
      trackSize: new Dimension2( 180, 3 ),
      thumbSize: new Dimension2( 17, 34 ),
      majorTickLength: 20,
      minorTickStroke: 'rgba( 0, 0, 0, 0.3 )',
      thumbFillEnabled: 'green',

      // A {function} that handles layout of subcomponents.
      // It has signature function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton )
      // and returns a Node. If you want to customize the layout, use one of the predefined creators
      // (see CREATE_LAYOUT_FUNCTION_*) or create your own function.
      layoutFunction: NumberControl.CREATE_LAYOUT_FUNCTION_1(),

      tandem: null

    }, options );

    // highlight color for thumb defaults to a brighter version of the thumb color
    options.thumbFillHighlighted = options.thumbFillHighlighted || Color.toColor( options.thumbFillEnabled ).brighterColor();

    // constrain the slider value to the provided range and the same delta as the arrow buttons
    options.constrainValue = options.constrainValue || function( value ) {
        var newValue = Util.roundSymmetric( value / options.delta ) * options.delta;
        return numberRange.constrainValue( newValue );
      };

    // validate options
    assert && assert( options.disabledOpacity > 0 && options.disabledOpacity < 1, 'invalid disabledOpacity: ' + options.disabledOpacity );
    Tandem.validateOptions( options ); // The tandem is required when brand==='phet-io'

    var thisNode = this;

    var delta = options.delta; // to improve readability

    var titleNode = new TandemText( title, {
      font: options.titleFont,
      maxWidth: options.titleMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'titleNode' )
    } );

    var numberDisplay = new NumberDisplay( numberProperty, numberRange, {
      valuePattern: options.valuePattern,
      font: options.valueFont,
      decimalPlaces: options.decimalPlaces,
      maxWidth: options.valueMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'numberDisplay' )
    } );

    var arrowButtonOptions = {
      delta: options.delta,
      startCallback: options.startCallback,
      endCallback: options.endCallback,
      scale: 0.85
    };

    var leftArrowButton = new ArrowButton( 'left', function() {
      var value = numberProperty.get() - delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.max( value, numberRange.min ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'leftArrowButton' )
    }, arrowButtonOptions ) );

    var rightArrowButton = new ArrowButton( 'right', function() {
      var value = numberProperty.get() + delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.min( value, numberRange.max ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'rightArrowButton' )
    }, arrowButtonOptions ) );

    var arrowEnabledListener = function( value ) {
      leftArrowButton.enabled = ( value > numberRange.min );
      rightArrowButton.enabled = ( value < numberRange.max );
    };
    numberProperty.link( arrowEnabledListener );

    //TODO pass only options that are relevant to HSlider, see https://github.com/phetsims/scenery-phet/issues/255
    var slider = new HSlider( numberProperty, numberRange, _.extend( {
      startDrag: options.startCallback,
      endDrag: options.endCallback,
      constrainValue: options.constrainValue
    }, options, {

      // This uses a 3-arg extend so that the tandem is overriden properly
      tandem: options.tandem && options.tandem.createTandem( 'slider' )
    } ) );

    // major ticks
    var majorTicks = options.majorTicks;
    for ( var i = 0; i < majorTicks.length; i++ ) {
      slider.addMajorTick( majorTicks[ i ].value, majorTicks[ i ].label );
    }

    // minor ticks, exclude values where we already have major ticks
    if ( options.minorTickSpacing > 0 ) {
      for ( var minorTickValue = numberRange.min; minorTickValue <= numberRange.max; ) {
        if ( !_.find( majorTicks, function( majorTick ) { return majorTick.value === minorTickValue; } ) ) {
          slider.addMinorTick( minorTickValue );
        }
        minorTickValue += options.minorTickSpacing;
      }
    }

    options.children = [
      options.layoutFunction( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton )
    ];
    Node.call( this, options );

    // enabled/disable this control
    this.enabledProperty = options.enabledProperty; // @public
    var enabledObserver = function( enabled ) {
      thisNode.pickable = enabled;
      thisNode.opacity = enabled ? 1.0 : options.disabledOpacity;
      //TODO if !enabled, cancel any interaction that is in progress, see scenery#218
    };
    this.enabledProperty.link( enabledObserver );

    // @private
    this.disposeNumberControl = function() {
      numberDisplay.dispose();
      numberProperty.unlink( arrowEnabledListener );
      thisNode.enabledProperty.unlink( enabledObserver );
      slider.dispose();
      options.tandem && options.tandem.removeInstance( thisNode );
    };

    options.tandem && options.tandem.addInstance( this, TNumberControl );
  }

  sceneryPhet.register( 'NumberControl', NumberControl );

  return inherit( VBox, NumberControl, {

    // @public
    dispose: function() {
      this.disposeNumberControl();
      VBox.prototype.dispose.call( this );
    },

    // @public
    setEnabled: function( enabled ) { this.enabledProperty.set( enabled ); },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled: function() { return this.enabledProperty.get(); },
    get enabled() { return this.getEnabled(); }

  }, {

    /**
     * Creates a NumberControl with default tick marks for min and max values.
     * @param {string} label
     * @param {Property.<number>} property
     * @param {Range} range
     * @param {Object} [options]
     * @returns {NumberControl}
     * @static
     * @public
     */
    withMinMaxTicks: function( label, property, range, options ) {

      options = _.extend( {
        tickLabelFont: new PhetFont( 12 )
      }, options );

      options.majorTicks = [
        { value: range.min, label: new Text( range.min, { font: options.tickLabelFont } ) },
        { value: range.max, label: new Text( range.max, { font: options.tickLabelFont } ) }
      ];

      return new NumberControl( label, property, range, options );
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title number
     *  < ------|------ >
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    CREATE_LAYOUT_FUNCTION_1: function( options ) {

      options = _.extend( {
        align: 'center', // {string} horizontal alignment of rows, 'left'|'right'|'center'
        titleXSpacing: 5, // {number} horizontal spacing between title and number
        arrowButtonsXSpacing: 15, // {number} horizontal spacing between arrow buttons and slider
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          align: options.align,
          spacing: options.ySpacing,
          children: [
            new HBox( {
              spacing: options.titleXSpacing,
              children: [ titleNode, numberDisplay ]
            } ),
            new HBox( {
              spacing: options.arrowButtonsXSpacing,
              resize: false, // workaround for slider
              children: [ leftArrowButton, slider, rightArrowButton ]
            } )
          ]
        } );
      };
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title < number >
     *  ------|------
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    CREATE_LAYOUT_FUNCTION_2: function( options ) {

      options = _.extend( {
        align: 'center', // {string} horizontal alignment of rows, 'left'|'right'|'center'
        xSpacing: 5, // {number} horizontal spacing in top row
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          align: options.align,
          spacing: options.ySpacing,
          resize: false, // workaround for slider
          children: [
            new HBox( {
              spacing: options.xSpacing,
              children: [ titleNode, leftArrowButton, numberDisplay, rightArrowButton ]
            } ),
            slider
          ]
        } );
      };
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title
     *  < number >
     *  -------|-------
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    CREATE_LAYOUT_FUNCTION_3: function( options ) {

      options = _.extend( {
        alignTitle: 'center', // {string} horizontal alignment of title, relative to slider, 'left'|'right'|'center'
        alignNumber: 'center', // {string} horizontal alignment of number display, relative to slider, 'left'|'right'|'center'
        xSpacing: 5, // {number} horizontal spacing between arrow buttons and slider
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          spacing: options.ySpacing,
          align: options.alignTitle,
          children: [
            titleNode,
            new VBox( {
              spacing: options.ySpacing,
              resize: false, // workaround for slider
              align: options.alignNumber,
              children: [
                new HBox( {
                  spacing: options.xSpacing,
                  children: [ leftArrowButton, numberDisplay, rightArrowButton ]
                } ),
                slider
              ]
            } )
          ]
        } );
      };
    }
  } );
} );
<|endoftext|>


<commit_before>// Copyright 2014-2015, University of Colorado Boulder

/**
 * Spinner for numbers.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var sun = require( 'SUN/sun' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );

  /**
   * @param {Property.<number>} numberProperty value, must be an integer
   * @param {Range} range range of values, min and max must be integers
   * @param {Object} [options]
   * @constructor
   */
  function NumberSpinner( numberProperty, range, options ) {

    assert && assert( range.contains( numberProperty.get() ), 'value ' + numberProperty.get() + ' is out of range ' + range.toString() );

    options = _.extend( {

      // {string} where to place the arrow buttons, 'leftRight'|'topBottom'|'bothRight'|'bothBottom'
      arrowsPosition: 'bothRight',

      // {number|null} arrows are by default scaled to fit dimensions of value background. This scales relative to that scale.
      arrowsScale: null,

      decimalPlaces: 0,
      font: new PhetFont( 28 ),
      xMargin: 5,
      yMargin: 3,
      xSpacing: 5,
      ySpacing: 3,
      cornerRadius: 5,
      touchXDilated: 20,
      touchYDilated: 10
    }, options );

    var valueOptions = {
      font: options.font,
      fill: 'black'
    };

    // compute max width
    var maxWidth = Math.max(
      new Text( Util.toFixed( range.max, options.decimalPlaces ), valueOptions ).width,
      new Text( Util.toFixed( range.min, options.decimalPlaces ), valueOptions ).width
    );

    // number
    var numberNode = new Text( numberProperty.get(), valueOptions );
    var backgroundNode = new Rectangle( 0, 0, maxWidth + ( 2 * options.xMargin ), numberNode.height + ( 2 * options.yMargin ),
      options.cornerRadius, options.cornerRadius, {
        fill: 'white',
        stroke: 'black',
        lineWidth: 0.5
      } );
    numberNode.center = backgroundNode.center;
    var valueParent = new Node( { children: [ backgroundNode, numberNode ] } );

    // buttons
    var incrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'up' : 'right';
    var incrementButton = new ArrowButton( incrementDirection, function() { numberProperty.set( numberProperty.get() + 1 ); } );
    var decrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'down' : 'left';
    var decrementButton = new ArrowButton( decrementDirection, function() { numberProperty.set( numberProperty.get() - 1 ); } );

    // pointer area
    incrementButton.touchArea = incrementButton.localBounds
      .dilatedXY( options.touchXDilated, options.touchYDilated )
      .shiftedY( -options.touchYDilated );
    decrementButton.touchArea = decrementButton.localBounds
      .dilatedXY( options.touchXDilated, options.touchYDilated )
      .shiftedY( options.touchYDilated );

    // arrow button scaling
    var arrowsScale;
    if ( !arrowsScale ) {
      if ( options.arrowsPosition === 'leftRight' ) {
        arrowsScale = valueParent.height / incrementButton.height;
      }
      else if ( options.arrowsPosition === 'topBottom' ) {
        arrowsScale = valueParent.width / incrementButton.width;
      }
      else if ( options.arrowsPosition === 'bothRight' ) {
        arrowsScale = ( ( valueParent.height / 2 ) - ( options.ySpacing / 2 ) ) / incrementButton.height;
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        arrowsScale = ( ( valueParent.width / 2 ) - ( options.xSpacing / 2 ) ) / incrementButton.width;
      }
      else {
        throw new Error( 'invalid options.arrowsPosition: ' + options.arrowsPosition );
      }
    }
    if ( options.arrowsScale ) {
      arrowsScale = arrowsScale * options.arrowsScale;
    }
    incrementButton.setScaleMagnitude( arrowsScale );
    decrementButton.setScaleMagnitude( arrowsScale );

    // layout
    if ( options.arrowsPosition === 'leftRight' ) {
      incrementButton.left = valueParent.right + options.xSpacing;
      decrementButton.right = valueParent.left - options.xSpacing;
      incrementButton.centerY = decrementButton.centerY = valueParent.centerY;
    }
    else if ( options.arrowsPosition === 'topBottom' ) {
      incrementButton.centerX = decrementButton.centerX = valueParent.centerX;
      incrementButton.bottom = valueParent.top - options.ySpacing;
      decrementButton.top = valueParent.bottom + options.ySpacing;
    }
    else if ( options.arrowsPosition === 'bothRight' ) {
      incrementButton.left = decrementButton.left = valueParent.right + options.xSpacing;
      incrementButton.bottom = valueParent.centerY - ( options.ySpacing / 2 );
      decrementButton.top = valueParent.centerY + ( options.ySpacing / 2 );
    }
    else if ( options.arrowsPosition === 'bothBottom' ) {
      incrementButton.left = valueParent.centerX + ( options.xSpacing / 2 );
      decrementButton.right = valueParent.centerX - ( options.xSpacing / 2 );
      incrementButton.top = decrementButton.top = valueParent.bottom + options.ySpacing;
    }
    else {
      throw new Error( 'invalid options.arrowsPosition: ' + options.arrowsPosition );
    }

    assert && assert( !this.children, 'decoration not supported' );
    options.children = [ valueParent, incrementButton, decrementButton ];

    Node.call( this, options );

    // @private When the value changes ...
    this.numberPropertyObserver = function( value ) {
      assert && assert( range.contains( value ) );

      // update the number and center it
      numberNode.text = Util.toFixed( value, options.decimalPlaces );
      numberNode.center = backgroundNode.center;

      // enable/disable arrow buttons
      incrementButton.enabled = ( value < range.max );
      decrementButton.enabled = ( value > range.min );
    };
    this.numberProperty = numberProperty; // @private
    this.numberProperty.link( this.numberPropertyObserver ); // must be unlinked in dispose
  }

  sun.register( 'NumberSpinner', NumberSpinner );

  return inherit( Node, NumberSpinner, {

    // @public Ensures that this node is eligible for GC.
    dispose: function() {
      this.numberProperty.unlink( this.numberPropertyObserver );
    }
  } );
} );
<commit_msg>note that work on NumberSpinner is in progress, https://github.com/phetsims/sun/issues/85<commit_after>// Copyright 2014-2015, University of Colorado Boulder

//TODO Further generalization of this component is in progress, see https://github.com/phetsims/sun/issues/85
/**
 * Spinner for numbers.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var sun = require( 'SUN/sun' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );

  /**
   * @param {Property.<number>} numberProperty value, must be an integer
   * @param {Range} range range of values, min and max must be integers
   * @param {Object} [options]
   * @constructor
   */
  function NumberSpinner( numberProperty, range, options ) {

    assert && assert( range.contains( numberProperty.get() ), 'value ' + numberProperty.get() + ' is out of range ' + range.toString() );

    options = _.extend( {

      // {string} where to place the arrow buttons, 'leftRight'|'topBottom'|'bothRight'|'bothBottom'
      arrowsPosition: 'bothRight',

      // {number|null} arrows are by default scaled to fit dimensions of value background. This scales relative to that scale.
      arrowsScale: null,

      decimalPlaces: 0,
      font: new PhetFont( 28 ),
      xMargin: 5,
      yMargin: 3,
      xSpacing: 5,
      ySpacing: 3,
      cornerRadius: 5,
      touchXDilated: 20,
      touchYDilated: 10
    }, options );

    var valueOptions = {
      font: options.font,
      fill: 'black'
    };

    // compute max width
    var maxWidth = Math.max(
      new Text( Util.toFixed( range.max, options.decimalPlaces ), valueOptions ).width,
      new Text( Util.toFixed( range.min, options.decimalPlaces ), valueOptions ).width
    );

    // number
    var numberNode = new Text( numberProperty.get(), valueOptions );
    var backgroundNode = new Rectangle( 0, 0, maxWidth + ( 2 * options.xMargin ), numberNode.height + ( 2 * options.yMargin ),
      options.cornerRadius, options.cornerRadius, {
        fill: 'white',
        stroke: 'black',
        lineWidth: 0.5
      } );
    numberNode.center = backgroundNode.center;
    var valueParent = new Node( { children: [ backgroundNode, numberNode ] } );

    // buttons
    var incrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'up' : 'right';
    var incrementButton = new ArrowButton( incrementDirection, function() { numberProperty.set( numberProperty.get() + 1 ); } );
    var decrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'down' : 'left';
    var decrementButton = new ArrowButton( decrementDirection, function() { numberProperty.set( numberProperty.get() - 1 ); } );

    // pointer area
    incrementButton.touchArea = incrementButton.localBounds
      .dilatedXY( options.touchXDilated, options.touchYDilated )
      .shiftedY( -options.touchYDilated );
    decrementButton.touchArea = decrementButton.localBounds
      .dilatedXY( options.touchXDilated, options.touchYDilated )
      .shiftedY( options.touchYDilated );

    // arrow button scaling
    var arrowsScale;
    if ( !arrowsScale ) {
      if ( options.arrowsPosition === 'leftRight' ) {
        arrowsScale = valueParent.height / incrementButton.height;
      }
      else if ( options.arrowsPosition === 'topBottom' ) {
        arrowsScale = valueParent.width / incrementButton.width;
      }
      else if ( options.arrowsPosition === 'bothRight' ) {
        arrowsScale = ( ( valueParent.height / 2 ) - ( options.ySpacing / 2 ) ) / incrementButton.height;
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        arrowsScale = ( ( valueParent.width / 2 ) - ( options.xSpacing / 2 ) ) / incrementButton.width;
      }
      else {
        throw new Error( 'invalid options.arrowsPosition: ' + options.arrowsPosition );
      }
    }
    if ( options.arrowsScale ) {
      arrowsScale = arrowsScale * options.arrowsScale;
    }
    incrementButton.setScaleMagnitude( arrowsScale );
    decrementButton.setScaleMagnitude( arrowsScale );

    // layout
    if ( options.arrowsPosition === 'leftRight' ) {
      incrementButton.left = valueParent.right + options.xSpacing;
      decrementButton.right = valueParent.left - options.xSpacing;
      incrementButton.centerY = decrementButton.centerY = valueParent.centerY;
    }
    else if ( options.arrowsPosition === 'topBottom' ) {
      incrementButton.centerX = decrementButton.centerX = valueParent.centerX;
      incrementButton.bottom = valueParent.top - options.ySpacing;
      decrementButton.top = valueParent.bottom + options.ySpacing;
    }
    else if ( options.arrowsPosition === 'bothRight' ) {
      incrementButton.left = decrementButton.left = valueParent.right + options.xSpacing;
      incrementButton.bottom = valueParent.centerY - ( options.ySpacing / 2 );
      decrementButton.top = valueParent.centerY + ( options.ySpacing / 2 );
    }
    else if ( options.arrowsPosition === 'bothBottom' ) {
      incrementButton.left = valueParent.centerX + ( options.xSpacing / 2 );
      decrementButton.right = valueParent.centerX - ( options.xSpacing / 2 );
      incrementButton.top = decrementButton.top = valueParent.bottom + options.ySpacing;
    }
    else {
      throw new Error( 'invalid options.arrowsPosition: ' + options.arrowsPosition );
    }

    assert && assert( !this.children, 'decoration not supported' );
    options.children = [ valueParent, incrementButton, decrementButton ];

    Node.call( this, options );

    // @private When the value changes ...
    this.numberPropertyObserver = function( value ) {
      assert && assert( range.contains( value ) );

      // update the number and center it
      numberNode.text = Util.toFixed( value, options.decimalPlaces );
      numberNode.center = backgroundNode.center;

      // enable/disable arrow buttons
      incrementButton.enabled = ( value < range.max );
      decrementButton.enabled = ( value > range.min );
    };
    this.numberProperty = numberProperty; // @private
    this.numberProperty.link( this.numberPropertyObserver ); // must be unlinked in dispose
  }

  sun.register( 'NumberSpinner', NumberSpinner );

  return inherit( Node, NumberSpinner, {

    // @public Ensures that this node is eligible for GC.
    dispose: function() {
      this.numberProperty.unlink( this.numberPropertyObserver );
    }
  } );
} );
<|endoftext|>


<commit_before>(function() {
    "use strict";

    var defaultOscillatorLineConfiguration = {
        filter : {
            type : 'lowpass',
            baseFrequency : 20000
        },
        oscillator : {
            script : '/* param1 : label param1 */\n/* param2 : label param2 */\n/* param3 : label param3 */\n/* param4 : label param4 */\n\nresult = Math.sin(2 * Math.PI / phase);'
        }
    };

    var Synth = function(context) {
        this.oscillatorLines = [];
        this.voices = [];
        this.context = context;

        this.createFilter();
        this.createGain();

        this.filter.connect(this.gain);
        this.gain.connect(this.context.destination);

        // setup the three oscillator lines
        this.addOscillatorLine();
        //this.addOscillatorLine();
        //this.addOscillatorLine();
    };

    Synth.prototype.oscillatorLines = null;
    Synth.prototype.voices = null;
    Synth.prototype.context = null;

    Synth.prototype.filter = null;
    Synth.prototype.gain = null;

    Synth.prototype.createFilter = function() {
        this.filter = this.context.createBiquadFilter();
    };

    Synth.prototype.createGain = function() {
        this.gain = this.context.createGain();
    };

    Synth.prototype.addOscillatorLine = function() {
        this.oscillatorLines.push(new App.OscillatorLine(defaultOscillatorLineConfiguration));
    };

    Synth.prototype.setOscillatorLineProperties = function(line, properties) {
        if(this.oscillatorLines[line]) {
            this.oscillatorLines[line].setProperties(properties);
        }
    };

    Synth.prototype.createVoice = function(note) {
        //TODO use a custom object instead of a litteral
        var voice = {
            lines : []
        };

        for(var i = 0; i < this.oscillatorLines.length; i++) {
            var nodes = this.oscillatorLines[i].createNode(this.context, note);
            voice.lines.push(nodes);

            nodes.output.connect(this.context.destination);
        }

        return voice;
    };

    App.Synth = Synth;
}(App || {}));<commit_msg>fix error in default oscillatorline configuration<commit_after>(function() {
    "use strict";

    var defaultOscillatorLineConfiguration = {
        filter : {
            type : 'lowpass',
            baseFrequency : 20000
        },
        oscillator : {
            script : '/* param1 : label param1 */\n/* param2 : label param2 */\n/* param3 : label param3 */\n/* param4 : label param4 */\n\nresult = Math.sin(2 * Math.PI * phase);'
        }
    };

    var Synth = function(context) {
        this.oscillatorLines = [];
        this.voices = [];
        this.context = context;

        this.createFilter();
        this.createGain();

        this.filter.connect(this.gain);
        this.gain.connect(this.context.destination);

        // setup the three oscillator lines
        this.addOscillatorLine();
        //this.addOscillatorLine();
        //this.addOscillatorLine();
    };

    Synth.prototype.oscillatorLines = null;
    Synth.prototype.voices = null;
    Synth.prototype.context = null;

    Synth.prototype.filter = null;
    Synth.prototype.gain = null;

    Synth.prototype.createFilter = function() {
        this.filter = this.context.createBiquadFilter();
    };

    Synth.prototype.createGain = function() {
        this.gain = this.context.createGain();
    };

    Synth.prototype.addOscillatorLine = function() {
        this.oscillatorLines.push(new App.OscillatorLine(defaultOscillatorLineConfiguration));
    };

    Synth.prototype.setOscillatorLineProperties = function(line, properties) {
        if(this.oscillatorLines[line]) {
            this.oscillatorLines[line].setProperties(properties);
        }
    };

    Synth.prototype.createVoice = function(note) {
        //TODO use a custom object instead of a litteral
        var voice = {
            lines : []
        };

        for(var i = 0; i < this.oscillatorLines.length; i++) {
            var nodes = this.oscillatorLines[i].createNode(this.context, note);
            voice.lines.push(nodes);

            nodes.output.connect(this.context.destination);
        }

        return voice;
    };

    Synth.prototype.noteOn = function(note) {
        var voice = this.createVoice(note);

        this.voices.push(voice);
    };

    Synth.prototype.noteOff = function(note) {
        //TODO to implement
    };

    Synth.prototype.panic = function() {
        //TODO to implement
    };

    App.Synth = Synth;
}(App || {}));<|endoftext|>


<commit_before>function DebtProto(){this.snowball=0,this.getMinPayment=function(){return"percent"===this.minpaymenttype?Math.max(Math.round(this.balance*this.minpayment),1):this.minpayment},this.getPayment=function(){return(this.makeMin||null===this.payment)&&(this.payment=this.getMinPayment()),this.payment},this.update=function(){this.balance=this.balance*(1+this.rate/12)-this.getPayment(),this.balance=Math.round(this.balance),this.balance<this.payment&&("dollar"===this.getminpaymenttype&&this.balance<this.minpayment&&(this.minpayment=this.balance),this.snowball=this.payment-this.balance,this.payment=this.balance),this.balance<=0&&(this.balance=0,this.minpayment=0,this.snowball=this.payment,this.payment=0)}}function Debt(a,b,c,d,e){this.name=a,this.balance=b,this.rate=c,this.payment="dollar"===e?d:d*b,this.makeMin=!0,this.minpayment=d,this.minpaymenttype=e,$.extend(this,new DebtProto)}function avalanche(a,b){var c=a.reduce(function(a,b){return a+b.snowball},0);if(c>0){var d=a.filter(function(a){return 0==a.snowball});if(a.forEach(function(a){a.snowball=0}),d.length>0){var e=b(d);e.payment=e.payment+c,e.makeMin=!1}}}function sumBalances(a){return a.reduce(function(a,b){return a+b.balance},0)}function sumMinpayments(a){return a.reduce(function(a,b){return a+b.getMinPayment()},0)}function sumPayments(a){return a.reduce(function(a,b){return a+b.getPayment()},0)}function totalAccounts(a){return a.reduce(function(a,b){return a+Math.min(b.balance,1)},0)}function lowestBalance(a){return a.reduce(function(a,b){return null==a?b:a.balance>b.balance?b:a.balance===b.balance&&a.rate<b.rate?b:a})}function highestRate(a){return a.reduce(function(a,b){return null==a?b:a.rateb.balance?b:a})}function usingMethod(a,b){var c=totalAccounts(a);"undefined"!=typeof b&&(b(a).makeMin=!1);for(var d={balances:[["year","balance","minimum payment"],[0,sumBalances(a),sumMinpayments(a)]],accounts:[["month","number of debts"],[0,c]],totalPaid:0,months:0},e=1,f=sumBalances(a);f>0;f=sumBalances(a)){a.forEach(function(a){a.update()}),e%12===0&&d.balances.push([e/12,f,sumMinpayments(a)]);var g=totalAccounts(a);if(g!==c&&(d.accounts.push([e,g]),c=g),d.totalPaid=d.totalPaid+sumPayments(a),"undefined"!=typeof b&&avalanche(a,b),e++,e>500)break}return e%12!=0&&d.balances.push([e/12,sumBalances(a),sumMinpayments(a)]),d.months=e,d}function getResults(a){var b=JSON.parse(JSON.stringify(a));b.forEach(function(a){$.extend(a,new DebtProto)});var c=JSON.parse(JSON.stringify(a));c.forEach(function(a){$.extend(a,new DebtProto)});var d=JSON.parse(JSON.stringify(a));d.forEach(function(a){$.extend(a,new DebtProto)});var e={highestRate:usingMethod(b,highestRate),lowestBalance:usingMethod(c,lowestBalance),justPayments:usingMethod(d)};return e}function getTimeString(a){var b=Math.floor(a/12),c=a-12*b;return b+" years "+c+" months"}function generateRow(a){var b="<input type='text' name='name' value='"+a.name+"' required />",c="$<input type='number' name='balance' class='input-dollar' min='0' value='"+a.balance+"' required />",d="<input type='number' name='rate' class='input-percent' min='0' max='100' step='0.01' value='"+100*a.rate+"' required />%",e="<input type='number' name='minpayment' min='0' value='";e+="percent"===a.minpaymenttype?100*a.minpayment:a.minpayment,e+="' required />";var f="<select name='minpaymenttype'><option value='dollar'"+("dollar"===a.minpaymenttype?" selected":"")+">dollar</option><option value='percent'"+("percent"===a.minpaymenttype?" selected":"")+">percent</option></select>",g="
"+b+"	"+c+"	"+d+"	"+e+"	"+f+"
";return g}function make(a,b){$("#"+a+"-total-paid").append("$"+b.totalPaid+" in "+getTimeString(b.months)),c3.generate({bindto:"#"+a+"-balance-chart",data:{x:"year",rows:b.balances,type:"spline"},axis:{x:{label:{text:"year",position:"outer-center"}},y:{label:{text:"dollars",position:"outer-middle"}}}}),c3.generate({bindto:"#"+a+"-account-chart",data:{x:"month",rows:b.accounts,type:"area-step"},axis:{x:{label:{text:"month",position:"outer-center"}},y:{label:{text:"number of debts",position:"outer-middle"}}},line:{step:{type:"step-after"}}})}$(document).ready(function(){$("#addRowButton").click(function(){var a=generateRow(new Debt("",0,0,0,0,"dollar"));$("#debt-table tbody").append(a)}),$("#removeRowButton").click(function(){$("#debt-table tbody tr").last().remove()});var a;if(hasKey("name")&&hasKey("balance")&&hasKey("rate")&&hasKey("minpayment"))a=getObjectArray(),a.forEach(function(a){$.extend(a,new DebtProto),a.name=decodeURIComponent(a.name.replace(/\+/g," ")),a.rate=a.rate/100,a.balance=parseInt(a.balance),"percent"===a.minpaymenttype?(a.minpayment=a.minpayment/100,a.payment=a.payment*a.minpayment):(a.minpayment=parseInt(a.minpayment),a.payment=a.minpayment),a.makeMin=!0});else{a=new Array;var b=new Debt("mortgage",15e4,.03,750,"dollar"),c=new Debt("car loan",2e4,.1,250,"dollar"),d=new Debt("credit card 1",5e3,.2,.04,"percent"),e=new Debt("credit card 2",100,.01,.04,"percent");a=[b,c,d,e]}for(var f=0;f<a.length;f++){var g=a[f];$("#debt-table").append(generateRow(g))}var h=getResults(a);make("highest-rate",h.highestRate),make("lowest-balance",h.lowestBalance),make("just-payments",h.justPayments)});
<commit_msg>Update avalanche.min.js<commit_after>function DebtProto(){this.getMinPayment=function(){var a;return a="percent"===this.minpaymenttype?Math.max(Math.round(this.balance*this.minpayment),1):this.minpayment,Math.min(a,this.balance)},this.update=function(){return this.balance=this.balance*(1+this.rate/12),this.balance=Math.round(this.balance),this.pay(this.getMinPayment())},this.pay=function(a){var b=0;return this.balance=this.balance-a,this.balance<=0&&(b=-this.balance,this.balance=0,this.minpayment=0),b}}function Debt(a,b,c,d,e){this.name=a,this.balance=b,this.rate=c,this.minpayment=d,this.minpaymenttype=e,$.extend(this,new DebtProto)}function avalanche(a,b,c){var d=c-sumMinpayments(a);for(d+=payDebts(a);d>0;)if(a=getDebtsWithBalances(a),a.length>0){var e=b(a);d=e.pay(d)}else d=0}function payDebts(a){return a.reduce(function(a,b){return a+b.update()},0)}function getDebtsWithBalances(a){return a.filter(function(a){return a.balance>0})}function sumBalances(a){return a.reduce(function(a,b){return a+b.balance},0)}function sumMinpayments(a){return a.reduce(function(a,b){return a+b.getMinPayment()},0)}function totalAccounts(a){return a.reduce(function(a,b){return a+Math.min(b.balance,1)},0)}function lowestBalance(a){return a.reduce(function(a,b){return null==a?b:a.balance>b.balance?b:a.balance===b.balance&&a.rate<b.rate?b:a})}function highestRate(a){return a.reduce(function(a,b){return null==a?b:a.rateb.balance?b:a})}function usingMethod(a,b){for(var c=totalAccounts(a),d=sumMinpayments(a),e={balances:[["month","balance","minimum payment"],[0,sumBalances(a),sumMinpayments(a)]],accounts:[["month","number of debts"],[0,c]],totalPaid:0,months:0},f=1,g=sumBalances(a);g>0&&f<=500;g=sumBalances(a),f++){"undefined"!=typeof b?avalanche(a,b,d):payDebts(a),f%12===0&&e.balances.push([f,g,sumMinpayments(a)]);var h=totalAccounts(a);h!==c&&(e.accounts.push([f,h]),c=h),"undefined"!=typeof b?d<sumBalances(a)?e.totalPaid=e.totalPaid+d:e.totalPaid=e.totalPaid+sumBalances(a):e.totalPaid=e.totalPaid+sumMinpayments(a)}return e.balances.push([f,sumBalances(a),sumMinpayments(a)]),e.months=f,e}function getResults(a){var b=JSON.parse(JSON.stringify(a));b.forEach(function(a){$.extend(a,new DebtProto)});var c=JSON.parse(JSON.stringify(a));c.forEach(function(a){$.extend(a,new DebtProto)});var d=JSON.parse(JSON.stringify(a));d.forEach(function(a){$.extend(a,new DebtProto)});var e={highestRate:usingMethod(b,highestRate),lowestBalance:usingMethod(c,lowestBalance),justPayments:usingMethod(d)};return e}function getTimeString(a){var b=Math.floor(a/12),c=a-12*b;return b+" years "+c+" months"}function generateRow(a){var b="<input type='text' name='name' value='"+a.name+"' required />",c="$<input type='number' name='balance' class='input-dollar' min='0' value='"+a.balance+"' required />",d="<input type='number' name='rate' class='input-percent' min='0' max='100' step='0.01' value='"+100*a.rate+"' required />%",e="<input type='number' name='minpayment' min='0' value='";e+="percent"===a.minpaymenttype?100*a.minpayment:a.minpayment,e+="' required />";var f="<select name='minpaymenttype'><option value='dollar'"+("dollar"===a.minpaymenttype?" selected":"")+">dollar</option><option value='percent'"+("percent"===a.minpaymenttype?" selected":"")+">percent</option></select>",g="
"+b+"	"+c+"	"+d+"	"+e+"	"+f+"
";return g}function make(a,b){$("#"+a+"-total-paid").append("$"+b.totalPaid.toLocaleString()+" in "+getTimeString(b.months)),c3.generate({bindto:"#"+a+"-balance-chart",data:{x:"month",rows:b.balances,type:"spline"},axis:{x:{label:{text:"month",position:"outer-center"}},y:{label:{text:"dollars",position:"outer-middle"}}}}),c3.generate({bindto:"#"+a+"-account-chart",data:{x:"month",rows:b.accounts,type:"area-step"},axis:{x:{label:{text:"month",position:"outer-center"}},y:{label:{text:"number of debts",position:"outer-middle"}}},line:{step:{type:"step-after"}}})}$(document).ready(function(){$("#addRowButton").click(function(){var a=generateRow(new Debt("",0,0,0,0,"dollar"));$("#debt-table tbody").append(a)}),$("#removeRowButton").click(function(){$("#debt-table tbody tr").last().remove()});var a;if(hasKey("name")&&hasKey("balance")&&hasKey("rate")&&hasKey("minpayment"))a=getObjectArray(),a.forEach(function(a){$.extend(a,new DebtProto),a.name=decodeURIComponent(a.name.replace(/\+/g," ")),a.rate=a.rate/100,a.balance=parseInt(a.balance),"percent"===a.minpaymenttype?a.minpayment=a.minpayment/100:a.minpayment=parseInt(a.minpayment)});else{a=new Array;var b=new Debt("credit card 1",3e3,.13,.04,"percent"),c=new Debt("credit card 2",8e3,.16,.04,"percent");a=[b,c]}for(var d=0;d<a.length;d++){var e=a[d];$("#debt-table").append(generateRow(e))}var f=getResults(a);make("highest-rate",f.highestRate),make("lowest-balance",f.lowestBalance),make("just-payments",f.justPayments)});
<|endoftext|>