<commit_before>/**
 * PLUGIN_NAME
 * Version: VERSION
 * URL: URL
 * Description: DESCRIPTION
 * Requires: JQUERY_VERSION, OTHER_PLUGIN(S), ETC.
 * Author: AUTHOR (AUTHOR_URL)
 * Copyright: Copyright 2013 YOUR_NAME
 * License: LICENSE_INFO
 */

// Plugin closure wrapper
// Uses dollar, but calls jQuery to prevent conflicts with other libraries
// Semicolon to prevent breakage with concatenation
// Pass in window as local variable for efficiency (could do same for document)
// Pass in undefined to prevent mutation in ES3
;(function($, document, window, undefined) {
    // Optional, but considered best practice by some
    "use strict";

    // Name the plug-in so it's only in one place
    var pluginName = 'tagfriends2';

    // Default options for the plug-in as a simple object
    var defaults = {
        property: 'value',
        anotherProperty: 10
    };

    // Plug-in constructor
    // This is the boilerplate to set up the plug-in to keep our actual logic in one place
    function Plugin(element, options) {
        this.element = element;
        this.id = $(this.element).prop('id')+'-tagger';

        this.tagger = $('

');
    	this.suggs = $('

').hide();

    	$('body').append(this.suggs);

        $(this.element).before(this.tagger);

        this.range = rangy.createRange();
        this.el = document.getElementById(this.id);
        this.anchorNode = this.anchorOffset = this.focusNode = this.focusOffset = 0;
        this.isCollapsed = true;
        this.suggestionsVisible = false;
        this.suggestionsDisabled = false;
        this.lastw = "";

        //console.log(this.id);

        /*var el = document.getElementById(this.id);

        range.selectNodeContents(el);
        var sel = rangy.getSelection();
        sel.setSingleRange(range);
        console.log(sel.toString());

        range.setStart(3);

        sel.setSingleRange(range);*/

        var instance = this;

        $(this.el).bind('click keyup', function(){
        	instance.range = rangy.createRange();
        	instance.range.selectNodeContents(this);

        	var sel = rangy.getSelection();
        	instance.anchorNode = sel.nativeSelection.anchorNode;
        	instance.anchorOffset = sel.anchorOffset;
        	instance.focusNode = sel.nativeSelection.focusNode;
        	instance.focusOffset = sel.focusOffset;
        	instance.isCollapsed = sel.isCollapsed;

        	/*console.log('anchorNode ' + instance.anchorNode.nodeValue);
        	console.log('anchorOffset ' + instance.anchorOffset);
        	console.log('focusNode ' + instance.focusNode.nodeValue);
        	console.log('focusOffset ' + instance.focusOffset);
        	console.log('isCollapsed ' + instance.isCollapsed);*/
        	console.log('-----------------------------');
        	console.log('suggdisd: '+instance.suggestionsDisabled);
        	isSuggestible(instance, function(d){
        		console.log('isSuggestible callback '+d);
        		instance.suggestionsDisabled = d;
        	});
        	console.log('suggdisd2: '+instance.suggestionsDisabled);

        	if(isTag(instance) && instance.isCollapsed === true && instance.suggestionsVisible === false){
        		suggest(instance, function(d){
        			console.log('suggest callback ' +d);
        			instance.suggestionsDisabled = d;
        		});
        	}
        	else{
        		hideSuggest(instance);
        	}

        	console.log('suggdisd3: '+instance.suggestionsDisabled);
        });

        // Merge the options given by the user with the defaults
        this.options = $.extend({}, defaults, options)

        // Attach data to the element
        this.$el      = $(element);
        this.$el.data(name, this);

        this._defaults = defaults;

        var meta      = this.$el.data(name + '-opts');
        this.opts     = $.extend(this._defaults, options, meta);

        this.init();

    }

    Plugin.prototype = {
        // Public functions accessible to users
        init: function() {

        }
    };

    $.fn[pluginName] = function(options) {
        // Iterate through each DOM element and return it
        return this.each(function() {
            // prevent multiple instantiations
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
            }
        });
    };

    var updateRange = function(i){

    }

    var getClosestBefore = function(text, offset){
    	for ( var int = offset; int > 0; int--) {
			if(text[int] == '@'){
				return int;
			}
		}
    	return 0;
    }

    var getClosestAfter = function(text, offset){
    	for ( var int = offset; int < text.length; int++) {
			if(text[int] == '@'){
				return int;
			}
		}
    	return text.length;
    }

    var isSuggestible = function(i, callback){
    	var text = i.focusNode.nodeValue;
    	if(text == null) text = '';
    	var tags = text.split('@');

    	var ws = getClosestBefore(text, i.focusOffset);
    	var we = getClosestAfter(text, i.focusOffset);

    	if(we == ws) we = text.length;

    	var neww = text.substring(ws, we);

    	console.log('new: '+ neww+' old: '+i.lastw);

    	callback(neww == i.lastw);
    }

    // Private function that is only called by the plugin
    var isTag = function(i) {
    	var text = i.focusNode.nodeValue;
    	if(text == null) text = '';
    	var tags = text.split('@');

    	var ws = getClosestBefore(text, i.focusOffset);
    	var we = getClosestAfter(text, i.focusOffset);

    	//console.log(ws + ' ' + we);

    	if(we == ws) we = text.length;

    	i.lastw = text.substring(ws, we);

    	//console.log('lastw: ' + i.lastw);

    	return i.lastw.trim().substring(0, 1) == '@';
    }

    var addTag = function(i, val, txt){
    	//var range = rangy.createRange();
    	i.range.refresh();
    	i.range.selectNodeContents(i.focusNode);
    	i.range.setStart(i.focusNode, i.focusOffset);

    	var sp1 = document.createElement('span');
    	$(sp1).attr('data-id', val);
    	$(sp1).addClass('tag');
    	sp1.contentEditable = false;
    	sp1.appendChild(document.createTextNode(txt));

    	i.range.insertNode(sp1);

    	var ws = getClosestBefore(i.range.getSelection().toString(), i.focusOffset);
    	var we = getClosestAfter(text, i.focusOffset);

    	console.log(ws + ' ' + we);
    	//i.range.selectNode(i.anchorNode);
    	//i.range.deleteContents();
    }

    var hideSuggest = function(i){
    	i.suggs.hide();
    }

    var suggest = function(i, callback){
    	var q = i.lastw.substring(1);
    	console.log('sug disabled: '+i.suggestionsDisabled);
    	if(q !== '' && i.suggestionsDisabled == false){
	    	var data = {};
	    	data[i.opts.pageKey] = i.opts.pageStart;
	    	data[i.opts.pagePerKey] = i.opts.pagePer;
	    	data[i.opts.queryKey] = q;

	    	$.ajax({
	    		url: i.opts.url,
	    		type:'post',
	    		data: data
			}).done(function(data) {
				/*for ( var int = 0; int < i.opts.dataObj.length; int++){
					data = data[i.opts.dataObj[int]];
					console.log(i.opts.dataObj[int]);
				}*/

				var ret = data.ret.data;

				//console.log(ret.length);

				if(ret.length == 0){
					console.log('empty data');
					hideSuggest(i);
					callback(true);
				}
				else{
					$(i.suggs).empty();

					$.each(ret, function(index, v){
						var li = $('
'+v.text+'
');
						$(i.suggs).append(li);
					});

					i.suggs.find('a').bind('click', function(e){
				     	e.preventDefault();
				      	addTag(i, $(this).data('val'), $(this).text());
				       	return false;
				    });

					var pos = $(i.tagger).offset();
			    	pos.top = pos.top + $(i.tagger).height();
			    	i.suggs.offset(pos).show();
			    	callback(false);
				}
			});
    	}
    }

})(jQuery, document, window);<commit_msg>set caret after inserted tag<commit_after>/**
 * PLUGIN_NAME
 * Version: VERSION
 * URL: URL
 * Description: DESCRIPTION
 * Requires: JQUERY_VERSION, OTHER_PLUGIN(S), ETC.
 * Author: AUTHOR (AUTHOR_URL)
 * Copyright: Copyright 2013 YOUR_NAME
 * License: LICENSE_INFO
 */

// Plugin closure wrapper
// Uses dollar, but calls jQuery to prevent conflicts with other libraries
// Semicolon to prevent breakage with concatenation
// Pass in window as local variable for efficiency (could do same for document)
// Pass in undefined to prevent mutation in ES3
;(function($, document, window, undefined) {
    // Optional, but considered best practice by some
    "use strict";

    // Name the plug-in so it's only in one place
    var pluginName = 'tagfriends2';

    // Default options for the plug-in as a simple object
    var defaults = {
        property: 'value',
        anotherProperty: 10
    };

    // Plug-in constructor
    // This is the boilerplate to set up the plug-in to keep our actual logic in one place
    function Plugin(element, options) {
        this.element = element;
        this.id = $(this.element).prop('id')+'-tagger';

        this.tagger = $('

');
    	this.suggs = $('

').hide();

    	$('body').append(this.suggs);

        $(this.element).before(this.tagger);

        this.range = rangy.createRangyRange();
        this.el = document.getElementById(this.id);
        this.anchorNode = this.anchorOffset = this.focusNode = this.focusOffset = 0;
        this.isCollapsed = true;
        this.suggestionsVisible = false;
        this.suggestionsDisabled = false;
        this.lastw = "";

        //console.log(this.id);

        /*var el = document.getElementById(this.id);

        range.selectNodeContents(el);
        var sel = rangy.getSelection();
        sel.setSingleRange(range);
        console.log(sel.toString());

        range.setStart(3);

        sel.setSingleRange(range);*/

        var instance = this;

        $(this.el).bind('click keyup', function(e){

        	instance.range = rangy.createRangyRange();
        	instance.range.selectNodeContents(this);

        	var sel = rangy.getSelection();
        	instance.anchorNode = sel.nativeSelection.anchorNode;
        	instance.anchorOffset = sel.anchorOffset;
        	instance.focusNode = sel.nativeSelection.focusNode;
        	instance.focusOffset = sel.focusOffset;
        	instance.isCollapsed = sel.isCollapsed;

        	/*console.log('anchorNode ' + instance.anchorNode.nodeValue);
        	console.log('anchorOffset ' + instance.anchorOffset);
        	console.log('focusNode ' + instance.focusNode.nodeValue);
        	console.log('focusOffset ' + instance.focusOffset);
        	console.log('isCollapsed ' + instance.isCollapsed);*/
        	//console.log('-----------------------------');
        	//console.log('suggdisd: '+instance.suggestionsDisabled);

        	if(e.keyCode == 8 || e.keyCode == 39){
        		endSpacer(instance);
        	}

        	isSuggestible(instance, function(d){
        		//console.log('isSuggestible callback '+d);
        		instance.suggestionsDisabled = d;
        	});
        	console.log('suggdisd2: '+instance.suggestionsDisabled);

        	if(isTag(instance) && instance.isCollapsed === true && instance.suggestionsVisible === false){
        		suggest(instance, function(d){
        			//console.log('suggest callback ' +d);
        			instance.suggestionsDisabled = d;
        		});
        	}
        	else{
        		hideSuggest(instance);
        	}

        	//console.log('suggdisd3: '+instance.suggestionsDisabled);
        });

        // Merge the options given by the user with the defaults
        this.options = $.extend({}, defaults, options)

        // Attach data to the element
        this.$el      = $(element);
        this.$el.data(name, this);

        this._defaults = defaults;

        var meta      = this.$el.data(name + '-opts');
        this.opts     = $.extend(this._defaults, options, meta);

        this.init();

    }

    Plugin.prototype = {
        // Public functions accessible to users
        init: function() {

        }
    };

    $.fn[pluginName] = function(options) {
        // Iterate through each DOM element and return it
        return this.each(function() {
            // prevent multiple instantiations
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
            }
        });
    };

    var endSpacer = function(i){
    	var str = i.focusNode.toString();
    	var len = str.length;
    	console.log(str.charCodeAt( len-1));
    	console.log(str.substr(len, -6));
    	if(str.substr(len, -1) != ' '){

	    	console.log(str);
	    	i.range.setStart(i.focusNode, i.focusOffset);
	    	var s = document.createTextNode('\u00a0');
	    	i.range.insertNode(s);
	    	i.range.collapseToPoint(i.focusNode, str.length);
    	}
    }

    var getClosestBefore = function(text, offset){
    	for ( var int = offset; int > 0; int--) {
			if(text[int] == '@'){
				return int;
			}
		}
    	return 0;
    }

    var getClosestAfter = function(text, offset){
    	for ( var int = offset; int < text.length; int++) {
			if(text[int] == '@'){
				return int;
			}
		}
    	return text.length;
    }

    var isSuggestible = function(i, callback){
    	var text = i.focusNode.nodeValue;
    	if(text == null) text = '';
    	var tags = text.split('@');

    	var ws = getClosestBefore(text, i.focusOffset);
    	var we = getClosestAfter(text, i.focusOffset);

    	if(we == ws) we = text.length;

    	var neww = text.substring(ws, we);

    	console.log('new: '+ neww+' old: '+i.lastw);

    	callback(neww == i.lastw);
    }

    // Private function that is only called by the plugin
    var isTag = function(i) {
    	var text = i.focusNode.nodeValue;
    	if(text == null) text = '';
    	var tags = text.split('@');

    	var ws = getClosestBefore(text, i.focusOffset);
    	var we = getClosestAfter(text, i.focusOffset);

    	//console.log(ws + ' ' + we);

    	if(we == ws) we = text.length;

    	i.lastw = text.substring(ws, we);

    	console.log('lastw: ' + i.lastw);

    	return i.lastw.trim().substring(0, 1) == '@';
    }

    var addTag = function(i, val, txt){
    	i.range.selectNodeContents(i.focusNode);
    	i.range.setStart(i.focusNode, i.focusOffset);

    	var sp1 = document.createElement('span');
    	$(sp1).attr('data-id', val);
    	$(sp1).addClass('tag');
    	sp1.contentEditable = false;
    	sp1.appendChild(document.createTextNode(txt));

    	i.range.insertNode(sp1);

    	i.range.selectNode(i.anchorNode);
    	var ds = getClosestBefore(i.range.toString(), i.range.toString().length-i.lastw.length);
    	var de = i.focusOffset;

    	console.log('delete ' + ds+ ' ' + de);

    	i.range.setStart(i.focusNode, ds);
    	i.range.setEnd(i.focusNode, de);
    	i.range.deleteContents();

    	i.range.selectNodeContents(i.focusNode);

    	i.range.setStartAfter(sp1);
    	i.range.setEndAfter(sp1);
    	rangy.getSelection().removeAllRanges();
    	rangy.getSelection().addRange(i.range);
    }

    var hideSuggest = function(i){
    	i.suggs.hide();
    }

    var suggest = function(i, callback){
    	var q = i.lastw.substring(1);
    	console.log('sug disabled: '+i.suggestionsDisabled);
    	if(q !== '' && i.suggestionsDisabled == false){
	    	var data = {};
	    	data[i.opts.pageKey] = i.opts.pageStart;
	    	data[i.opts.pagePerKey] = i.opts.pagePer;
	    	data[i.opts.queryKey] = q;

	    	$.ajax({
	    		url: i.opts.url,
	    		type:'post',
	    		data: data
			}).done(function(data) {
				/*for ( var int = 0; int < i.opts.dataObj.length; int++){
					data = data[i.opts.dataObj[int]];
					console.log(i.opts.dataObj[int]);
				}*/

				var ret = data.ret.data;

				//console.log(ret.length);

				if(ret.length == 0){
					console.log('empty data');
					hideSuggest(i);
					callback(true);
				}
				else{
					$(i.suggs).empty();

					$.each(ret, function(index, v){
						var li = $('
'+v.text+'
');
						$(i.suggs).append(li);
					});

					i.suggs.find('a').bind('click', function(e){
				     	e.preventDefault();
				      	addTag(i, $(this).data('val'), $(this).text());
				       	return false;
				    });

					var pos = $(i.tagger).offset();
			    	pos.top = pos.top + $(i.tagger).height();
			    	i.suggs.offset(pos).show();
			    	callback(false);
				}
			});
    	}
    }

})(jQuery, document, window);<|endoftext|>


<commit_before>//META {"name": "OnlineFriendCount", "source": "https://github.com/Zerthox/BetterDiscord-Plugins/blob/master/v1/OnlineFriendCount.plugin.js"} *//

/**
 * @author Zerthox
 * @version 1.0.0
 * @return {class} OnlineFriendCount plugin class
 */
const OnlineFriendCount = (() => {

    // Api constants
    const {React} = BdApi;

    /**
     * module storage
     */
    const Module = {
        status: BdApi.findModuleByProps("getStatus", "getOnlineFriendCount"),
        guilds: BDV2.WebpackModules.findByDisplayName("Guilds")
    };

    /**
     * selector storage
     */
    const Selector = {
        guildsWrapper: BdApi.findModuleByProps("wrapper", "unreadMentionsBar"),
        guilds: BdApi.findModuleByProps("listItem", "friendsOnline")
    };

    /**
     * storage for patches
     */
    const Patches = {};

    // return plugin class
    return class OnlineFriendCount {

        /**
         * @return {string} plugin name
         */
        getName() {
            return "OnlineFriendCount";
        }

        /**
         * @return {*} plugin description
         */
        getDescription() {
            return React.createElement("span", {"white-space": "pre-line"},
                "Add the old online friend count back to guild list. Because nostalgia."
            );
        }

        /**
         * @return {string} plugin version
         */
        getVersion() {
            return "1.0.0";
        }

        /**
         * @return {*} plugin author
         */
        getAuthor() {
            return React.createElement("a", {href: "https://github.com/Zerthox", target: "_blank"}, "Zerthox");
        }

        /**
         * plugin start function
         */
        start() {

            // patch settings render function
            Patches.settings = BdApi.monkeyPatch(Module.guilds.prototype, "render", {after: (d) => {

                // get return value
                const r = d.returnValue;

                // find guild list
                const l = r.props.children[1].props.children;

                // find index of separator
                const i = l.indexOf(l.find((e) => e.type.name === "N"));

                // check if online friends count is not inserted yet
                if (l[i].props.className !== Selector.guilds.friendsOnline) {

                    // insert online friends count before separator
                    l.splice(i, 0, React.createElement("div", {className: Selector.guilds.friendsOnline, style: {"margin-left": 0}}, `${Module.status.getOnlineFriendCount()} Online`));
                }

                // return modified return value
                return r;
            }});

            // force update
            this.forceUpdateAll();

            // console output
            console.log(`%c[${this.getName()}]%c v${this.getVersion()} enabled`, "color: #3a71c1; font-weight: 700;", "");
        }

        /**
         * plugin stop function
         */
        stop() {

            // revert all patches
            for (const f of Object.values(Patches)) {
                f();
            }

            // force update
            this.forceUpdateAll();

            // console output
            console.log(`%c[${this.getName()}]%c v${this.getVersion()} disabled`, "color: #3a71c1; font-weight: 700;", "");
        }

        /**
         * force update guild list
         */
        forceUpdateAll() {

            // force update guilds wrapper
            for (const e of document.getElementsByClassName(Selector.guildsWrapper.wrapper)) {
                BdApi.getInternalInstance(e).return.stateNode.forceUpdate();
            }
        }

    }
})();
<commit_msg>Fix error<commit_after>//META {"name": "OnlineFriendCount", "source": "https://github.com/Zerthox/BetterDiscord-Plugins/blob/master/v1/OnlineFriendCount.plugin.js"} *//

/**
 * @author Zerthox
 * @version 1.0.1
 * @return {class} OnlineFriendCount plugin class
 */
const OnlineFriendCount = (() => {

    // Api constants
    const {React} = BdApi;

    /**
     * module storage
     */
    const Module = {
        status: BdApi.findModuleByProps("getStatus", "getOnlineFriendCount"),
        guilds: BDV2.WebpackModules.findByDisplayName("Guilds")
    };

    /**
     * selector storage
     */
    const Selector = {
        guildsWrapper: BdApi.findModuleByProps("wrapper", "unreadMentionsBar"),
        guilds: BdApi.findModuleByProps("listItem", "friendsOnline")
    };

    /**
     * storage for patches
     */
    const Patches = {};

    // return plugin class
    return class OnlineFriendCount {

        /**
         * @return {string} plugin name
         */
        getName() {
            return "OnlineFriendCount";
        }

        /**
         * @return {*} plugin description
         */
        getDescription() {
            return React.createElement("span", {"white-space": "pre-line"},
                "Add the old online friend count back to guild list. Because nostalgia."
            );
        }

        /**
         * @return {string} plugin version
         */
        getVersion() {
            return "1.0.1";
        }

        /**
         * @return {*} plugin author
         */
        getAuthor() {
            return React.createElement("a", {href: "https://github.com/Zerthox", target: "_blank"}, "Zerthox");
        }

        /**
         * plugin start function
         */
        start() {

            // patch guilds render function
            Patches.guilds = BdApi.monkeyPatch(Module.guilds.prototype, "render", {after: (d) => {

                // get return value
                const r = d.returnValue;

                // find guild list
                const l = r.props.children[1].props.children;

                // find index of separator
                const i = l.indexOf(l.find((e) => e.type.name === "N"));

                // check if online friends count is not inserted yet
                if (l[i].props.className !== Selector.guilds.friendsOnline) {

                    // insert online friends count before separator
                    l.splice(i, 0, React.createElement("div", {className: Selector.guilds.friendsOnline, style: {"margin-left": 0}}, `${Module.status.getOnlineFriendCount()} Online`));
                }

                // return modified return value
                return r;
            }});

            // force update
            this.forceUpdateAll();

            // console output
            console.log(`%c[${this.getName()}]%c v${this.getVersion()} enabled`, "color: #3a71c1; font-weight: 700;", "");
        }

        /**
         * plugin stop function
         */
        stop() {

            // revert all patches
            for (const f of Object.values(Patches)) {
                f();
            }

            // force update
            this.forceUpdateAll();

            // console output
            console.log(`%c[${this.getName()}]%c v${this.getVersion()} disabled`, "color: #3a71c1; font-weight: 700;", "");
        }

        /**
         * force update guild list
         */
        forceUpdateAll() {

            // force update guilds wrapper
            for (const e of document.getElementsByClassName(Selector.guildsWrapper.wrapper)) {
                const i = BdApi.getInternalInstance(e);
                i && i.return.stateNode.forceUpdate();
            }
        }

    }
})();
<|endoftext|>


<commit_before>(function( $, undefined ) {

$.widget("ui.rotatable", $.ui.mouse, {

  options: {
    handle: false,
    angle: false,

    // callbacks
    start: null,
    rotate: null,
    stop: null
  },

  handle: function(handle) {
    if (handle === undefined) {
      return this.options.handle;
    }
    this.options.handle = handle;
  },

  angle: function(angle) {
    if (angle === undefined) {
      return this.options.angle;
    }
    this.options.angle = angle;
    this.performRotation(this.options.angle);
  },

  _create: function() {
    var handle;
    if (!this.options.handle) {
      handle = $(document.createElement('div'));
      handle.addClass('ui-rotatable-handle');
    }
    else {
      handle = this.options.handle;
    }

    this.listeners = {
      rotateElement: $.proxy(this.rotateElement, this),
      startRotate: $.proxy(this.startRotate, this),
      stopRotate: $.proxy(this.stopRotate, this),
    };

    handle.addClass('ui-rotatable-handle');
    handle.draggable({ helper: 'clone', start: this.dragStart });
    handle.bind('mousedown', this.listeners.startRotate);
    handle.appendTo(this.element);
    if(this.options.angle != false) {
      this.elementCurrentAngle = this.options.angle;
      performRotation(this.elementCurrentAngle);
    }
    else {
      this.elementCurrentAngle = 0;
    }
  },

  _destroy: function() {
    this.element.removeClass('ui-rotatable');
    this.element.find('.ui-rotatable-handle').remove();
  },

  performRotation: function(angle) {
    this.element.css('transform','rotate(' + angle + 'rad)');
    this.element.css('-moz-transform','rotate(' + angle + 'rad)');
    this.element.css('-webkit-transform','rotate(' + angle + 'rad)');
    this.element.css('-o-transform','rotate(' + angle + 'rad)');
  },

  getElementOffset: function() {
    this.performRotation(0);
    var offset = this.element.offset();
    this.performRotation(this.elementCurrentAngle);
    return offset;
  },

  getElementCenter: function() {
    var elementOffset = this.getElementOffset();
    var elementCentreX = elementOffset.left + this.element.width() / 2;
    var elementCentreY = elementOffset.top + this.element.height() / 2;
    return Array(elementCentreX, elementCentreY);
  },

  dragStart: function(event) {
    if (this.element) {
      return false;
    }
  },

  startRotate: function(event) {
    var center = this.getElementCenter();
    var startXFromCenter = event.pageX - center[0];
    var startYFromCenter = event.pageY - center[1];
    this.mouseStartAngle = Math.atan2(startYFromCenter, startXFromCenter);
    this.elementStartAngle = this.elementCurrentAngle;
    this.hasRotated = false;

    this._propagate("start", event);

    $(document).bind('mousemove', this.listeners.rotateElement);
    $(document).bind('mouseup', this.listeners.stopRotate);

    return false;
  },

  rotateElement: function(event) {
    if (!this.element) {
      return false;
    }

    var center = this.getElementCenter();
    var xFromCenter = event.pageX - center[0];
    var yFromCenter = event.pageY - center[1];
    var mouseAngle = Math.atan2(yFromCenter, xFromCenter);
    var rotateAngle = mouseAngle - this.mouseStartAngle + this.elementStartAngle;

    this.performRotation(rotateAngle);
    var previousRotateAngle = this.elementCurrentAngle;
    this.elementCurrentAngle = rotateAngle;

    // Plugins callbacks need to be called first.
    this._propagate("rotate", event);

    if (previousRotateAngle != rotateAngle) {
      this._trigger("rotate", event, this.ui());
      this.hasRotated = true;
    }

    return false;
  },

  stopRotate: function(event) {
    if (!this.element) {
      return;
    }

    $(document).unbind('mousemove', this.listeners.rotateElement);
    $(document).unbind('mouseup', this.listeners.stopRotate);

    this.elementStopAngle = this.elementCurrentAngle;
    if (this.hasRotated) {
      this._propagate("stop", event);
    }

    setTimeout( function() { this.element = false; }, 10 );
    return false;
  },

  _propagate: function(n, event) {
    $.ui.plugin.call(this, n, [event, this.ui()]);
    (n !== "rotate" && this._trigger(n, event, this.ui()));
  },

  plugins: {},

  ui: function() {
    return {
      element: this.element,
      angle: {
        start: this.elementStartAngle,
        current: this.elementCurrentAngle,
        stop: this.elementStopAngle,
      },
    };
  },

});

})(jQuery);
<commit_msg>fix bad reference to performRotation method.<commit_after>(function( $, undefined ) {

$.widget("ui.rotatable", $.ui.mouse, {

  options: {
    handle: false,
    angle: false,

    // callbacks
    start: null,
    rotate: null,
    stop: null
  },

  handle: function(handle) {
    if (handle === undefined) {
      return this.options.handle;
    }
    this.options.handle = handle;
  },

  angle: function(angle) {
    if (angle === undefined) {
      return this.options.angle;
    }
    this.options.angle = angle;
    this.performRotation(this.options.angle);
  },

  _create: function() {
    var handle;
    if (!this.options.handle) {
      handle = $(document.createElement('div'));
      handle.addClass('ui-rotatable-handle');
    }
    else {
      handle = this.options.handle;
    }

    this.listeners = {
      rotateElement: $.proxy(this.rotateElement, this),
      startRotate: $.proxy(this.startRotate, this),
      stopRotate: $.proxy(this.stopRotate, this),
    };

    handle.addClass('ui-rotatable-handle');
    handle.draggable({ helper: 'clone', start: this.dragStart });
    handle.bind('mousedown', this.listeners.startRotate);
    handle.appendTo(this.element);
    if(this.options.angle != false) {
      this.elementCurrentAngle = this.options.angle;
      this.performRotation(this.elementCurrentAngle);
    }
    else {
      this.elementCurrentAngle = 0;
    }
  },

  _destroy: function() {
    this.element.removeClass('ui-rotatable');
    this.element.find('.ui-rotatable-handle').remove();
  },

  performRotation: function(angle) {
    this.element.css('transform','rotate(' + angle + 'rad)');
    this.element.css('-moz-transform','rotate(' + angle + 'rad)');
    this.element.css('-webkit-transform','rotate(' + angle + 'rad)');
    this.element.css('-o-transform','rotate(' + angle + 'rad)');
  },

  getElementOffset: function() {
    this.performRotation(0);
    var offset = this.element.offset();
    this.performRotation(this.elementCurrentAngle);
    return offset;
  },

  getElementCenter: function() {
    var elementOffset = this.getElementOffset();
    var elementCentreX = elementOffset.left + this.element.width() / 2;
    var elementCentreY = elementOffset.top + this.element.height() / 2;
    return Array(elementCentreX, elementCentreY);
  },

  dragStart: function(event) {
    if (this.element) {
      return false;
    }
  },

  startRotate: function(event) {
    var center = this.getElementCenter();
    var startXFromCenter = event.pageX - center[0];
    var startYFromCenter = event.pageY - center[1];
    this.mouseStartAngle = Math.atan2(startYFromCenter, startXFromCenter);
    this.elementStartAngle = this.elementCurrentAngle;
    this.hasRotated = false;

    this._propagate("start", event);

    $(document).bind('mousemove', this.listeners.rotateElement);
    $(document).bind('mouseup', this.listeners.stopRotate);

    return false;
  },

  rotateElement: function(event) {
    if (!this.element) {
      return false;
    }

    var center = this.getElementCenter();
    var xFromCenter = event.pageX - center[0];
    var yFromCenter = event.pageY - center[1];
    var mouseAngle = Math.atan2(yFromCenter, xFromCenter);
    var rotateAngle = mouseAngle - this.mouseStartAngle + this.elementStartAngle;

    this.performRotation(rotateAngle);
    var previousRotateAngle = this.elementCurrentAngle;
    this.elementCurrentAngle = rotateAngle;

    // Plugins callbacks need to be called first.
    this._propagate("rotate", event);

    if (previousRotateAngle != rotateAngle) {
      this._trigger("rotate", event, this.ui());
      this.hasRotated = true;
    }

    return false;
  },

  stopRotate: function(event) {
    if (!this.element) {
      return;
    }

    $(document).unbind('mousemove', this.listeners.rotateElement);
    $(document).unbind('mouseup', this.listeners.stopRotate);

    this.elementStopAngle = this.elementCurrentAngle;
    if (this.hasRotated) {
      this._propagate("stop", event);
    }

    setTimeout( function() { this.element = false; }, 10 );
    return false;
  },

  _propagate: function(n, event) {
    $.ui.plugin.call(this, n, [event, this.ui()]);
    (n !== "rotate" && this._trigger(n, event, this.ui()));
  },

  plugins: {},

  ui: function() {
    return {
      element: this.element,
      angle: {
        start: this.elementStartAngle,
        current: this.elementCurrentAngle,
        stop: this.elementStopAngle,
      },
    };
  },

});

})(jQuery);
<|endoftext|>


<commit_before>/**
 * Tile loading progress bar
 */
L.Control.Progress = L.Control.extend({
    options: {
        position: 'topleft',
        text: 'Loading',
        // id of element to add this control to, instead of corner (optional)
        div: null
    },

    initialize: function (tileLayer, options) {
        L.setOptions(this, options);

        this._layer = tileLayer;
    },

    addTo: function (map) {
        L.Control.prototype.addTo.apply(this, arguments);

        if (this.options.div) {
            this._container.parentNode.removeChild(this._container);
            L.DomUtil.get(this.options.div).appendChild(this._container);
        }

        return this;
    },

    onAdd: function (map) {
        var container, text;

        this._map = map;

        container = L.DomUtil.create('div', 'progress leaflet-bar hidden');

        text = L.DomUtil.create('span', 'progress-text');
        text.innerHTML = this.options.text;
        container.appendChild(text);

        this._bar = L.DomUtil.create('progress', 'progress-bar');
        container.appendChild(this._bar);

        this._layer.on('loading', this._onLoading, this);
        this._layer.on('load', this._onLoad, this);
        this._layer.on('tileload', this._onTileLoad, this);
        // L.TileLayer.Vector events
        this._layer.on('tileloading', this._onTileLoading, this);
        this._layer.on('tileerror', this._onTileLoad, this);
        this._layer.on('tileabort', this._onTileLoad, this);

        return container;
    },

    onRemove: function (map) {
        this._layer.off('loading', this._onLoading, this);
        this._layer.off('load', this._onLoad, this);
        this._layer.off('tileload', this._onTileLoad, this);
        this._layer.off('tileloading', this._onTileLoading, this);
        this._layer.off('tileerror', this._onTileLoad, this);
        this._layer.off('tileabort', this._onTileLoad, this);
    },

    _onLoading: function () {
        this._bar.max = 0.1; // must be greater than zero, so adjust later
        this._bar.value = 0;
        L.DomUtil.removeClass(this._container, 'hidden');
    },

    _onLoad: function () {
        window.setTimeout(L.bind(function() {
            L.DomUtil.addClass(this._container, 'hidden');
        }, this), 200);
    },

    _onTileLoading: function (evt) {
        // max can't be initialized with 0, so adjust once to match number of tiles
        if (this._bar.max === 0.1) {
            this._bar.max = 1;
        } else {
            this._bar.max++;
        }
    },

    _onTileLoad: function (evt) {
        this._bar.value++;
    }
});
<commit_msg>update progress control (tileloading > tileloadstart event)<commit_after>/**
 * Tile loading progress bar
 */
L.Control.Progress = L.Control.extend({
    options: {
        position: 'topleft',
        text: 'Loading',
        // id of element to add this control to, instead of corner (optional)
        div: null
    },

    initialize: function (tileLayer, options) {
        L.setOptions(this, options);

        this._layer = tileLayer;
    },

    addTo: function (map) {
        L.Control.prototype.addTo.apply(this, arguments);

        if (this.options.div) {
            this._container.parentNode.removeChild(this._container);
            L.DomUtil.get(this.options.div).appendChild(this._container);
        }

        return this;
    },

    onAdd: function (map) {
        var container, text;

        this._map = map;

        container = L.DomUtil.create('div', 'progress leaflet-bar hidden');

        text = L.DomUtil.create('span', 'progress-text');
        text.innerHTML = this.options.text;
        container.appendChild(text);

        this._bar = L.DomUtil.create('progress', 'progress-bar');
        container.appendChild(this._bar);

        this._layer.on('loading', this._onLoading, this);
        this._layer.on('load', this._onLoad, this);
        this._layer.on('tileload', this._onTileLoad, this);
        this._layer.on('tileloadstart', this._onTileLoading, this);
        this._layer.on('tileerror', this._onTileLoad, this);
        this._layer.on('tileabort', this._onTileLoad, this);

        return container;
    },

    onRemove: function (map) {
        this._layer.off('loading', this._onLoading, this);
        this._layer.off('load', this._onLoad, this);
        this._layer.off('tileload', this._onTileLoad, this);
        this._layer.off('tileloadstart', this._onTileLoading, this);
        this._layer.off('tileerror', this._onTileLoad, this);
        this._layer.off('tileabort', this._onTileLoad, this);
    },

    _onLoading: function () {
        this._bar.max = 0.1; // must be greater than zero, so adjust later
        this._bar.value = 0;
        L.DomUtil.removeClass(this._container, 'hidden');
    },

    _onLoad: function () {
        window.setTimeout(L.bind(function() {
            L.DomUtil.addClass(this._container, 'hidden');
        }, this), 200);
    },

    _onTileLoading: function (evt) {
        // max can't be initialized with 0, so adjust once to match number of tiles
        if (this._bar.max === 0.1) {
            this._bar.max = 1;
        } else {
            this._bar.max++;
        }
    },

    _onTileLoad: function (evt) {
        this._bar.value++;
    }
});
<|endoftext|>


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

/**
 * This class aims to have the same interface as ArrowNode and support the same API but to do it by transforming shapes where
 * possible instead of creating new shapes, in order to improve performance, see https://github.com/phetsims/scenery-phet/issues/28
 *
 * TODO: Add support for "collar" stroke and shrunken arrowheads.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  // Imports
  var inherit = require( 'PHET_CORE/inherit' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Matrix3 = require( 'DOT/Matrix3' );
  var Shape = require( 'KITE/Shape' );
  var ArrowNode = require( 'SCENERY_PHET/ArrowNode' );
  var ArrowShape = require( 'SCENERY_PHET/ArrowShape' );

  /*
   * @param {number} tailX
   * @param {number} tailY
   * @param {number} tipX
   * @param {number} tipY
   * @param {Object} options
   * @constructor
   */
  function MutableArrowNode( tailX, tailY, tipX, tipY, options ) {

    // default options
    options = _.extend( {
      headHeight: 10,
      headWidth: 10,
      tailWidth: 5,
      doubleHead: false, // true puts heads on both ends of the arrow, false puts a head at the tip
      fill: 'black',
      stroke: 'black',
      lineWidth: 1
    }, options );

    // things you're likely to mess up, add more as needed
    assert && assert( options.headWidth > options.tailWidth );

    //The tail of the Arrow is a
    Node.call( this, options );

    //Intermediate parent for setting the rotation of the node
    this.parent = new Node();

    //Rectangle for body and stripes along the sides for stroke
    this.body = new Node( {
      children: [
        new Rectangle( 0, 0, options.tailWidth, 1, {fill: options.fill} ),
        new Rectangle( -options.lineWidth / 2, 0, options.lineWidth, 1, {fill: options.stroke} ),
        new Rectangle( options.tailWidth - options.lineWidth / 2, 0, options.lineWidth, 1, {fill: options.stroke} )
      ]} );

    //Head shape.  Don't close so the stroke can be used for the angled parts, but not the flat part (the "collar").
    var headShape = new Shape().moveTo( -options.headWidth / 2, 0 ).lineTo( 0, options.headHeight ).lineTo( options.headWidth / 2, 0 );
    this.head = new Path( headShape, {fill: options.fill, stroke: options.stroke, lineWidth: options.lineWidth} );

    this.tailEdgeStroke = new Rectangle( 0, -options.lineWidth / 2, options.tailWidth, options.lineWidth, {fill: 'black'} );

    this.parent.addChild( this.head );
    this.parent.addChild( this.body );
    this.parent.addChild( this.tailEdgeStroke );

    this.addChild( this.parent );
    this.options = options;

    //Fall back to ArrowNode when arrowhead is small
    //TODO: We could replace this with optimized shrunken arrowhead and arrow tip too
    this.arrowNode = new ArrowNode( 0, 0, 0, 0, options );
    this.arrowNode.visible = false;
    this.addChild( this.arrowNode );

    this.optimized = true;
    this.setTailAndTip( tailX, tailY, tipX, tipY );
  }

  return inherit( Node, MutableArrowNode, {
    getMatrix: function( x, y, angle, sx, sy, originX, originY ) {

      //Translate to the desired location
      var matrix = Matrix3.translation( x, y );

      //Rotation and translation can happen in any order
      matrix = matrix.multiplyMatrix( Matrix3.rotation2( angle ) );

      //Update scale if not 1
      if ( originX !== 1 || originY !== 1 ) {
        matrix = matrix.multiplyMatrix( Matrix3.scaling( sx, sy ) );
      }

      //Update origin if non-zero
      if ( originX !== 0 || originY !== 0 ) {

        //Think of it as a multiplying the Vector2 to the right, so this step happens first actually.  Use it to center the registration point
        matrix = matrix.multiplyMatrix( Matrix3.translation( -originX, -originY ) );
      }

      return matrix;
    },
    setTailAndTip: function( tailX, tailY, tipX, tipY ) {
      if ( this.tailX !== tailX || this.tailY !== tailY || this.tipX !== tipX || this.tipY !== tipY ) {
        var dx = tipX - tailX;
        var dy = tipY - tailY;

        var bodyDistance = Math.sqrt( dx * dx + dy * dy ) - this.options.headHeight;

        var willBeOptimized = bodyDistance > this.options.headHeight;

        if ( willBeOptimized ) {

          if ( !this.optimized ) {
            this.body.visible = true;
            this.head.visible = true;
            this.tailEdgeStroke.visible = true;
            this.arrowNode.visible = false;
          }

          var angle = Math.atan2( dy, dx );

          var bodyTipX = bodyDistance * Math.cos( angle ) + tailX;
          var bodyTipY = bodyDistance * Math.sin( angle ) + tailY;

          //Overlap a bit so it looks like a solid piece
          var overlap = 2E-1;

          this.body.setMatrix( this.getMatrix( tailX, tailY, angle - Math.PI / 2, 1, bodyDistance + overlap, this.options.tailWidth / 2, 0 ) );
          this.head.setMatrix( this.getMatrix( bodyTipX, bodyTipY, angle - Math.PI / 2, 1, 1, 0, 0 ) );
          this.tailEdgeStroke.setMatrix( this.getMatrix( tailX, tailY, angle - Math.PI / 2, 1, 1, this.options.tailWidth / 2, 0 ) );
        }
        else {
          if ( this.optimized ) {
            this.body.visible = false;
            this.head.visible = false;
            this.tailEdgeStroke.visible = false;
            this.arrowNode.visible = true;
          }

          //For short arrows, the head height should be half of the arrow length.  See https://github.com/phetsims/scenery-phet/issues/30
          var arrowLength = Math.sqrt( (tipX - tailX) * (tipX - tailX) + (tipY - tailY) * (tipY - tailY) );
          var headHeight = Math.min( Math.abs( arrowLength ) / 2, this.options.headHeight );
          var newOptions = _.extend( _.clone( this.options ), {headHeight: headHeight} );
          this.arrowNode.setShape( new ArrowShape( tailX, tailY, tipX, tipY, newOptions ) );
        }

        this.optimized = willBeOptimized;
        this.tailX = tailX;
        this.tailY = tailY;
        this.tipX = tipX;
        this.tipY = tipY;
      }
    },
    setShape: function() {
      console.log( 'set shape called on MutableArrowNode.' );
    }
  } );
} );
<commit_msg>Added a flag 'headHeightMaximumHalf' to set the head height to be (at most) half the length of the arrow.  See https://github.com/phetsims/scenery-phet/issues/30<commit_after>// Copyright 2002-2014, University of Colorado Boulder

/**
 * This class aims to have the same interface as ArrowNode and support the same API but to do it by transforming shapes where
 * possible instead of creating new shapes, in order to improve performance, see https://github.com/phetsims/scenery-phet/issues/28
 *
 * TODO: Add support for "collar" stroke and shrunken arrowheads.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  // Imports
  var inherit = require( 'PHET_CORE/inherit' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Matrix3 = require( 'DOT/Matrix3' );
  var Shape = require( 'KITE/Shape' );
  var ArrowNode = require( 'SCENERY_PHET/ArrowNode' );
  var ArrowShape = require( 'SCENERY_PHET/ArrowShape' );

  /*
   * @param {number} tailX
   * @param {number} tailY
   * @param {number} tipX
   * @param {number} tipY
   * @param {Object} options
   * @constructor
   */
  function MutableArrowNode( tailX, tailY, tipX, tipY, options ) {

    // default options
    options = _.extend( {
      headHeight: 10,
      headWidth: 10,
      tailWidth: 5,
      doubleHead: false, // true puts heads on both ends of the arrow, false puts a head at the tip
      fill: 'black',
      stroke: 'black',
      lineWidth: 1,

      //Flag to set the head height to be (at most) half the length of the arrow.  See https://github.com/phetsims/scenery-phet/issues/30
      headHeightMaximumHalf: false
    }, options );

    // things you're likely to mess up, add more as needed
    assert && assert( options.headWidth > options.tailWidth );

    //The tail of the Arrow is a
    Node.call( this, options );

    //Intermediate parent for setting the rotation of the node
    this.parent = new Node();

    //Rectangle for body and stripes along the sides for stroke
    this.body = new Node( {
      children: [
        new Rectangle( 0, 0, options.tailWidth, 1, {fill: options.fill} ),
        new Rectangle( -options.lineWidth / 2, 0, options.lineWidth, 1, {fill: options.stroke} ),
        new Rectangle( options.tailWidth - options.lineWidth / 2, 0, options.lineWidth, 1, {fill: options.stroke} )
      ]} );

    //Head shape.  Don't close so the stroke can be used for the angled parts, but not the flat part (the "collar").
    var headShape = new Shape().moveTo( -options.headWidth / 2, 0 ).lineTo( 0, options.headHeight ).lineTo( options.headWidth / 2, 0 );
    this.head = new Path( headShape, {fill: options.fill, stroke: options.stroke, lineWidth: options.lineWidth} );

    this.tailEdgeStroke = new Rectangle( 0, -options.lineWidth / 2, options.tailWidth, options.lineWidth, {fill: 'black'} );

    this.parent.addChild( this.head );
    this.parent.addChild( this.body );
    this.parent.addChild( this.tailEdgeStroke );

    this.addChild( this.parent );
    this.options = options;

    //Fall back to ArrowNode when arrowhead is small
    //TODO: We could replace this with optimized shrunken arrowhead and arrow tip too
    this.arrowNode = new ArrowNode( 0, 0, 0, 0, options );
    this.arrowNode.visible = false;
    this.addChild( this.arrowNode );

    this.optimized = true;
    this.setTailAndTip( tailX, tailY, tipX, tipY );
  }

  return inherit( Node, MutableArrowNode, {
    getMatrix: function( x, y, angle, sx, sy, originX, originY ) {

      //Translate to the desired location
      var matrix = Matrix3.translation( x, y );

      //Rotation and translation can happen in any order
      matrix = matrix.multiplyMatrix( Matrix3.rotation2( angle ) );

      //Update scale if not 1
      if ( originX !== 1 || originY !== 1 ) {
        matrix = matrix.multiplyMatrix( Matrix3.scaling( sx, sy ) );
      }

      //Update origin if non-zero
      if ( originX !== 0 || originY !== 0 ) {

        //Think of it as a multiplying the Vector2 to the right, so this step happens first actually.  Use it to center the registration point
        matrix = matrix.multiplyMatrix( Matrix3.translation( -originX, -originY ) );
      }

      return matrix;
    },
    setTailAndTip: function( tailX, tailY, tipX, tipY ) {
      if ( this.tailX !== tailX || this.tailY !== tailY || this.tipX !== tipX || this.tipY !== tipY ) {
        var dx = tipX - tailX;
        var dy = tipY - tailY;

        var bodyDistance = Math.sqrt( dx * dx + dy * dy ) - this.options.headHeight;

        var willBeOptimized = bodyDistance > this.options.headHeight;

        if ( willBeOptimized ) {

          if ( !this.optimized ) {
            this.body.visible = true;
            this.head.visible = true;
            this.tailEdgeStroke.visible = true;
            this.arrowNode.visible = false;
          }

          var angle = Math.atan2( dy, dx );

          var bodyTipX = bodyDistance * Math.cos( angle ) + tailX;
          var bodyTipY = bodyDistance * Math.sin( angle ) + tailY;

          //Overlap a bit so it looks like a solid piece
          var overlap = 2E-1;

          this.body.setMatrix( this.getMatrix( tailX, tailY, angle - Math.PI / 2, 1, bodyDistance + overlap, this.options.tailWidth / 2, 0 ) );
          this.head.setMatrix( this.getMatrix( bodyTipX, bodyTipY, angle - Math.PI / 2, 1, 1, 0, 0 ) );
          this.tailEdgeStroke.setMatrix( this.getMatrix( tailX, tailY, angle - Math.PI / 2, 1, 1, this.options.tailWidth / 2, 0 ) );
        }
        else {
          if ( this.optimized ) {
            this.body.visible = false;
            this.head.visible = false;
            this.tailEdgeStroke.visible = false;
            this.arrowNode.visible = true;
          }

          //For short arrows, the head height should be half of the arrow length.  See https://github.com/phetsims/scenery-phet/issues/30
          var newOptions = this.options;
          if ( this.options.headHeightMaximumHalf ) {
            var arrowLength = Math.sqrt( (tipX - tailX) * (tipX - tailX) + (tipY - tailY) * (tipY - tailY) );
            var headHeight = Math.min( Math.abs( arrowLength ) / 2, this.options.headHeight );
            newOptions = _.extend( _.clone( this.options ), {headHeight: headHeight} );
          }
          this.arrowNode.setShape( new ArrowShape( tailX, tailY, tipX, tipY, newOptions ) );
        }

        this.optimized = willBeOptimized;
        this.tailX = tailX;
        this.tailY = tailY;
        this.tipX = tipX;
        this.tipY = tipY;
      }
    },
    setShape: function() {
      console.log( 'set shape called on MutableArrowNode.' );
    }
  } );
} );
<|endoftext|>


<commit_before>'use strict';

var $ = require('jquery');

function NavMainComponent (html, window) {
    this.$html = html;
    this.$window = $(window);
};

NavMainComponent.prototype.init = function () {
    var component = this;

    component.adjustNavItems();

    component.$navMain = this.$html.find('.nav-main');
    component.$contentMain = this.$html.find('.content-main');
    component.$navPrimary = this.$html.find('.nav-primary');
    component.$navSecondary = this.$html.find('.nav-secondary');
    component.$navTertiary = component.$navMain.find('.nav-tertiary');
    component.$navQuaternary = component.$navMain.find('.nav-quaternary');
    component.$primaryNavLinks = component.$navPrimary.find('.nav-link');
    component.$secondaryNavLinks = component.$navSecondary.find('.nav-link');
    component.$tertiaryNavLinks = component.$navTertiary.find('.nav-link');
    component.$quaternaryNavLinks = component.$navQuaternary.find('.nav-link');
    component.$moreIcon = component.$navMain.find('.more-icon');
    component.$closeLink = component.$navMain.find('[data-nav-action=close]');

    component.$window.resize(function () {
        component.adjustNavItems();
    });

    component.closeHandler = function () {
        component.closeNavs();
    };

    component.$primaryNavLinks.on('click', function (e) {
        var $self = $(this),
            href = $self.attr('href');

        // If href is a fragment, don't add it to the URL because it breaks the
        // back button
        if (href.indexOf('#') !== -1) {
            e.preventDefault();
        }

        component.switchPrimaryNav(href);
        component.switchSecondaryNav(href);
        component.$contentMain.on('click', component.closeHandler);
    });

    component.$secondaryNavLinks.on('click', function () {
        component.changeActiveSecondaryNavLink($(this).attr('href'));
    });

    component.$tertiaryNavLinks.on('click', function (e) {
        var $self = $(this),
            href = $self.attr('href');

        if (href.indexOf('#') !== -1) {
            e.preventDefault();
        }

        component.switchTertiartyNav(href);
        component.switchQuaternaryNav(href);
        component.$contentMain.on('click', component.closeHandler);
    });

    component.$quaternaryNavLinks.on('click', function () {
        component.changeActiveQuaternaryNavLink($(this).attr('href'));
    });

    component.$moreIcon.find('.nav-link').on('click', function () {
        component.$navSecondary.removeClass('is-open');
        component.$navTertiary.toggleClass('is-open');
        component.$navTertiary.find('.nav-list').addClass('is-active');
    });

    component.$closeLink.on('click', function (e) {
        e.preventDefault();
        component.closeNavs();
        component.closeSubNavs();
        component.$primaryNavLinks.removeClass('is-active');
    });
};

NavMainComponent.prototype.switchPrimaryNav = function (target) {
    var component = this;

    component.$primaryNavLinks.removeClass('is-active');
    component.$navQuaternary.removeClass('is-open');
    component.$navTertiary.removeClass('is-open');

    if (component.$html.find('[data-nav="' + target + '"]').length >= 1) {
        component.$navMain.addClass('is-open');
    } else {
        component.closeNavs();
    }

    component.$navPrimary.find('.is-active').removeClass('is-active');
    component.$navPrimary.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.switchSecondaryNav = function (target) {
    var component = this;

    component.closeSubNavs();
    if (target.indexOf('#') !== -1) {
        component.$navSecondary.toggleClass('is-open');
        component.$navSecondary.find('.nav-list.is-active').removeClass('is-active');
        component.$navSecondary.find('[data-nav="' + target + '"]').addClass('is-active');
    }
};

NavMainComponent.prototype.switchTertiartyNav = function (target) {
    var component = this;

    if (!component.$html.find('[data-nav="' + target + '"]').length) {
        component.closeNavs();
    }

    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.switchQuaternaryNav = function (target) {
    var component = this;

    // If category item has encapsulated options and if it is same-page link then open the proper menu
    if (target.indexOf('#') !== -1) {
        component.$navQuaternary.addClass('is-open');
    }

    component.$html.find('[data-nav="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.changeActiveSecondaryNavLink = function (target) {
    var component = this;

    component.$navSecondary.find('.nav-item.is-active').removeClass('is-active');
    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.changeActiveQuaternaryNavLink = function (target) {
    var component = this;

    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.closeNavs = function () {
    var component = this;

    if (component.$navSecondary.hasClass('is-open')) {
        component.$navSecondary.removeClass('is-open');
    }

    component.$contentMain.unbind('click', component.closeHandler);
};

NavMainComponent.prototype.closeSubNavs = function () {
    var component = this;

    component.$navSecondary.find('.nav-list').removeClass('is-active');

    if (component.$navQuaternary.hasClass('is-open')) {
        component.$navQuaternary.removeClass('is-open');
        component.$navQuaternary.find('.nav-list.is-active').removeClass('is-active');
    } else {
        component.$navTertiary.removeClass('is-open');
    }
};

// Detect window height, adjust the number of items in the primary nav and check when to add "More" option
NavMainComponent.prototype.adjustNavItems = function () {
    var component = this,
        availableHeight = component.$window.height(),
        navItemsHeight = (component.$html.find('.nav-primary .nav-items').outerHeight(true) + component.$html.find('.jadu-branding').outerHeight(true)),
        moreIconHeight = 72, // Pre calculated height of the "More" nav item
        navItemsCountTotal = component.$html.find('.nav-primary .nav-items li').length,
        numberOfHiddenNavItems = 0;

    if (navItemsHeight + moreIconHeight > availableHeight) {
        // If there is not enough space hide the last primary nav items
        component.hidePrimaryNavItems(navItemsHeight, moreIconHeight, availableHeight);
        // Get the number of hidden items to make only them visible in the tertiary menu
        numberOfHiddenNavItems = component.$html.find('.nav-primary .nav-items li:hidden').length;
        // Add "More" nav item and check its visibility if already exists
        component.addMoreNavItem(numberOfHiddenNavItems);
        // Hide the primary nav items duplicate in tertiary menu
        component.hideMoreCategoriesTopItems(navItemsCountTotal, numberOfHiddenNavItems);
    } else {
        // Unhide items if they were hidden and there is space in the primary nav
        component.$navPrimary.find('.nav-items li').show();
        component.$navPrimary.find('.nav-items [label="More"]').hide();
        component.$html.find('.nav-primary .nav-items li').show();
        component.$html.find('.nav-primary .nav-items [label="More"]').hide();
    }
};

NavMainComponent.prototype.hidePrimaryNavItems = function (navItemsHeight, moreIconHeight, availableHeight) {
    var component = this,
        navItems = component.$html.find('.nav-primary .nav-items'),
        lastPrimaryNavItem =  navItems.find('li:last-child'),
        nthChild = 2; // This number represents the item before the last in the nth-last-child;

    while (navItemsHeight + moreIconHeight > availableHeight) {
        // If last nav item is visible hide it
        if (lastPrimaryNavItem.is(':visible')) {
            lastPrimaryNavItem.hide();
        } else {
            // If last nav item is hidden hide the next one up
            navItems.find('li:nth-last-child('+ nthChild +')').hide();
            nthChild++;
        }

        // Recalculate nav items height based on items just hidden
        navItemsHeight = (navItems.outerHeight(true) + component.$html.find('.jadu-branding').outerHeight(true));
    }
};

NavMainComponent.prototype.addMoreNavItem = function (numberOfHiddenNavItems) {
    var component = this,
        navItems = component.$html.find('.nav-primary .nav-items'),
        navItemMore = navItems.find('[label="More"]');

    // Add the "More" nav item
    if ((numberOfHiddenNavItems > 0) && (!component.$html.find('.more-icon').length)){
        navItems.append('
More
');
    }

    // Check if "More" nav item is visible
    if (navItemMore.is(':visible') === false) {
        navItemMore.show();
    }
};

NavMainComponent.prototype.hideMoreCategoriesTopItems = function (navItemsCountTotal, numberOfHiddenNavItems) {
    var component = this,
        nthChild = 1, // Number used to iterate nth-child
        itemsToHideCount;

    // Reset hidden nav items
    component.$html.find('.nav-tertiary .nav-items li').show();

    // Calculate the number of visible nav items in primary nav
    itemsToHideCount = navItemsCountTotal - numberOfHiddenNavItems - 1; // 1 is for the "More" nav item

    // Hide top items in "More Categories" equal to the number of visible items of primary nav
    while (itemsToHideCount >= 0) {
        component.$html.find('.nav-tertiary .nav-items li:nth-child('+ nthChild +')').hide();
        nthChild++;
        itemsToHideCount--;
    }
};

module.exports = NavMainComponent;
<commit_msg>Close Nav Tertiary when there are no items in More<commit_after>'use strict';

var $ = require('jquery');

function NavMainComponent (html, window) {
    this.$html = html;
    this.$window = $(window);
};

NavMainComponent.prototype.init = function () {
    var component = this;

    component.adjustNavItems();

    component.$navMain = this.$html.find('.nav-main');
    component.$contentMain = this.$html.find('.content-main');
    component.$navPrimary = this.$html.find('.nav-primary');
    component.$navSecondary = this.$html.find('.nav-secondary');
    component.$navTertiary = component.$navMain.find('.nav-tertiary');
    component.$navQuaternary = component.$navMain.find('.nav-quaternary');
    component.$primaryNavLinks = component.$navPrimary.find('.nav-link');
    component.$secondaryNavLinks = component.$navSecondary.find('.nav-link');
    component.$tertiaryNavLinks = component.$navTertiary.find('.nav-link');
    component.$quaternaryNavLinks = component.$navQuaternary.find('.nav-link');
    component.$moreIcon = component.$navMain.find('.more-icon');
    component.$closeLink = component.$navMain.find('[data-nav-action=close]');

    component.$window.resize(function () {
        component.adjustNavItems();
    });

    component.closeHandler = function () {
        component.closeNavs();
    };

    component.$primaryNavLinks.on('click', function (e) {
        var $self = $(this),
            href = $self.attr('href');

        // If href is a fragment, don't add it to the URL because it breaks the
        // back button
        if (href.indexOf('#') !== -1) {
            e.preventDefault();
        }

        component.switchPrimaryNav(href);
        component.switchSecondaryNav(href);
        component.$contentMain.on('click', component.closeHandler);
    });

    component.$secondaryNavLinks.on('click', function () {
        component.changeActiveSecondaryNavLink($(this).attr('href'));
    });

    component.$tertiaryNavLinks.on('click', function (e) {
        var $self = $(this),
            href = $self.attr('href');

        if (href.indexOf('#') !== -1) {
            e.preventDefault();
        }

        component.switchTertiartyNav(href);
        component.switchQuaternaryNav(href);
        component.$contentMain.on('click', component.closeHandler);
    });

    component.$quaternaryNavLinks.on('click', function () {
        component.changeActiveQuaternaryNavLink($(this).attr('href'));
    });

    component.$moreIcon.find('.nav-link').on('click', function () {
        component.$navSecondary.removeClass('is-open');
        component.$navTertiary.toggleClass('is-open');
        component.$navTertiary.find('.nav-list').addClass('is-active');
    });

    component.$closeLink.on('click', function (e) {
        e.preventDefault();
        component.closeNavs();
        component.closeSubNavs();
        component.$primaryNavLinks.removeClass('is-active');
    });
};

NavMainComponent.prototype.switchPrimaryNav = function (target) {
    var component = this;

    component.$primaryNavLinks.removeClass('is-active');
    component.$navQuaternary.removeClass('is-open');
    component.$navTertiary.removeClass('is-open');

    if (component.$html.find('[data-nav="' + target + '"]').length >= 1) {
        component.$navMain.addClass('is-open');
    } else {
        component.closeNavs();
    }

    component.$navPrimary.find('.is-active').removeClass('is-active');
    component.$navPrimary.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.switchSecondaryNav = function (target) {
    var component = this;

    component.closeSubNavs();
    if (target.indexOf('#') !== -1) {
        component.$navSecondary.toggleClass('is-open');
        component.$navSecondary.find('.nav-list.is-active').removeClass('is-active');
        component.$navSecondary.find('[data-nav="' + target + '"]').addClass('is-active');
    }
};

NavMainComponent.prototype.switchTertiartyNav = function (target) {
    var component = this;

    if (!component.$html.find('[data-nav="' + target + '"]').length) {
        component.closeNavs();
    }

    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.switchQuaternaryNav = function (target) {
    var component = this;

    // If category item has encapsulated options and if it is same-page link then open the proper menu
    if (target.indexOf('#') !== -1) {
        component.$navQuaternary.addClass('is-open');
    }

    component.$html.find('[data-nav="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.changeActiveSecondaryNavLink = function (target) {
    var component = this;

    component.$navSecondary.find('.nav-item.is-active').removeClass('is-active');
    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.changeActiveQuaternaryNavLink = function (target) {
    var component = this;

    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.closeNavs = function () {
    var component = this;

    if (component.$navSecondary.hasClass('is-open')) {
        component.$navSecondary.removeClass('is-open');
    }

    component.$contentMain.unbind('click', component.closeHandler);
};

NavMainComponent.prototype.closeSubNavs = function () {
    var component = this;

    component.$navSecondary.find('.nav-list').removeClass('is-active');

    if (component.$navQuaternary.hasClass('is-open')) {
        component.$navQuaternary.removeClass('is-open');
        component.$navQuaternary.find('.nav-list.is-active').removeClass('is-active');
    } else {
        component.$navTertiary.removeClass('is-open');
    }
};

// Detect window height, adjust the number of items in the primary nav and check when to add "More" option
NavMainComponent.prototype.adjustNavItems = function () {
    var component = this,
        availableHeight = component.$window.height(),
        navItemsHeight = (component.$html.find('.nav-primary .nav-items').outerHeight(true) + component.$html.find('.jadu-branding').outerHeight(true)),
        moreIconHeight = 72, // Pre calculated height of the "More" nav item
        navItemsCountTotal = component.$html.find('.nav-primary .nav-items li').length,
        numberOfHiddenNavItems = 0;

    if (navItemsHeight + moreIconHeight > availableHeight) {
        // If there is not enough space hide the last primary nav items
        component.hidePrimaryNavItems(navItemsHeight, moreIconHeight, availableHeight);
        // Get the number of hidden items to make only them visible in the tertiary menu
        numberOfHiddenNavItems = component.$html.find('.nav-primary .nav-items li:hidden').length;
        // Add "More" nav item and check its visibility if already exists
        component.addMoreNavItem(numberOfHiddenNavItems);
        // Hide the primary nav items duplicate in tertiary menu
        component.hideMoreCategoriesTopItems(navItemsCountTotal, numberOfHiddenNavItems);
    } else {
        // Unhide items if they were hidden and there is space in the primary nav
        component.$html.find('.nav-primary .nav-items li').show();
        component.$html.find('.nav-primary .nav-items [label="More"]').hide();
        component.$html.find('.nav-tertiary').removeClass('is-open');
    }
};

NavMainComponent.prototype.hidePrimaryNavItems = function (navItemsHeight, moreIconHeight, availableHeight) {
    var component = this,
        navItems = component.$html.find('.nav-primary .nav-items'),
        lastPrimaryNavItem =  navItems.find('li:last-child'),
        nthChild = 2; // This number represents the item before the last in the nth-last-child;

    while (navItemsHeight + moreIconHeight > availableHeight) {
        // If last nav item is visible hide it
        if (lastPrimaryNavItem.is(':visible')) {
            lastPrimaryNavItem.hide();
        } else {
            // If last nav item is hidden hide the next one up
            navItems.find('li:nth-last-child('+ nthChild +')').hide();
            nthChild++;
        }

        // Recalculate nav items height based on items just hidden
        navItemsHeight = (navItems.outerHeight(true) + component.$html.find('.jadu-branding').outerHeight(true));
    }
};

NavMainComponent.prototype.addMoreNavItem = function (numberOfHiddenNavItems) {
    var component = this,
        navItems = component.$html.find('.nav-primary .nav-items'),
        navItemMore = navItems.find('[label="More"]');

    // Add the "More" nav item
    if ((numberOfHiddenNavItems > 0) && (!component.$html.find('.more-icon').length)){
        navItems.append('
More
');
    }

    // Check if "More" nav item is visible
    if (navItemMore.is(':visible') === false) {
        navItemMore.show();
    }
};

NavMainComponent.prototype.hideMoreCategoriesTopItems = function (navItemsCountTotal, numberOfHiddenNavItems) {
    var component = this,
        nthChild = 1, // Number used to iterate nth-child
        itemsToHideCount;

    // Reset hidden nav items
    component.$html.find('.nav-tertiary .nav-items li').show();

    // Calculate the number of visible nav items in primary nav
    itemsToHideCount = navItemsCountTotal - numberOfHiddenNavItems - 1; // 1 is for the "More" nav item

    // Hide top items in "More Categories" equal to the number of visible items of primary nav
    while (itemsToHideCount >= 0) {
        component.$html.find('.nav-tertiary .nav-items li:nth-child('+ nthChild +')').hide();
        nthChild++;
        itemsToHideCount--;
    }
};

module.exports = NavMainComponent;
<|endoftext|>


<commit_before>/**
 * @class Oskari.userinterface.bundle.ui.UserInterfaceBundle
 *
 *
 */

Oskari.clazz.define("Oskari.mapframework.bundle.userguide.UserGuideBundle", function() {

}, {
	/**
	 * @method create creates an Oskari DIV Manager instance
	 * @return {Oskari.userinterface.bundle.ui.UserInterfaceBundleInstance}
	 */
	"create" : function() {

		return Oskari.clazz.create("Oskari.mapframework.bundle.userguide.UserGuideBundleInstance");
	},
	/**
	 * @method update called by the bundle manager to inform on changes in bundlage
	 */
	"update" : function(manager, bundle, bi, info) {

	}
}, {
	/**
	 * @static
	 * @property protocol protocols implemented by this bundle
	 */
	"protocol" : ["Oskari.bundle.Bundle"],
	"source" : {
		/**
		 * @static
		 * @property source.scripts
		 *
		 */
		"scripts" : [{
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/instance.js"
		}, {
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/request/ShowUserGuideRequest.js"
		}, {
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/request/ShowUserGuideRequestHandler.js"
		},{
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/service/UserGuideService.js"
		},{
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/Tile.js"
		},{
            "type" : "text/javascript",
            "src" : "../../../../bundles/framework/bundle/userguide/Flyout.js"
        },{
			"type" : "text/css",
			"src" : "../../../../resources/framework/bundle/userguide/css/style.css"
		}],
		"locales" : [
		{
			"lang" : "fi",
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/locale/fi.js"
		}, {
			"lang" : "en",
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/locale/sv.js"
		},{
			"lang" : "sv",
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/locale/en.js"
		}]
	},
	"bundle" : {
		/**
		 * @static
		 * @property bundle.manifest
		 */
		"manifest" : {
			"Bundle-Identifier" : "userguide",
			"Bundle-Name" : "userguide",
			"Bundle-Tag" : {
				"mapframework" : true
			},

			"Bundle-Author" : [{
				"Name" : "jjk",
				"Organisation" : "nls.fi",
				"Temporal" : {
					"Start" : "2009",
					"End" : "2011"
				},
				"Copyleft" : {
					"License" : {
						"License-Name" : "EUPL",
						"License-Online-Resource" : "http://www.paikkatietoikkuna.fi/license"
					}
				}
			}],
			"Bundle-Name-Locale" : {
				"fi" : {
					"Name" : "userguide",
					"Title" : "userguide"
				},
				"en" : {}
			},
			"Bundle-Version" : "1.0.0",
			"Import-Namespace" : ["Oskari"],
			"Import-Bundle" : {}
		}
	}
});

Oskari.bundle_manager.installBundleClass("userguide", "Oskari.mapframework.bundle.userguide.UserGuideBundle");
<commit_msg>[PORTTISK-1275], metadataflyout and userguide language presentations are now fixed<commit_after>/**
 * @class Oskari.userinterface.bundle.ui.UserInterfaceBundle
 *
 *
 */

Oskari.clazz.define("Oskari.mapframework.bundle.userguide.UserGuideBundle", function() {

}, {
	/**
	 * @method create creates an Oskari DIV Manager instance
	 * @return {Oskari.userinterface.bundle.ui.UserInterfaceBundleInstance}
	 */
	"create" : function() {

		return Oskari.clazz.create("Oskari.mapframework.bundle.userguide.UserGuideBundleInstance");
	},
	/**
	 * @method update called by the bundle manager to inform on changes in bundlage
	 */
	"update" : function(manager, bundle, bi, info) {

	}
}, {
	/**
	 * @static
	 * @property protocol protocols implemented by this bundle
	 */
	"protocol" : ["Oskari.bundle.Bundle"],
	"source" : {
		/**
		 * @static
		 * @property source.scripts
		 *
		 */
		"scripts" : [{
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/instance.js"
		}, {
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/request/ShowUserGuideRequest.js"
		}, {
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/request/ShowUserGuideRequestHandler.js"
		},{
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/service/UserGuideService.js"
		},{
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/Tile.js"
		},{
            "type" : "text/javascript",
            "src" : "../../../../bundles/framework/bundle/userguide/Flyout.js"
        },{
			"type" : "text/css",
			"src" : "../../../../resources/framework/bundle/userguide/css/style.css"
		}],
		"locales" : [
		{
			"lang" : "fi",
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/locale/fi.js"
		}, {
			"lang" : "sv",
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/locale/sv.js"
		},{
			"lang" : "en",
			"type" : "text/javascript",
			"src" : "../../../../bundles/framework/bundle/userguide/locale/en.js"
		}]
	},
	"bundle" : {
		/**
		 * @static
		 * @property bundle.manifest
		 */
		"manifest" : {
			"Bundle-Identifier" : "userguide",
			"Bundle-Name" : "userguide",
			"Bundle-Tag" : {
				"mapframework" : true
			},

			"Bundle-Author" : [{
				"Name" : "jjk",
				"Organisation" : "nls.fi",
				"Temporal" : {
					"Start" : "2009",
					"End" : "2011"
				},
				"Copyleft" : {
					"License" : {
						"License-Name" : "EUPL",
						"License-Online-Resource" : "http://www.paikkatietoikkuna.fi/license"
					}
				}
			}],
			"Bundle-Name-Locale" : {
				"fi" : {
					"Name" : "userguide",
					"Title" : "userguide"
				},
				"en" : {}
			},
			"Bundle-Version" : "1.0.0",
			"Import-Namespace" : ["Oskari"],
			"Import-Bundle" : {}
		}
	}
});

Oskari.bundle_manager.installBundleClass("userguide", "Oskari.mapframework.bundle.userguide.UserGuideBundle");
<|endoftext|>


<commit_before>define([
  'firebase',
  'jquery',
  'leap',
  'leapjsplugins',
  'oauth',
  'lodash',
  'kibo',

  'js/RiftSandbox',
  'js/File',
  'js/Sketch',

  'text!js/Files/Behaviors.js',
  'text!js/Files/Boid.js',
  'text!js/Files/World.js'
],
function (
  Firebase,
  $,
  Leap,
  leapjsplugins,
  OAuth,
  _,
  Kibo,

  RiftSandbox,
  File,
  Sketch,

  Behaviors,
  Boid,
  World
) {
  'use strict';

  var SketchController = function() {
    this.hands = [];
    var setupVideoPassthrough = function () {
      this.domMonitor = document.getElementById('monitor');
      navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
      navigator.getUserMedia(
        {video: {
          mandatory: {
            minWidth: 1280,
            minHeight: 720
          }
        }},
        function (stream) {
          this.domMonitor.src = window.URL.createObjectURL(stream);
        }.bind(this),
        function () {
          // video pass-through is optional.
        }
      );
    }.bind(this);
    setupVideoPassthrough();

    var loadSketch = function (ref) {
      this.sketch = {};
      this.firebaseRef = ref;
      ref.on('value', function (data) {
        this.readCode(data.val().contents);
      }.bind(this));
    }.bind(this);

    var getShortcut = function (key) {
      key = key || '';
      return ['alt shift ' + key, 'ctrl shift ' + key];
    };

    this.is_editor_visible = true;
    this.bindKeyboardShortcuts = function (domTextArea, file) {
      var kibo = new Kibo(domTextArea);
      kibo.down(getShortcut('z'), function () {
        this.riftSandbox.controls.zeroSensor();
        return false;
      }.bind(this));
      kibo.down(getShortcut('e'), function () {
        this.is_editor_visible = !this.is_editor_visible;
        this.riftSandbox.toggleTextArea(this.is_editor_visible);
        return false;
      }.bind(this));
      kibo.down(getShortcut('u'), function () {
        spinNumberAndKeepSelection(-1, 10);
        return false;
      });
      kibo.down(getShortcut('i'), function () {
        spinNumberAndKeepSelection(1, 10);
        return false;
      });
      kibo.down(getShortcut('j'), function () {
        spinNumberAndKeepSelection(-1, 1);
        return false;
      });
      kibo.down(getShortcut('k'), function () {
        spinNumberAndKeepSelection(1, 1);
        return false;
      });
      kibo.down(getShortcut('n'), function () {
        spinNumberAndKeepSelection(-1, 0.1);
        return false;
      });
      kibo.down(getShortcut('m'), function () {
        spinNumberAndKeepSelection(1, 0.1);
        return false;
      });

      var MOVEMENT_RATE = 0.01;
      var ROTATION_RATE = 0.01;

      kibo.down('w', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.setVelocity(MOVEMENT_RATE);
        }
      }.bind(this));
      kibo.up('w', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.setVelocity(0);
        }
      }.bind(this));

      kibo.down('s', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.setVelocity(-MOVEMENT_RATE);
        }
      }.bind(this));
      kibo.up('s', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.setVelocity(0);
        }
      }.bind(this));

      kibo.down('a', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.BaseRotationEuler.y += ROTATION_RATE;
        }
      }.bind(this));
      kibo.down('d', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.BaseRotationEuler.y -= ROTATION_RATE;
        }
      }.bind(this));

      kibo.down('q', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.BaseRotationEuler.y += Math.PI / 4;
        }
      }.bind(this));
      kibo.down('e', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.BaseRotationEuler.y -= Math.PI / 4;
        }
      }.bind(this));

      kibo.down(getShortcut(), function () {
        if (this.shiftPressed) { return false; }
        this.shiftPressed = true;
        return false;
      }.bind(this));
      kibo.up('shift', function () {
        this.shiftPressed = false;
        return false;
      }.bind(this));

      kibo.down(getShortcut(), function () {
        if (this.modifierPressed) { return false; }
        var start = this.currentDomTextArea.selectionStart;
        file.recordOriginalNumberAt(start);
        this.handStart = this.handCurrent;
        this.modifierPressed = true;
        return false;
      }.bind(this));
      kibo.up(getShortcut(), function () {
        this.modifierPressed = false;
        return false;
      }.bind(this));
    }.bind(this);

    var setupDomTextArea = function (file) {
      var domTextArea = $('<textarea>').
        appendTo('body').
        on('keyup', function (e) {
          var contents = e.target.value;
          if (contents === file.contents) { return; }
          file.contents = contents;
          this.executeCode();
        }.bind(this)).
        on('keydown', function (e) {
          e.stopPropagation();
        }).
        val(file.contents).
        get(0);
      this.bindKeyboardShortcuts(domTextArea, file);
      return domTextArea;
    }.bind(this);

    var setupSketch = function () {
      this.sketch = new Sketch('', [
        new File('Behaviors', Behaviors),
        new File('Boid', Boid),
        new File('World', World)
      ]);
      this.domTextAreas = this.sketch.files.map(setupDomTextArea);
      this.currentDomTextArea = this.domTextAreas[0];
      this.currentDomTextArea.focus();
      this.currentFile = this.sketch.files[0];

      var sketches_base = 'https://riftsketch2.firebaseio.com/sketches/';
      var ref;
      if (!window.location.hash) {
        ref = new Firebase(sketches_base);
        ref = ref.push(
          this.sketch,
          function () {
            window.location.hash = '#!' + ref.key();
            loadSketch(ref);
          }
        );
      }
      else {
        ref = new Firebase(sketches_base + window.location.hash.substring(2));
        loadSketch(ref);
      }
    }.bind(this);
    setupSketch();

    var mousePos = {x: 0, y: 0};
    window.addEventListener(
      'mousemove',
      function (e) {
        mousePos.x = e.clientX;
        mousePos.y = e.clientY;
      },
      false
    );

    this.sketchLoop = function () {};

    this.mainLoop = function () {
      window.requestAnimationFrame( this.mainLoop.bind(this) );

      // Apply movement
      // if (this.deviceManager.sensorDevice && this.riftSandbox.vrMode) {
      //   this.riftSandbox.setHmdPositionRotation(
      //     this.deviceManager.sensorDevice.getState());
      // }
      // else {
      //   this.riftSandbox.setRotation({
      //     y: mousePos.x / window.innerWidth * Math.PI * 2
      //   });
      // }
      // this.riftSandbox.setBaseRotation();
      // this.riftSandbox.updateCameraPositionRotation();

      try {
        this.sketchLoop();
      }
      catch (err) {
        this.riftSandbox.textArea.setInfo(err.toString());
      }

      this.riftSandbox.render();
    };

    var spinNumberAndKeepSelection = function (direction, amount) {
      var start = this.domTextArea.selectionStart;
      File.spinNumberAt(this.sketch, start, direction, amount);
      this.writeCode(this.sketch.contents);
      this.domTextArea.selectionStart = this.domTextArea.selectionEnd = start;
    }.bind(this);

    var offsetNumberAndKeepSelection = function (offset) {
      var start = this.domTextArea.selectionStart;
      File.offsetOriginalNumber(this.sketch, offset);
      this.writeCode(this.sketch.contents);
      this.domTextArea.selectionStart = this.domTextArea.selectionEnd = start;
    }.bind(this);

    this.handStart = this.handCurrent = null;
    this.modifierPressed = this.shiftPressed = false;
    Leap.loop({}, function (frame) {
      if (frame.hands.length) {
        this.handCurrent = frame;
        if (this.modifierPressed && this.handStart) {
          var hand = frame.hands[0];
          var handTranslation = hand.translation(this.handStart);
          var factor = this.shiftPressed ? 10 : 100;
          var offset = Math.round(handTranslation[1] / factor * 1000) / 1000;
          offsetNumberAndKeepSelection(offset);
        }
      }
      this.previousFrame = frame;
    }.bind(this));

    OAuth.initialize('bnVXi9ZBNKekF-alA1aF7PQEpsU');
    var apiCache = {};
    var api = _.throttle(function (provider, url, data, callback) {
      var cacheKey = url + JSON.stringify(data);
      var cacheEntry = apiCache[cacheKey];
      if (cacheEntry && (Date.now() - cacheEntry.lastCall) < 1000 * 60 * 5) {
        callback(cacheEntry.data);
        return;
      }
      OAuth.popup(
        provider,
        {cache: true}
      ).done(function(result) {
        result.get(
          url,
          {data: data, cache: true}
        ).done(function (data) {
          apiCache[cacheKey] = {
            lastCall: Date.now(),
            data: data
          };
          callback(data);
        });
      });
    }, 1000);

    var toggleVrMode = function () {
      if (
        !(document.mozFullScreenElement || document.webkitFullScreenElement) &&
        this.riftSandbox.vrMode
      ) {
        this.isInfullscreen = false;
        this.riftSandbox.toggleVrMode();
      }
      else {
        this.isInfullscreen = true;
      }
    }.bind(this);
    document.addEventListener('mozfullscreenchange', toggleVrMode, false);
    document.addEventListener('webkitfullscreenchange', toggleVrMode, false);

    $(function () {
      this.domTextArea = document.querySelector('textarea');
      this.bindKeyboardShortcuts();
      var $domTextArea = $(this.domTextArea);
      $domTextArea.on('blur', function () {
        $domTextArea.focus();
      }.bind(this));
      $domTextArea.on('keydown', function (e) {
        // prevent VR polyfill from hijacking wasd.
        e.stopPropagation();
      });
      $domTextArea.focus();
      this.domTextArea.selectionStart = this.domTextArea.selectionEnd = 0;
      this.riftSandbox = new RiftSandbox(
        window.innerWidth, window.innerHeight,
        this.domTextAreas,
        this.domMonitor,
        function (err) {
          this.seemsUnsupported = !!err;
        }.bind(this)
      );

      this.readCode = function (code) {
        this.sketch.contents = code;
        this.domTextArea.value = code;

        this.riftSandbox.clearScene();
        var _sketchLoop;
        this.riftSandbox.setInfo('');
        try {
          /* jshint -W054 */
          var _sketchFunc = new Function(
            'scene', 'camera', 'api',
            '"use strict";\n' + code
          );
          /* jshint +W054 */
          _sketchLoop = _sketchFunc(
            this.riftSandbox.scene, this.riftSandbox.cameraPivot, api);
        }
        catch (err) {
          this.riftSandbox.textArea.setInfo(err.toString());
        }
        if (_sketchLoop) {
          this.sketchLoop = _sketchLoop;
        }
      }.bind(this);

      this.writeCode = function (code) {
        this.firebaseRef.set({contents: code});
      };

      $('#sketchContents').on('keyup', function (e) {
        var code = e.target.value;
        if (code === this.sketch.contents) { return; }
        this.writeCode(code);
      }.bind(this));

      window.addEventListener(
        'resize',
        this.riftSandbox.resize.bind(this.riftSandbox),
        false
      );

      var kibo = new Kibo(this.domTextArea);
      kibo.down(getShortcut('v'), function () {
        this.riftSandbox.toggleVrMode();
        this.riftSandbox.vrManager.toggleVRMode();
        return false;
      }.bind(this));

      this.riftSandbox.resize();

      this.mainLoop();
    }.bind(this));
  };

  return SketchController;
});
<commit_msg>Save code to firebase<commit_after>define([
  'firebase',
  'jquery',
  'leap',
  'leapjsplugins',
  'oauth',
  'lodash',
  'kibo',

  'js/RiftSandbox',
  'js/File',
  'js/Sketch',

  'text!js/Files/Behaviors.js',
  'text!js/Files/Boid.js',
  'text!js/Files/World.js'
],
function (
  Firebase,
  $,
  Leap,
  leapjsplugins,
  OAuth,
  _,
  Kibo,

  RiftSandbox,
  File,
  Sketch,

  Behaviors,
  Boid,
  World
) {
  'use strict';

  var SketchController = function() {
    this.hands = [];
    var setupVideoPassthrough = function () {
      this.domMonitor = document.getElementById('monitor');
      navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
      navigator.getUserMedia(
        {video: {
          mandatory: {
            minWidth: 1280,
            minHeight: 720
          }
        }},
        function (stream) {
          this.domMonitor.src = window.URL.createObjectURL(stream);
        }.bind(this),
        function () {
          // video pass-through is optional.
        }
      );
    }.bind(this);
    setupVideoPassthrough();

    var loadSketch = function (ref) {
      this.sketch = {};
      this.firebaseRef = ref;
      ref.on('value', function (data) {
        this.readCode(data.val().contents);
      }.bind(this));
    }.bind(this);

    var getShortcut = function (key) {
      key = key || '';
      return ['alt shift ' + key, 'ctrl shift ' + key];
    };

    this.is_editor_visible = true;
    this.bindKeyboardShortcuts = function (domTextArea, file) {
      var kibo = new Kibo(domTextArea);
      kibo.down(getShortcut('z'), function () {
        this.riftSandbox.controls.zeroSensor();
        return false;
      }.bind(this));
      kibo.down(getShortcut('e'), function () {
        this.is_editor_visible = !this.is_editor_visible;
        this.riftSandbox.toggleTextArea(this.is_editor_visible);
        return false;
      }.bind(this));
      kibo.down(getShortcut('u'), function () {
        spinNumberAndKeepSelection(-1, 10);
        return false;
      });
      kibo.down(getShortcut('i'), function () {
        spinNumberAndKeepSelection(1, 10);
        return false;
      });
      kibo.down(getShortcut('j'), function () {
        spinNumberAndKeepSelection(-1, 1);
        return false;
      });
      kibo.down(getShortcut('k'), function () {
        spinNumberAndKeepSelection(1, 1);
        return false;
      });
      kibo.down(getShortcut('n'), function () {
        spinNumberAndKeepSelection(-1, 0.1);
        return false;
      });
      kibo.down(getShortcut('m'), function () {
        spinNumberAndKeepSelection(1, 0.1);
        return false;
      });

      var MOVEMENT_RATE = 0.01;
      var ROTATION_RATE = 0.01;

      kibo.down('w', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.setVelocity(MOVEMENT_RATE);
        }
      }.bind(this));
      kibo.up('w', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.setVelocity(0);
        }
      }.bind(this));

      kibo.down('s', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.setVelocity(-MOVEMENT_RATE);
        }
      }.bind(this));
      kibo.up('s', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.setVelocity(0);
        }
      }.bind(this));

      kibo.down('a', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.BaseRotationEuler.y += ROTATION_RATE;
        }
      }.bind(this));
      kibo.down('d', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.BaseRotationEuler.y -= ROTATION_RATE;
        }
      }.bind(this));

      kibo.down('q', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.BaseRotationEuler.y += Math.PI / 4;
        }
      }.bind(this));
      kibo.down('e', function () {
        if (!this.is_editor_visible) {
          this.riftSandbox.BaseRotationEuler.y -= Math.PI / 4;
        }
      }.bind(this));

      kibo.down(getShortcut(), function () {
        if (this.shiftPressed) { return false; }
        this.shiftPressed = true;
        return false;
      }.bind(this));
      kibo.up('shift', function () {
        this.shiftPressed = false;
        return false;
      }.bind(this));

      kibo.down(getShortcut(), function () {
        if (this.modifierPressed) { return false; }
        var start = this.currentDomTextArea.selectionStart;
        file.recordOriginalNumberAt(start);
        this.handStart = this.handCurrent;
        this.modifierPressed = true;
        return false;
      }.bind(this));
      kibo.up(getShortcut(), function () {
        this.modifierPressed = false;
        return false;
      }.bind(this));
    }.bind(this);

    var setupDomTextArea = function (file) {
      var domTextArea = $('<textarea>').
        appendTo('body').
        on('keyup', function (e) {
          var contents = e.target.value;
          if (contents === file.contents) { return; }
          file.contents = contents;
          this.firebaseRef.set(this.sketch);
        }.bind(this)).
        on('keydown', function (e) {
          e.stopPropagation();
        }).
        val(file.contents).
        get(0);
      this.bindKeyboardShortcuts(domTextArea, file);
      return domTextArea;
    }.bind(this);

    var setupSketch = function () {
      this.sketch = new Sketch('', [
        new File('Behaviors', Behaviors),
        new File('Boid', Boid),
        new File('World', World)
      ]);
      this.domTextAreas = this.sketch.files.map(setupDomTextArea);
      this.currentDomTextArea = this.domTextAreas[0];
      this.currentDomTextArea.focus();
      this.currentFile = this.sketch.files[0];

      var sketches_base = 'https://riftsketch2.firebaseio.com/sketches/';
      var ref;
      if (!window.location.hash) {
        ref = new Firebase(sketches_base);
        ref = ref.push(
          this.sketch,
          function () {
            window.location.hash = '#!' + ref.key();
            loadSketch(ref);
          }
        );
      }
      else {
        ref = new Firebase(sketches_base + window.location.hash.substring(2));
        loadSketch(ref);
      }
    }.bind(this);
    setupSketch();

    var mousePos = {x: 0, y: 0};
    window.addEventListener(
      'mousemove',
      function (e) {
        mousePos.x = e.clientX;
        mousePos.y = e.clientY;
      },
      false
    );

    this.sketchLoop = function () {};

    this.mainLoop = function () {
      window.requestAnimationFrame( this.mainLoop.bind(this) );

      // Apply movement
      // if (this.deviceManager.sensorDevice && this.riftSandbox.vrMode) {
      //   this.riftSandbox.setHmdPositionRotation(
      //     this.deviceManager.sensorDevice.getState());
      // }
      // else {
      //   this.riftSandbox.setRotation({
      //     y: mousePos.x / window.innerWidth * Math.PI * 2
      //   });
      // }
      // this.riftSandbox.setBaseRotation();
      // this.riftSandbox.updateCameraPositionRotation();

      try {
        this.sketchLoop();
      }
      catch (err) {
        this.riftSandbox.textArea.setInfo(err.toString());
      }

      this.riftSandbox.render();
    };

    var spinNumberAndKeepSelection = function (direction, amount) {
      var start = this.domTextArea.selectionStart;
      File.spinNumberAt(this.sketch, start, direction, amount);
      this.writeCode(this.sketch.contents);
      this.domTextArea.selectionStart = this.domTextArea.selectionEnd = start;
    }.bind(this);

    var offsetNumberAndKeepSelection = function (offset) {
      var start = this.domTextArea.selectionStart;
      File.offsetOriginalNumber(this.sketch, offset);
      this.writeCode(this.sketch.contents);
      this.domTextArea.selectionStart = this.domTextArea.selectionEnd = start;
    }.bind(this);

    this.handStart = this.handCurrent = null;
    this.modifierPressed = this.shiftPressed = false;
    Leap.loop({}, function (frame) {
      if (frame.hands.length) {
        this.handCurrent = frame;
        if (this.modifierPressed && this.handStart) {
          var hand = frame.hands[0];
          var handTranslation = hand.translation(this.handStart);
          var factor = this.shiftPressed ? 10 : 100;
          var offset = Math.round(handTranslation[1] / factor * 1000) / 1000;
          offsetNumberAndKeepSelection(offset);
        }
      }
      this.previousFrame = frame;
    }.bind(this));

    OAuth.initialize('bnVXi9ZBNKekF-alA1aF7PQEpsU');
    var apiCache = {};
    var api = _.throttle(function (provider, url, data, callback) {
      var cacheKey = url + JSON.stringify(data);
      var cacheEntry = apiCache[cacheKey];
      if (cacheEntry && (Date.now() - cacheEntry.lastCall) < 1000 * 60 * 5) {
        callback(cacheEntry.data);
        return;
      }
      OAuth.popup(
        provider,
        {cache: true}
      ).done(function(result) {
        result.get(
          url,
          {data: data, cache: true}
        ).done(function (data) {
          apiCache[cacheKey] = {
            lastCall: Date.now(),
            data: data
          };
          callback(data);
        });
      });
    }, 1000);

    var toggleVrMode = function () {
      if (
        !(document.mozFullScreenElement || document.webkitFullScreenElement) &&
        this.riftSandbox.vrMode
      ) {
        this.isInfullscreen = false;
        this.riftSandbox.toggleVrMode();
      }
      else {
        this.isInfullscreen = true;
      }
    }.bind(this);
    document.addEventListener('mozfullscreenchange', toggleVrMode, false);
    document.addEventListener('webkitfullscreenchange', toggleVrMode, false);

    $(function () {
      this.domTextArea = document.querySelector('textarea');
      this.bindKeyboardShortcuts();
      var $domTextArea = $(this.domTextArea);
      $domTextArea.on('blur', function () {
        $domTextArea.focus();
      }.bind(this));
      $domTextArea.on('keydown', function (e) {
        // prevent VR polyfill from hijacking wasd.
        e.stopPropagation();
      });
      $domTextArea.focus();
      this.domTextArea.selectionStart = this.domTextArea.selectionEnd = 0;
      this.riftSandbox = new RiftSandbox(
        window.innerWidth, window.innerHeight,
        this.domTextAreas,
        this.domMonitor,
        function (err) {
          this.seemsUnsupported = !!err;
        }.bind(this)
      );

      this.readCode = function (code) {
        this.sketch.contents = code;
        this.domTextArea.value = code;

        this.riftSandbox.clearScene();
        var _sketchLoop;
        this.riftSandbox.setInfo('');
        try {
          /* jshint -W054 */
          var _sketchFunc = new Function(
            'scene', 'camera', 'api',
            '"use strict";\n' + code
          );
          /* jshint +W054 */
          _sketchLoop = _sketchFunc(
            this.riftSandbox.scene, this.riftSandbox.cameraPivot, api);
        }
        catch (err) {
          this.riftSandbox.textArea.setInfo(err.toString());
        }
        if (_sketchLoop) {
          this.sketchLoop = _sketchLoop;
        }
      }.bind(this);

      $('#sketchContents').on('keyup', function (e) {
        var code = e.target.value;
        if (code === this.sketch.contents) { return; }
        this.writeCode(code);
      }.bind(this));

      window.addEventListener(
        'resize',
        this.riftSandbox.resize.bind(this.riftSandbox),
        false
      );

      var kibo = new Kibo(this.domTextArea);
      kibo.down(getShortcut('v'), function () {
        this.riftSandbox.toggleVrMode();
        this.riftSandbox.vrManager.toggleVRMode();
        return false;
      }.bind(this));

      this.riftSandbox.resize();

      this.mainLoop();
    }.bind(this));
  };

  return SketchController;
});
<|endoftext|>