Toradorable Animator

Library to use for Toradorable Animations on agar and deviants. Animations stored separately. To use, @require this first, then Animations.

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/24894/159298/Toradorable%20Animator.js

// ==UserScript==
// @name         Toradorable Animator
// @namespace    http://tampermonkey.net/
// @version      0.0.16
// @description  Library to use for Toradorable Animations on agar and deviants. Animations stored separately. To use, @require this first, then Animations.
// @author       Toradorable
// @grant        none
// ==/UserScript==

/* Library to use for Toradorable Animations on agar and deviants.
 * Animations stored separately. 
 * To use, @require this first, then any Animations you would like. 
 * To play the currently selected animation, call 
animator.playAnimation();
 * NOTE: playAnimation requires per-site functions that are not included with this library.
 * You can find per-site functions in the link below.
 * To stop playing the current animation, call
animator.pauseAnimation(); 
 *
 * To select the next/prev animation, call 
animator.nextAnimation(); animator.prevAnimation();
 * Note that next/prev do not change the playing status. If we are already playing, we will seamlessly switch over to the new animation.
 *
 * To add your own animations, type 
animator.addAnimation({
    title: "Name Of Your Animation",
    // Optional Default display time, used when/if a frame does not have a time specified.
    defaultDisplayTime: 1000,
    frames: [
       //time: Optional display time for this frame in milliseconds,
       //url: "http://Link/To/Your/Image.png",
       //nick: "Optional Nick to use if applicable. Most sites do not allow you to change your nick in game."
         {time: 500, url: "https://s22.postimg.org/jha3867up/image.png", nick: "To"},
         {time: 500, url: "https://s22.postimg.org/jrhlrimgx/image.png", nick: "Ra"},
         {time: 500, url: "https://s22.postimg.org/6xjjy691d/image.png", nick: "Do"},
         {time: 500, url: "https://s22.postimg.org/idpyw7n7l/Ra2.png", nick: "Ra"},
         {time: 500, url: "https://s22.postimg.org/inxhfk1tt/exclam.png", nick: "!"},
         {time: 2000, url: "https://s18.postimg.org/tl8xraeux/Taiga_square.png", nick: "Toradora!"}
    ]
})'
 * To import a skinList, type
animator.importSkinList(
    // First argument is a skin list array.
    // Below is iWubbz's candy skinList as found on
    // https://greasyfork.org/en/scripts/23677-iwubbz-candy-skin-changer/code
    ["http://i.imgur.com/1JQqUzR.png",
     "http://i.imgur.com/VKcEy4k.png",
     "http://i.imgur.com/FKsf0PC.png",
     "http://i.imgur.com/zg6Oxzo.png",
     "http://i.imgur.com/EPawa6H.png",
     "http://i.imgur.com/NyKl8tG.png"
    ],
    // Second argument is optional. However, I recomend setting title at the least.
    //defaultDisplayTime is 1000 (1 second) by default.
    //All frames will be displayed for defaultDisplayTime milliseconds.
    //Use animator.addAnimation if you want different display times per frame.
    {title: "iWubbz's Candy", defaultDisplayTime: 5000}
);
 * ^^ Importing skin lists is as easy as stealing candy from iWubbz. ^^
 * Note that this is just the Toradorable animator library. 
 * Keybindings, Animations, and per-site functions are stored separately.
 *
 * If you need Animations, Keybindings, and Per-Site functions, look in
 * https://greasyfork.org/en/users/79223-Toradorable
 * per-site scripts are labled "Toradorable Site.extention". NOTE: All per-site scripts already include this library.
 * animations are labled "TitleOfAnimation Animation for Toradorable Skin Changer"
 * and extentions are labled "FunctionOfLibrary Extention for Toradorable Skin Changer"
 */



function ToradorableAnimator(initArgs={}) {
	if (!( this instanceof ToradorableAnimator) ) {
	    return new ToradorableAnimator(initArgs);
	}
	function isNumeric(n) {
		if (typeof(n) === 'undefined' || typeof(n) === 'null') return false;
		return !isNaN(parseFloat(n)) && isFinite(n);
	}

	function Print(msg) {
		console.log(msg);
	}
    function ArrayMove(arr, old_index, new_index) {
        if (old_index < 0) {
            old_index += arr.length;
        }
        if (new_index < 0) {
            new_index += arr.length;
        }
        if (new_index >= arr.length) {
            var k = new_index - arr.length;
            while ((k--) + 1) {
                arr.push(undefined);
            }
        }
        arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
        
        return arr; // for testing purposes
    };
    function IsJsonString(str) {
        try {
            JSON.parse(str);
        } catch (e) {
            return false;
        }
        return true;
    };
    var animator=this;
    const keycodes={
    	    backspace:8,    tab:9,         enter:13,
    	    shift:16,       ctrl:17,       alt:18,
    	    pause_break:19, capslock:20,   escape:27,
    	    space:32,       pageup:33,     pagedown:34,
    	    end:35,         home:36,       leftarrow:37,
    	    uparrow:38,     rightarrow:39, downarrow:40,
    	    insert:45,      delete:46,
    	    0:48,   1:49,   2:50,   3:51,
    	    4:52,   5:53,   6:54,   7:55,
    	    8:56,   9:57,   a:65,   b:66,
    	    c:67,   d:68,   e:69,   f:70,
    	    g:71,   h:72,   i:73,   j:74,
    	    k:75,   l:76,   m:77,   n:78,
    	    o:79,   p:80,   q:81,   r:82,
    	    s:83,   t:84,   u:85,   v:86,
    	    w:87,   x:88,   y:89,   z:90,
    	    multiply: 106, add: 107, subtract: 109,
    	    decimalpoint: 110, divide: 111,
    	    f1: 112, f2: 113, f3: 114,
    	    f4: 115, f5: 116, f6: 117,
    	    f7: 118, f8: 119, f9: 120,
    	    f10: 121, f11: 122, f12: 123,
    	    numlock: 144, scrolllock: 145,
    	    semicolon: 186, equalsign: 187,
    	    comma: 188, dash: 189, period: 190,
    	    forwardslash: 191, graveaccent: 192,
    	    openbracket: 219, backslash: 220,
    	    closebraket: 221, singlequote: 222
    	};
	this.startOnFirstFrame = false;
	var _status = "Standby";
	// Automaticly update UI if initilaized
	this.autoUpdate = true;
	this.defaultDisplayTime = 1000;
	this.animations = [];
	this.animationsById = [];
	this.framesById = [];
    this.defaultAnimationTitle="Unamed Animation";
    this.showPreview = true;
    this.autoSave = true;
    this.autoRestore = true;
    this.doSetNick=true;
	//this.framesById = [];
    this.skinList = [];
	
	var _animationIndex = 0;
	var _animationTimeout =  null;
	var _isPlaying = false;
	var _isInitialized = false;
	var _speedMultiplier = 1;
	var _frameIndex = 0;
	var changeEvents={};
	
	
	// FUNCTONS
	this.onSpeedMultiplierChange = function() {
		this.site.elements.speedMultiplierBox.value=this.speedMultiplier;
	};
	this.decrementSpeedMultiplier = function() {
		this.speedMultiplier *= 0.5;
	};
	this.incrementSpeedMultiplier = function() {
		this.speedMultiplier *= 2;
	};
	var changeEventCount=0;
	this.addChangeEvent = function(id,prop,func) {
		//var id=changeEventCount++;
    	if (!(prop in changeEvents)) changeEvents[prop]={};
    	changeEvents[prop][id]=func;
        return id;
    };
    this.removeChangeEvent = function(prop,id) {
        //console.log("Setter called me");
    	if (!(prop in changeEvents) || !(id in changeEvents[prop]) ) return;
        delete changeEvents[prop][id];
    };
    this.callChangeEvent = function(prop) {
    	if (!(prop in changeEvents) ) return;
        //Check here since we may add more call events in out called functions
        var stopon=changeEvents[prop].length;
    	for (var id in changeEvents[prop]) {
    		if (!(changeEvents[prop][id])) continue;
    		changeEvents[prop][id](this,prop);
    	}
    };
	// Name of the animation
	this.titleOf = function(n){
        if (this.animations[n].title !== '') {
            return this.animations[n].title;
        } else {
            return animator.defaultAnimationTitle;
        }
	};
    this.setTitleOf = function(n,title) {
        this.animations[n].title = title;
        return this.animations[n].title;
    };
	this.setAnimation = function(n){
		this.animationIndex = n;
		if (this.animationIndex >= this.animations.length) this.animationIndex = 0;
		if (this.animationIndex < 0) this.animationIndex = this.animations.length - 1;
		if (this.isPlaying && !this.animationTimeout) {
			// Last Animation/frame had a displayTime <= 0, so no timeout as set.
			// We are still considered to be playing, so lets update
			this.playAnimation();
		}
		this.callChangeEvent('animation-select');
		return this.animations[this.animationIndex];
	};
	this.nextAnimation = function(n=1){
		this.animationIndex += n;
		if (this.animationIndex >= this.animations.length) this.animationIndex = 0;
		if (this.isPlaying && !this.animationTimeout) {
			// Last Animation/frame had a displayTime <= 0, so no timeout as set.
			// We are still considered to be playing, so lets update
			this.playAnimation();
		}
		this.callChangeEvent('animation-next');
		this.callChangeEvent('animation-select');
		return this.animations[this.animationIndex];
	};
	this.prevAnimation = function(n=1){
		this.animationIndex -= n;
		if (this.animationIndex < 0) this.animationIndex = this.animations.length - 1;
		if (this.isPlaying && !this.animationTimeout) {
			// Last Animation/frame had a displayTime <= 0, so no timeout as set.
			// We are still considered to be playing, so lets update
			this.playAnimation();
		}
		this.callChangeEvent('animation-prev');
		this.callChangeEvent('animation-select');
		return this.animations[this.animationIndex];
	};
	this.pauseAnimation = function(){
		this.isPlaying=false;
		if (this.animationTimeout) {
	        clearTimeout(this.animationTimeout);
	    }
		this.callChangeEvent('pause');
	};
	this.addChangeEvent('update status','pause', function() {
		animator.status='Standby';
	});
	this.addChangeEvent('update status','play',function() {
		animator.status='Animating Skin';
	});
	
	this.addChangeEvent('animator modified','autoSave', function() {
		if (animator.autoSave) animator.save();
	});
    this.addChangeEvent('animation modified','animation-add', function() {
			animator.callChangeEvent('autoSave');
    });
	this.addChangeEvent('animation modified','animation-remove', function() {
			animator.callChangeEvent('autoSave');
    });
    this.addChangeEvent('animation modified','animation-order', function() {
			animator.callChangeEvent('autoSave');
    });
	this.onFrameChange = function() {
	};
	this.playAnimation = function(sentoptions={}){
		var options=Object.assign({},{
			isFirstFrame: true,
			animationIdx: null,
			mode: "normal", // normal preview
			callback: function() { animator.site.updateFrame(); },
		}, sentoptions);
		this.isPlaying=true;
		var aniIdx = this.animationIndex;
		if ( isNumeric(options.animationIdx) ) {
			aniIdx = options.animationIdx;
		}
		var animation = this.animations[aniIdx];
		if (options.isFirstFrame) {
			if (this.startOnFirstFrame) this.frameIndex = 0;
			this.callChangeEvent('play');
			animation.callChangeEvent('play');
		} else {
			animation.nextFrame();
			//this.callChangeEvent('frame');
		}
		animation.callChangeEvent('show-frame');
		
		if (options.callback) options.callback(animation.currentSkin());
		var time = animation.currentDisplayTime();
		if (time > 0) {
			clearTimeout(this.animationTimeout);
			options.isFirstFrame=false;
			this.animationTimeout=setTimeout(function() { animator.playAnimation(options); }, time);
		}
	};
	this.toggleAnimation = function() {
		if (this.isPlaying) {
			this.pauseAnimation();
		} else {
			this.playAnimation();
		}
	};
	this.refreshFrame = function(){
		this.site.updateFrame( );
	};
	// Frames
	this.init = function() {
		this.site.initialize();
		this.isInitialized=true;
	};
	this.setFrameIndex = function(n) {
		return this.currentAnimation().frameIdx = n;
	};
	this.nextFrame = function(n=1){
		return this.currentAnimation().nextFrame(n);
	};
	this.prevFrame = function(n=1){
		return this.currentAnimation().prevFrame(n);
	};
	
	// For re-transmitting when player-update
	this.currentAnimation = function() {
		if (this.animationIndex >= this.animations.length) this.animationIndex = 0;
		return this.animations[this.animationIndex];
	};
	this.currentFrame = function(){
		return this.currentAnimation().currentFrame();
	};
	this.currentFrameDisplayTime = function() {
		return this.currentAnimation().currentDisplayTime();
	};

	// Agar Networks allows you to change your nick
	this.currentFrameNick = function(){
		return this.currentAnimation().currentNick();
	};
	this.currentFrameSkin = function(){
		return this.currentAnimation().currentSkin();
	};
	
	this.getAnimation = function(id) {
		return this.animations[id];
	};
	this.addAnimation = function(animation){
		//var a = Object.assign({},this.animationTemplate.clone(),animation);
        var a = new this.animationTemplate(animation);
		this.animations.push(a);
		this.animationsById[a.id]=a;
		this.callChangeEvent('animation-add');
		return a;
	};
	this.importSkinList = function(skinList,attributes={}) {
		var animation = this.addAnimation(attributes);
		animation.importFromSkinList(skinList);
		return animation;
	};
	this.moveAnimation = function(source,dest) {
		ArrayMove(this.animations,source,dest);
		this.callChangeEvent('animation-order');
	};
	this.onAddAnimation = function() {
		if (this.autoUpdate && this.isInitialized) {
			this.site.updateUI();
		}
	};
	this.addAnimations = function() {
		for (var i = 0; i < arguments.length; i++) {
			this.addAnimation(arguments[i]);
		}
		return this.animations;
	};
	this.getAnimationSpecifics = function(i) {
		var diffkeys={};
		for (var attr in this.animations[i]) {
            var template = new this.animationTemplate();
			if (attr in template && this.animations[i][attr] === template[attr]) {
				continue;
			}
			diffkeys[attr]=this.animations[i][attr];
		}
		return diffkeys;
	};
	
	
	this.ui = {
		eventlist: {
			animator:{},
			animation:{},
			frame:{},
		},
		addAnimatorEvent: function(region,name,event,fn) {
			if ( !(region in this.eventlist.animator) ) this.eventlist.animator[region] = {};
			if (event in this.eventlist.animator[region]) {
				animator.removeChangeEvent(this.eventlist.animator[region][event]);
			}
			this.eventlist.animator[region][event] = animator.addChangeEvent(region + name,event,fn);
		},
		addAnimationEvent: function(region,name,animation,event,fn) {
			if (!(region in this.eventlist.animation)) this.eventlist.animation[region] = {};
			if (!(event in this.eventlist.animation[region])) this.eventlist.animation[region][event] = {};
			if (animation.id in this.eventlist.animation[region][event]) {
				animation.removeChangeEvent(this.eventlist.animation[region][event][animation.id]);
			}
			this.eventlist.animation[region][event][animation.id] = animation.addChangeEvent(region + name,event,fn);
		},
		addFrameEvent: function(region,name,frame,event,fn) {
			if (!( region in this.eventlist.frame)) this.eventlist.frame[region] = {};
			if (!( event in this.eventlist.frame[region])) this.eventlist.frame[region][event] = {};
			if (frame.id in this.eventlist.frame[region][event]) {
				frame.removeChangeEvent(this.eventlist.frame[region][event][frame.id]);
			}
			this.eventlist.frame[region][event][frame.id] = frame.addChangeEvent(region + name,event,fn);
		},
		resetFrameEvents: function(region) {
			if (!( region in this.eventlist.frame) || !(event in this.eventlist.frame[region])) return;
			for (var event in this.eventlist.frame[region]) {
				for (var frameid in this.eventlist.frame[region][event]) {
					if (frameid in animator.framesById) {
						animator.framesById[frameid].removeChangeEvent(this.eventlist.frame[region][event][frameid]);
					}
					delete this.eventlist.frame[region][event][frameid];
				}
				delete this.eventlist.frame[region][event];
			}
			delete this.eventlist.frame[region];
		},
		resetAnimationEvents: function(region) {
			if (!( region in this.eventlist.animation) || !(event in this.eventlist.animation[region])) return;
			for (var event in this.eventlist.animation[region]) {
				for (var animationid in this.eventlist.animation[region][event]) {
					if (animationid in animator.animationsById) {
						animator.animationsById[animationid].removeChangeEvent(this.eventlist.animation[region][event][animationid]);
					}
					delete this.eventlist.animation[region][event][animationid];
				}
				delete this.eventlist.animation[region][event];
			}
			delete this.eventlist.animation[region];
		},
		resetAnimatorEvents: function(region) {
			if (!(region in this.eventlist.animator) || !(event in this.eventlist.animator[region])) return;
			for (var event in this.eventlist.animator[region]) {
				animator.removeChangeEvent(this.eventlist.animator[region][event]);
				delete this.eventlist.animator[region][event];
			}
			delete this.eventlist.animator[region];
		},
		elements: {
				showAnimations: null,
				frameByFrameUI: null,
		},
		clearShowAnimations: function() {
			var eventRegion='show-Animations';
			this.resetAnimatorEvents(eventRegion);
			this.resetAnimationEvents(eventRegion);
			this.resetFrameEvents(eventRegion);
            if ('showAnimations' in this.elements && this.elements.showAnimations ) {
                this.elements.showAnimations.remove();
                delete(this.elements.showAnimations);
            }
		},
        hideAnimations: function() {
            this.elements.showAnimations.style.display="none";
        },
        showAnimations: function() {
            if (! this.elements.showAnimations) {
                this.createAnimationsList();
            } else {
                this.elements.showAnimations.style.display="inline-block";
            }
        },
		createAnimationsList: function() {
			var eventRegion='show-Animations';
			this.clearShowAnimations();
			var ui = this;
			var UIcontainer = document.createElement("div");
			
			this.elements.showAnimations=UIcontainer;
			UIcontainer.style.overflow="scroll";
			UIcontainer.style.height="100vh";
            UIcontainer.style.display="inline-block";
			UIcontainer.className="alisio-panel enabledAnimations";
			
			var div = document.createElement("div");
            div.style.display="inline-block"
			//var ul = document.createElement("ul");
			for (var i=0; i < animator.animations.length; i++) {
				//var li = document.createElement("li");
                var animation=animator.animations[i];
                var animationDiv=document.createElement("div");
                animationDiv.dataset.associatedAnimationId=animation.id;
                //option.style.cssText = document.defaultView.getComputedStyle(chatboxInput, "").cssText;
				var img = document.createElement("img");
				var title = animator.site.elements.cssElement.cloneNode(true);
				title.value=animator.animations[i].title;
                title.placeholder=animator.defaultAnimationTitle;
                title.style.width="calc(100% - 50px)";
                title.onchange=(function(animation,key,element){ return function() {
                        animation[key]=element.value;
                    };
                })(animation,'title',title);
                (function(title,animation){
	                ui.addAnimationEvent(eventRegion,'title update',animation,'title',function() {
	                	title.value=animation.title;
	                })
                })(title,animation);
				var json = animator.site.elements.cssElement.cloneNode(true);
                //var changedValues=animator.getAnimationSpecifics(i);
                //delete changedValues.title;
				json.value = JSON.stringify(animator.animations[i].saveable);
				json.title = 'JSON animation object or a comma seperated quoted image links.';
				(function(animation,json) {
					json.onchange = function () {
						var jsonstr = json.value.trim();
						if (jsonstr.charAt(0) == '{' && IsJsonString(jsonstr) ) {
							animation.saveable=JSON.parse(json.value);
							json.style.backgroundColor=animator.site.elements.cssElement.style.backgroundColor;
						}
						else if (jsonstr.charAt(0) === '[' && IsJsonString(jsonstr)) {
							animation.removeAllFrames();
							animation.importFromSkinList(JSON.parse( jsonstr ));
							json.style.backgroundColor=animator.site.elements.cssElement.style.backgroundColor;
						}
						else if ((jsonstr.charAt(0) == '"') && IsJsonString('[' + jsonstr + ']')) {
							animation.removeAllFrames();
							animation.importFromSkinList(JSON.parse( '[' + jsonstr + ']'));
							json.style.backgroundColor=animator.site.elements.cssElement.style.backgroundColor;
						}
						else {
							alert('Must be a valid json object!\n' + 
								'Try double clicking on the image to edit the animation instead. ');
							json.style.backgroundColor='red';
						}
					};
					animation.addChangeEvent('update json','animation-modify', function() {
						json.value=JSON.stringify(animation.saveable);
						json.style.backgroundColor=animator.site.elements.cssElement.style.backgroundColor;
					});
				})(animation,json);
				img.src = (animation.frames.length > 0) ? animation.frames[0].url : "";
				img.style.width='50px';
				img.style.height='50px';
				img.style.borderRadius='50% 50%';
                img.title="Double Click to Edit";
                img.onmouseenter=(function(img,animation) {
                    return function() {
                        animator.playAnimation({animationIdx: animator.animations.indexOf(animation),callback: function(skin) {
                        	img.src=skin;
                        	//if (controlVisible && animator.showPreview) {
                			animator.site.elements.skinpreview.src = skin;
                			//}
                        }});
                    };
                })(img,animation);
                img.onmouseleave=function() { 
                	animator.pauseAnimation();
                	animator.site.elements.skinpreview.src = animator.site.elements.skinurl.value;
                };
                (function(animation) {
	                img.ondblclick=function() {
                        animator.ui.hideAnimations();
	                	animator.ui.frameByFrameUI(animation);
	                };
                })(animation);
               // (function(img,animation){
	           //     ui.addAnimationEvent(eventRegion,'img update',animation,'frame-modify',function() {
	           //     	img.src=(animation.frames.length > 0) ? animation.frames[0].url : "";
	           //     })
               // })(img,animation);
                (function(img,animation){
	                ui.addAnimationEvent(eventRegion,'img update',animation,'frame-add',function() {
	                	img.src=(animation.frames.length > 0) ? animation.frames[0].url : "";
	                });
                    ui.addAnimationEvent(eventRegion,'img update',animation,'frame-remove',function() {
	                	img.src=(animation.frames.length > 0) ? animation.frames[0].url : "";
	                });
                    ui.addAnimationEvent(eventRegion,'img update',animation,'frame-order',function() {
	                	img.src=(animation.frames.length > 0) ? animation.frames[0].url : "";
	                })
                })(img,animation);
				//animationDiv.id=
                animationDiv.appendChild(img);
                animationDiv.appendChild(title);
				animationDiv.appendChild(json);
				
			    //ui.elements.animationSelector.appendChild(li);
                //li.appendChild(animationDiv);
				animationDiv.className = 'orderedAnimation';

                //animationDiv.dataset.shortname=i;
				div.appendChild(animationDiv);
			}
			//div.appendChild(ul);
			//this.addAnimatorEvent(eventRegion,'animation-order', function() {
			//	ui.createAnimationsList();
			//});
			this.addAnimatorEvent(eventRegion,'rebuild animation list','animation-add', function() {
				ui.createAnimationsList();
			});
			this.addAnimatorEvent(eventRegion,'rebuild animation list','animation-remove', function() {
				ui.createAnimationsList();
			});
			var newAnimationBtn = animator.site.elements.cssButton.cloneNode(true);
			newAnimationBtn.textContent="Create";
			newAnimationBtn.title="Create New Animation";
			/*newAnimationBtn.style.width='100px';
            newAnimationBtn.style.height='100px';
            newAnimationBtn.style.top='0px';
            newAnimationBtn.style.left='0px';
            newAnimationBtn.style.right='0px';
            newAnimationBtn.style.bottom='0px';
            newAnimationBtn.style.position='absolute';*/
			//newAnimationBtn.style.left='8px';
			newAnimationBtn.onclick=function() {
				var newAnimation = animator.addAnimation();
			};
			/*var exportBtn = animator.site.elements.cssButton.cloneNode(true);
			exportBtn.textContent="Export";
			exportBtn.title="Export Animation List";
			exportBtn.onclick=function() {
				var newAnimation = animator.addAnimation();
			};*/
			UIcontainer.appendChild(div);
			UIcontainer.appendChild(newAnimationBtn);
			
			var deleteBox = document.createElement('div');
			//var deleteBox = document.createElement('div');
			deleteBox.style.cssText = animator.site.elements.cssButton.style.cssText;
			deleteBox.style.width='200px';
			deleteBox.style.height='88vh';
            deleteBox.style.position='absolute';
            deleteBox.style.boxSizing='border-box';
            //deleteBox.style.position='fixed';
            deleteBox.style.top='0vh';
            deleteBox.style.left='0vw';
            deleteBox.style.zIndex='1001';
            deleteBox.style.lineHeight='88vh';

			deleteBox.style.backgroundColor='red';
			deleteBox.textContent="Delete";
			deleteBox.title="Drag Animation here to Delete";
            deleteBox.style.display="none";
			animator.site.elements.uiOverlay.appendChild(deleteBox);
			document.getElementById('helloContainer').appendChild(UIcontainer);
            dragula([div], {
            	isContainer: function (el) {
            		if (el === deleteBox) return true;
            	    return false; // only elements in drake.containers will be taken into account
            	},
                revertOnSpill: true,
            	//moves: function (el, source, handle, sibling) {
                  //if (el === deleteBox) return true;
            	//  return true; // elements are always draggable by default
            	//},
            }).on('drop',function(el, target, source, sibling) {
                deleteBox.style.display="none";
                if (target === deleteBox) {
                    animator.removeAnimation(animator.animationsById[el.dataset.associatedAnimationId]);
                    return;
                }
            	var sourceIdx = animator.animations.indexOf( animator.animationsById[el.dataset.associatedAnimationId]);
                if (sibling) {
                	var destIdx = animator.animations.indexOf(animator.animationsById[sibling.dataset.associatedAnimationId]);
                    if (destIdx > sourceIdx) destIdx -= 1;
                    //console.log("Moving from " + sourceIdx +  " to " + destIdx);
                	animator.moveAnimation(sourceIdx, destIdx);
                } else {
                   // console.log("Moving from " + sourceIdx +  " to end at " + animator.animations.length - 1);
                	animator.moveAnimation(sourceIdx,animator.animations.length - 1);
                }
            }).on('drag', function(el,source) {
                //deleteBox.style.top=el.style.top;
                deleteBox.style.display="inline-block";
            }).on('cancel', function(el,container,source) {
                deleteBox.style.display="none";
            });
		},
        showSkinList: function() {
        	var eventRegion='skinList';
			var div = document.createElement("div");
            div.style.overflow="scroll";
            div.style.height="100vh";
			div.className="alisio-panel enabledAnimations";
			for (var i=0; i < animator.animations.length; i++) {
                var animation=animator.animations[i];
                var animationDiv=document.createElement("div");
			    var li = animationDiv;
                //option.style.cssText = document.defaultView.getComputedStyle(chatboxInput, "").cssText;
				var img = document.createElement("img");
				var title = animator.site.elements.cssElement.cloneNode(true);
                title.placeholder=animator.defaultAnimationTitle;
				title.value=animator.animations[i];
                title.onchange=(function(animation,key,element){ return function() {
                    animation[key]=element.value;
                 };
            })(animation,'title',title);
				var skinListBox = animator.site.elements.cssElement.cloneNode(true);
                var speedBox = animator.site.elements.cssElement.cloneNode(true);
                var changedValues=animator.getAnimationSpecifics(i);
                delete changedValues.title;
                
				skinListBox.value = JSON.stringify(changedValues);
				img.src = animation.frames[0].url;
				img.style.width='50px';
				img.style.height='50px';
				img.style.borderRadius='50% 50%';
                img.onmouseenter=(function(img,i) {
                    return function() {
                    	animator.playAnimation({
                    		isFirstFrame: true,
                    		animationIdx: i,
                    		callback: function(skin) { img.src=skin; }
                    	});
                    };
                })(img,i);
                img.onmouseleave=function() { animator.pauseAnimation(); console.log("Pausing Animation");};
				animationDiv.appendChild(img);
                animationDiv.appendChild(title);
				animationDiv.appendChild(skinListBox);
                animationDiv.appendChild(speedBox);
				
				li.className = 'orderedAnimation';
				div.appendChild(li);
			}
			//div.appendChild(ul);
			document.getElementById('helloContainer').appendChild(div);
            dragula([div]);
		},
		clearFrameByFrameUI: function() {
			var eventRegion='frame-by-frame';
			this.resetAnimatorEvents(eventRegion);
			this.resetAnimationEvents(eventRegion);
			this.resetFrameEvents(eventRegion);
            
			if ('frameByFrameUI' in this.elements && this.elements.frameByFrameUI ) {
                this.elements.frameByFrameUI.remove();
                delete(this.elements.frameByFrameUI);
            }
		},
        frameByFrameUI: function(animation) {
        	var eventRegion='frame-by-frame';
        	this.clearFrameByFrameUI();
            var ui=this;
            var UIcontainer = document.createElement("div");
            this.elements.frameByFrameUI=UIcontainer;
         // define a handler
            function doc_keyUp(e) {
                if ( e.keycode === keycodes.escape ) {
                    animator.ui.clearFrameByFrameUI();
                    animator.ui.showAnimations();
                }
            }
            // register the handler 
            var savebtn = document.createElement('BUTTON');
            savebtn.onclick=function() {
            	animator.ui.clearFrameByFrameUI();
                animator.ui.showAnimations();
            }
            savebtn.textContent="Save";
            savebtn.title='Closes Animation Editor and Returns to Animation List';
            savebtn.style.cssText=animator.site.elements.cssButton.style.cssText;
            //savebtn.style.cssText = document.defaultView.getComputedStyle(animator.site.elements.cssElement, "").cssText;
            savebtn.style.display='inline-block';
            savebtn.style.opacity='1';
            savebtn.style.position='relative';
            savebtn.style.right='auto';
            savebtn.style.left='8px';
            savebtn.style.top='0px';
            savebtn.style.bottom='auto';
            savebtn.style.width='25%';
            
            UIcontainer.onkeyup=function(e) {
                if ( e.keycode === keycodes.escape ) {
                    animator.ui.clearFrameByFrameUI();
                    animator.ui.showAnimations();
                }
            };
            UIcontainer.style.overflow="scroll";
            UIcontainer.style.height="100vh";
			UIcontainer.className="alisio-panel";
            var title = animator.site.elements.cssElement.cloneNode(true);
            title.placeholder=animator.defaultAnimationTitle;
            title.value=animation.title;
            title.title='Title of Animation';
            title.onchange=(function(animation,key,element){ return function() {
                    animation[key]=element.value;
                 };
            })(animation,'title',title);
            (function(title,animation){
                ui.addAnimationEvent(eventRegion,'frame animation title',animation,'title',function() {
                	title.value=animation.title;
                })
            })(title,animation);
            title.style.width="70%";
            
            UIcontainer.appendChild(title);
            UIcontainer.appendChild(savebtn);
            
			var frameContainter = document.createElement("div");
            UIcontainer.appendChild(frameContainter);
			for (var i=0; i < animation.frames.length; i++) {
                var frame = animation.frames[i];
                var animationDiv=document.createElement("div");
                //option.style.cssText = document.defaultView.getComputedStyle(chatboxInput, "").cssText;
				var img = document.createElement("img");
				
				var skinBox = animator.site.elements.cssElement.cloneNode(true);
                skinBox.placeholder="url of skin:";
                skinBox.title="URL of the desired image.";
                skinBox.onchange=(function(img,frame,skinBox){ return function() {
                    frame.url=skinBox.value;
                    img.src=frame.url;
                  };
                })(img,frame,skinBox);
                (function(skinBox,frame,img){
                    ui.addFrameEvent(eventRegion,'skinbox image',frame,'title',function() {
                    	skinBox.value=frame.url;
                    	img.src=frame.url;
                    });
                })(skinBox,frame,img);
                
                var speedBox = animator.site.elements.cssElement.cloneNode(true);
                speedBox.onchange=(function(frame,key,element){ return function() {
                    frame[key]=element.value;
                  };
                })(frame,'time',speedBox);
                (function(speedBox,frame){
                    ui.addFrameEvent(eventRegion,'speedbox title',frame,'time',function() {
                    	speedBox.value=frame.time;
                    });
                })(speedBox,frame);
                
                speedBox.placeholder="Display time(in milliseconds):";
                speedBox.title="How long we should display this image frame(1000 = 1 sec)";
                if (animator.site.canSetNick) {
                	// Nick cannot be changed on live cells on most sites.
	                var nickBox = animator.site.elements.cssElement.cloneNode(true);
	                nickBox.onchange=(function(frame,key,element){ return function() {
	                    frame[key]=element.value;
	                  };
	                })(frame,'nick',nickBox);
	                (function(nickBox,frame){
	                    ui.addFrameEvent(eventRegion,'nickbox',frame,'nick',function() {
	                    	nickBox.value=frame.nick;
	                    });
	                })(nickBox,frame);
	                nickBox.placeholder="Nick to use:";
	                nickBox.title="What to name our cell when using this frame.";
	                nickBox.value = ('nick' in frame) ? frame.nick : "";
                }
                //var changedValues=AnimationContainer.getAnimationSpecifics(i);
                skinBox.style.width="calc(100% - 50px)";
				skinBox.value = ('url' in frame) ? frame.url : "";
                speedBox.value = (frame.hasExplicitTime) ? frame.time : "";
				img.src = frame.url;
				img.style.width='50px';
				img.style.height='50px';
				img.style.borderRadius='50% 50%';
                //img.onmouseenter=(function(img,i) {return function() { AnimationContainer.playAnimationOn(true,i,function(skin) { img.src=skin; console.log("Playing Animation"); }) }  })(img,i);
                //img.onmouseleave=function() { AnimationContainer.pauseAnimation(); console.log("Pausing Animation");};
				
                animationDiv.appendChild(img);
				animationDiv.appendChild(skinBox);
                animationDiv.appendChild(speedBox);
                if (animator.site.canSetNick) animationDiv.appendChild(nickBox);
				animationDiv.dataset.associatedFrameId=frame.id;
				animationDiv.className = 'orderedAnimation';

				frameContainter.appendChild(animationDiv);
			}
			(function(animation){
                ui.addAnimationEvent(eventRegion,'frameByFrame',animation,'frame-order',function() {
                	ui.frameByFrameUI(animation);
                });
            })(animation);
			(function(animation){
                ui.addAnimationEvent(eventRegion,'frameByFrame',animation,'frame-add',function() {
                	ui.frameByFrameUI(animation);
                });
            })(animation);
			(function(animation){
                ui.addAnimationEvent(eventRegion,'frameByFrame',animation,'frame-remove',function() {
                	ui.frameByFrameUI(animation);
                });
            })(animation);
			var newFrameBtn = animator.site.elements.cssButton.cloneNode(true);
			newFrameBtn.textContent="Add Frame";
			newFrameBtn.title="Create a new frame in the Animation";
			newFrameBtn.style.width='100%';
			newFrameBtn.style.left='8px';
			newFrameBtn.onclick=function() {
				var newFrame = animation.addFrame();
			};
			//UIcontainer.appendChild(div);
			UIcontainer.appendChild(newFrameBtn);
			//div.appendChild(ul);
            var deleteBox = document.createElement('div');
			//var deleteBox = document.createElement('div');
			deleteBox.style.cssText = animator.site.elements.cssButton.style.cssText;
			deleteBox.style.width='200px';
			deleteBox.style.height='88vh';
            deleteBox.style.position='absolute';
            deleteBox.style.boxSizing='border-box';
            //deleteBox.style.position='fixed';
            deleteBox.style.top='0vh';
            deleteBox.style.left='0vw';
            deleteBox.style.zIndex='1001';
            deleteBox.style.lineHeight='88vh';

			deleteBox.style.backgroundColor='red';
			deleteBox.textContent="Delete";
			deleteBox.title="Drag Frame here to Delete";
            deleteBox.style.display="none";
			animator.site.elements.uiOverlay.appendChild(deleteBox);
			document.getElementById('helloContainer').appendChild(UIcontainer);
            dragula([frameContainter],{
                isContainer: function (el) {
            		if (el === deleteBox) return true;
            	    return false; // only elements in drake.containers will be taken into account
            	},
                revertOnSpill: true,
            }).on('drop',function(el, target, source, sibling) {
                deleteBox.style.display="none";
                if (target === deleteBox) {
                    animation.removeFrame(animation.framesById[el.dataset.associatedFrameId]);
                    return;
                }
            	var sourceIdx = animation.frames.indexOf( animation.framesById[el.dataset.associatedFrameId]);
                if (sibling) {
                	var destIdx = animation.frames.indexOf( animation.framesById[sibling.dataset.associatedFrameId]);
                    if (destIdx > sourceIdx) destIdx -= 1;
                	animation.moveFrame(sourceIdx, destIdx);
                } else {
                	animation.moveFrame(sourceIdx,animation.frames.length-1);
                }
                //ArrayMove(el.associatedAnimationIdx,sibling.associatedAnimationIdx);
                //AnimationContainer.animations[el.associatedAnimationIdx];
                //AnimationContainer.animation.move();
               // for (var i = 0; i < target.children.length; i++) {
                    //div.children[i].associatedAnimationIdx--;
               //     target.children[i].dataset.associatedAnimationIdx=i;
               // }
            }).on('drag', function(el,source) {
                //deleteBox.style.top=el.style.top;
                deleteBox.style.display="inline-block";
            }).on('cancel', function(el,container,source) {
                deleteBox.style.display="none";
            });
		}
	};
	this.site = {
		nick: null,
		canSetNick: true,
		elements: {
			uiOverlay: null,
			uiContainer: null,
			nick: null,
			skinurl: null,
			skinpreview: null,
			chatboxInput: null,
			mipmapNode: null,
			animationSelector: null,
			animationStatus: null,
			cssButton: null,
		},
		// changeEventList: region: type (animator/animation): id 
		eventlist: {
			animator:{},
			animation:{},
		},
		addAnimatorEvent: function(region,name,event,fn) {
			if (!(region in this.eventlist.animator)) this.eventlist.animator[region] = {};
			if (event in this.eventlist.animator[region]) {
				animator.removeChangeEvent(this.eventlist.animator[region][event]);
			}
			this.eventlist.animator[region][event] = animator.addChangeEvent(region + name,event,fn);
		},
		addAnimationEvent: function(region,name,animation,event,fn) {
			if (!( region in this.eventlist.animation)) this.eventlist.animation[region] = {};
			if (!( event in this.eventlist.animation[region])) this.eventlist.animation[region][event] = {};
			if (animation.id in this.eventlist.animation[region][event]) {
				animation.removeChangeEvent(this.eventlist.animation[region][event][animation.id]);
			}
			this.eventlist.animation[region][event][animation.id] = animation.addChangeEvent(region + name,event,fn);
		},
		resetAnimationEvents: function(region) {
			if (!(region in this.eventlist.animation) || !(event in this.eventlist.animation[region])) return;
			for (var event in this.eventlist.animation[region]) {
				for (var animationid in this.eventlist.animation[region][event]) {
					if (animationid in animator.animationsById) {
						animator.animationsById[animationid].removeChangeEvent(this.eventlist.animation[region][event][animationid]);
					}
					delete this.eventlist.animation[region][event][animationid];
				}
				delete this.eventlist.animation[region][event];
			}
			delete this.eventlist.animation[region];
		},
		resetAnimatorEvents: function(region) {
			if (!(region in this.eventlist.animator)|| !(event in this.eventlist.animator[region])) return;
			for (var event in this.eventlist.animator[region]) {
				animator.removeChangeEvent(this.eventlist.animator[region][event]);
				delete this.eventlist.animator[region][event];
			}
			delete this.eventlist.animator[region];
		},
		initialize: function() {
			this.elements.mipmapNode = document.getElementById("mipmapNode");
			this.elements.chatboxInput=document.getElementById("input_box2");
			this.elements.uiOverlay=document.getElementById("helloContainer");
			this.elements.uiContainer=document.getElementById("overlays2");
			this.elements.nick = document.getElementById('nick');
			this.elements.skinurl = document.getElementById('skinurl');
			this.elements.skinpreview = document.getElementById('preview-img');
            this.elements.playButton = document.querySelector('.btn.btn-info.btn-play');
            
            this.elements.cssButton = this.elements.playButton.cloneNode(true);
            this.elements.cssButton.id="";
            this.elements.cssButton.name="";
            this.elements.cssButton.style.cssText=document.defaultView.getComputedStyle(this.elements.playButton, "").cssText;
            this.elements.cssButton.style.display='inline-block';
            this.elements.cssButton.style.opacity='1';
            this.elements.cssButton.style.position='relative';
            this.elements.cssButton.style.right='auto';
            this.elements.cssButton.style.left='auto';
            this.elements.cssButton.style.top='auto';
            this.elements.cssButton.style.bottom='auto';
            this.elements.cssButton.style.width='auto';
			this.elements.cssElement = this.elements.chatboxInput.cloneNode(true);
            this.elements.cssElement.id="";
            this.elements.cssElement.name="";
            this.elements.cssElement.style.cssText=document.defaultView.getComputedStyle(this.elements.chatboxInput, "").cssText;
                //this.elements.chatboxInput.style.cssText;
			//this.elements.cssButton = document.getElementById('');
            var siteGetDefaultSkin = myApp["getCustomSkinUrl"];
            myApp["getCustomSkinUrl"] = function () {
            	if (animator.isPlaying) {
            		return animator.currentFrameSkin();
            	} else {
            		return siteGetDefaultSkin();
            	}
            };
            this.canSetNick=false;
			this.initilaizeUI();
		},
		getNick: function() {
			return this.elements.nick.value;
		},
		setNick: function(newNick) {
			this.elements.nick.value = newNick;
			return newNick;
		},
		getSkin: function() {
		},
		setSkin: function(skin) {
		},
		isInGame() { // must be implemented on a per site basis
			if (typeof getCell === 'function' && getCell().length >0) {
				return true
			}
			return false;
		},
		//changeNickTo: function () {   },
		//changeSkinTo: function () {   },
		refreshCurrentFrame: function() {   },
		updateFrame: function(nick=animator.currentFrameNick(), skin=animator.currentFrameSkin(), time=animator.currentFrameDisplayTime(), displaylocal=true) {
		    //this.elements.skinurl.value = skin;
		    //setNick(nick,team,skin,partytoken);
		    //setNick(document.getElementById('nick').value);
			var controlVisible = ! this.elements.uiOverlay.hidden;
			if (controlVisible && animator.showPreview) {
				this.elements.skinpreview.src = skin;
			}
			if (animator.site.isInGame()) {
				var player=playerDetailsByIdentifier[nodeList[0][1] + nodeList[0][6]];
			    socket.emit("playerUpdated", {
			        "action": "update",
			        "displayName": player.displayName,
			        "socketRoom": player.socketRoom,
			        "identifier": player.identifier,
			        "url": skin,
			        "nick": player.nick,
			        "team": player.team,
			        "token": player.token
			    });
			    nodeList[0][5]=skin;
			    if (displaylocal) {
			    	player.url=skin;
			    }
			}
		},
		updateUI: function(status=animator.status) {
			this.elements.animationStatus.textContent=status;
			for (var i = this.elements.animationSelector.options.length - 1 ; i >= 0 ; i--)
		    {
				this.elements.animationSelector.remove(i);
		    }
			for (var i = 0; i < animation.animations.length; i++) {
			    var option = document.createElement("option");
			    //option.style.cssText = document.defaultView.getComputedStyle(chatboxInput, "").cssText;
			    option.value = i;
			    option.text = animation.titleOf(i);
			    //this.elements.animationSelector.children[i] = option;
			    this.elements.animationSelector.appendChild(option);
			}
			this.elements.animationSelector.selectedIndex=animation.animationIndex;
		},
		rebuildAnimationSelectBox: function() {
			var eventRegion = 'statusUI';
			var animationSelector = this.elements.animationSelector;
			for (var i = animationSelector.options.length - 1 ; i >= 0 ; i--)
		    {
				animationSelector.remove(i);
		    }
			for (var i = 0; i < animator.animations.length; i++) {
				var eventRegion = '';
			    var option = document.createElement("option");
			    //option.style.cssText = document.defaultView.getComputedStyle(chatboxInput, "").cssText;
			    var animation=animator.animations[i];
			    option.value = i;
			    option.text = animator.titleOf(i);
                //option.text = animator.defaultAnimationTitle;
			    (function(i,option,thissite) {
			    	thissite.addAnimationEvent(eventRegion,'animation option',animator.animations[i],'title', function() {
				    	option.text=animator.titleOf(i);
					});
			    })(i,option,this);
			    animationSelector.appendChild(option);
			}
		},
		initilaizeUI: function() {
			var eventRegion="statusUI";
			var ui = this;
			this.resetAnimatorEvents(eventRegion);
			this.resetAnimationEvents(eventRegion);
			var SkinTargetType = document.createElement('BUTTON');
			if (this.elements.animationStatus) this.elements.animationStatus.remove();
			this.elements.animationStatus=SkinTargetType;
			SkinTargetType.name="Skin Target Type:";
			SkinTargetType.id="SkinTargetType";
			SkinTargetType.textContent="Standby"; // Theft, Swap, Push
			SkinTargetType.placeholder="Skin Target Type:";
			SkinTargetType.style.cssText = animator.site.elements.cssElement.style.cssText;
			SkinTargetType.style.width="200px";
			SkinTargetType.style.right="9px";
			SkinTargetType.style.bottom="250px";
			SkinTargetType.style.position="absolute";
			SkinTargetType.onclick=function(e) { animator.toggleAnimation(); };
			this.addAnimatorEvent(eventRegion,'status box','status', function() {
				SkinTargetType.textContent = animator.status;
			});
			this.elements.uiContainer.insertBefore(SkinTargetType, this.elements.uiContainer.lastChild);
			
			if (this.elements.speedMultiplierBox) this.elements.speedMultiplierBox.remove();
			var SpeedMultiplierBox = this.elements.cssElement.cloneNode(true);
			this.elements.speedMultiplierBox=SpeedMultiplierBox;
			SpeedMultiplierBox.name="SpeedMultiplier:";
			SpeedMultiplierBox.id="SpeedMultiplierBox";
			SpeedMultiplierBox.value=1; // Theft, Swap, Push
			SpeedMultiplierBox.placeholder="Speed Multiplier:";
			SpeedMultiplierBox.style.cssText =this.elements.cssElement.style.cssText;
			SpeedMultiplierBox.style.width="140px";
			SpeedMultiplierBox.style.right="39px";
			SpeedMultiplierBox.style.bottom="290px";
			SpeedMultiplierBox.style.position="absolute";
			SpeedMultiplierBox.onchange=function(e) { animator.speedMultiplier=e.target.value; };
			this.elements.uiContainer.insertBefore(SpeedMultiplierBox, this.elements.uiContainer.lastChild);
			this.addAnimatorEvent(eventRegion,'SpeedMultiplierBox','speedmultiplier', function() {
				SpeedMultiplierBox.value=animator.speedMultiplier;
			});
			
			if (this.elements.incrementSpeedMuliplier) this.elements.incrementSpeedMuliplier.remove();
			var IncrementSpeed = document.createElement('BUTTON');
			this.elements.incrementSpeedMuliplier=IncrementSpeed;
			IncrementSpeed.name="Skin Target Type:";
			IncrementSpeed.id="incrementSpeedMultiplier";
			IncrementSpeed.textContent="+"; // Theft, Swap, Push
			IncrementSpeed.placeholder="Skin Target Type:";
			IncrementSpeed.style.cssText = this.elements.cssElement.style.cssText;
			IncrementSpeed.style.width="30px";
			IncrementSpeed.style.right="9px";
			IncrementSpeed.style.bottom="290px";
			IncrementSpeed.style.position="absolute";
			IncrementSpeed.onclick=function(e) { animator.incrementSpeedMultiplier(); };
			this.elements.uiContainer.insertBefore(IncrementSpeed, this.elements.uiContainer.lastChild);
			
			if (this.elements.decrementSpeedMuliplier) this.elements.decrementSpeedMuliplier.remove();
			var DecrementSpeed = document.createElement('BUTTON');
			this.elements.decrementSpeedMuliplier=DecrementSpeed;
			DecrementSpeed.name="Skin Target Type:";
			DecrementSpeed.id="decrementSpeedMultiplier";
			DecrementSpeed.textContent="-"; // Theft, Swap, Push
			DecrementSpeed.placeholder="Skin Target Type:";
			DecrementSpeed.style.cssText = this.elements.cssElement.style.cssText;
			DecrementSpeed.style.width="30px";
			DecrementSpeed.style.right="179px";
			DecrementSpeed.style.bottom="290px";
			DecrementSpeed.style.position="absolute";
			DecrementSpeed.onclick=function(e) { animator.decrementSpeedMultiplier(); };
			this.elements.uiContainer.insertBefore(DecrementSpeed, this.elements.uiContainer.lastChild);
			//overlays2.insertBefore(StealSkinBox, overlays2.lastChild);

			if (this.elements.animationSelector) this.elements.animationSelector.remove();
			var SkinListBox = document.createElement("select"); //StealSkinBox.cloneNode(true);
			this.elements.animationSelector=SkinListBox;
			SkinListBox.name="Selected Skin:";
			SkinListBox.id="SelectedSkinElm";
			SkinListBox.value=""; // Theft, Swap, Push
			SkinListBox.placeholder="No Animation Selected";
			SkinListBox.style.cssText = this.elements.cssElement.style.cssText;
			SkinListBox.style.width="200px";
			SkinListBox.style.right="9px";
			SkinListBox.style.bottom="210px";
			SkinListBox.style.position="absolute";
			this.addAnimatorEvent(eventRegion,'skinListBox','animation-select', function() {
				SkinListBox.selectedIndex=animator.animationIndex;
			});
			
			overlays2.insertBefore(SkinListBox, overlays2.lastChild);

			this.rebuildAnimationSelectBox();
			this.addAnimatorEvent(eventRegion,'skinListBox','animation-order', function() {
				ui.rebuildAnimationSelectBox();
			});
			this.addAnimatorEvent(eventRegion,'skinListBox','animation-add', function() {
				ui.rebuildAnimationSelectBox();
			});
			this.addAnimatorEvent(eventRegion,'skinListBox','animation-remove', function() {
				ui.rebuildAnimationSelectBox();
			});
			SkinListBox.onchange=function(event){ animator.setAnimation(event.target.value); };
		},
        
	};
	
	this.removeAnimation = function(animation){ 
		var idx = this.animations.indexOf(animation);
		while (idx >= 0) {
			this.animations.splice(idx,1);
			idx = this.animations.indexOf(animation);
		}
		delete this.animationsById[animation.id];
		this.callChangeEvent('animation-remove');
	};
	this.Frame = function(animation,attr) {
		//if (!( 'instances' in animator.animationTemplate.Frame)) {
		//	animator.animationTemplate.Frame.instances = {};
		//}
		animator.Frame.count = (animator.Frame.count || 0) + 1;
		var _id = animator.Frame.count;
		//animator.animationTemplate.Frame.instances[_id]=this;
		var _nick;
		var _time;
		var _url;
		//var animation;
		if (!(animation)) {
			console.error('Cannot create a frame putside of an animation.');
			return;
		}
		var changeEvents={};
        
        this.addChangeEvent = function(id,prop,func) {
    		//var id=changeEventCount++;
        	if (!(prop in changeEvents)) changeEvents[prop]={};
        	changeEvents[prop][id]=func;
            return id;
        };
        this.removeChangeEvent = function(prop,id) {
            //console.log("Setter called me");
        	if (!(prop in changeEvents) || !(id in changeEvents[prop]) ) return;
            delete changeEvents[prop][id];
        };
        this.callChangeEvent = function(prop) {
        	if (!(prop in changeEvents) ) return;
            //Check here since we may add more call events in out called functions
            var stopon=changeEvents[prop].length;
        	for (var id in changeEvents[prop]) {
        		if (!(changeEvents[prop][id])) continue;
        		changeEvents[prop][id](this,prop);
        	}
        };
		Object.defineProperties(this, {
	        "nick": {
	             "get": function() {
	            	 if (_nick) {
                         return _nick;
                     } else {
                         return "";
                     }
	             },
	             "set": function(val) {
	            	 _nick=val;
	            	 this.callChangeEvent('nick');
	                 //return _title;
	             }
	        },
            "hasExplicitTime": {
	             "get": function() {
	            	 if (isNumeric(_time) && _time > 0) {
	            		return true;
	            	 } else {
                         return false;
                     }
	             },
            },
	        "time": {
	             "get": function() {
	            	 if (isNumeric(_time) && _time > 0) {
	            		return _time;
	            	 } else {
	            		return animation.defaultDisplayTime;
	            	 }
	             },
	             "set": function(val) {
	            	 _time=val;
	            	 this.callChangeEvent('time');
	            	 return _time;
	                 //return _title;
	             }
	        },
	        "url": {
	             "get": function() {
	            	 if (_url) {
	            		return _url;
	            	 } else {
	            		 return "";
	            	 }
	             },
	             "set": function(val) {
	            	 _url=val;
	            	 this.callChangeEvent('url');
	                 //return _title;
	             }
	        },
            stringify: {
                "get": function() {
                    var string="{";
                    if ( typeof(_url)  !== 'undefined' && typeof(_url)  !== 'null' && _url !== '') string += '"url": ' + JSON.stringify(_url);
                    if ( typeof(_nick) !== 'undefined' && typeof(_nick)  !== 'null' && _nick !== '') string += ', "nick": ' + JSON.stringify(_nick);
                    if ( typeof(_time) !== 'undefined' && typeof(_time)  !== 'null' ) string += ', "time": ' + JSON.stringify(_time);
                    string += '}';
                    return string;
                }
            },
            saveable: {
                "get": function() {
                    var obj = {};
                    if ( typeof(_url)  !== 'undefined' && typeof(_url)  !== 'null' && _url !== '') obj.url = _url;
                    if ( typeof(_nick) !== 'undefined' && typeof(_nick)  !== 'null' && _nick !== '') obj.nick = _nick;
                    if ( typeof(_time) !== 'undefined' && typeof(_time)  !== 'null' ) obj.time = _time;
                    return obj;
                }
            },
	        "id": {
	        	"get": function() {
	        		return _id;
	        	}
	        }
	    });
		
		for (var key in attr) {
        //	if (key === 'animation') {
        //		animation=attr[key];
        //	} else {
        		this[key]=attr[key];
        //	}
        }
		
		this.addChangeEvent('animation modified','nick', function() {
			animation.callChangeEvent('animation-modify');
		});
		this.addChangeEvent('animation modified','url', function() {
			animation.callChangeEvent('animation-modify');
		});
		this.addChangeEvent('animation modified','time', function() {
			animation.callChangeEvent('animation-modify');
		});
		return this;
	};
	this.animationTemplate = function(attrib){
		/*addFrame: function(frame,showTime) {
		},*/
		/*fixFrameIndex: function() {
			if (animation.frameIndex >= this.frames.length) animation.frameIndex = 0;
		},*/
		animator.animationTemplate.count = (animator.animationTemplate.count || 0) + 1;
		var _id = animator.animationTemplate.count;
		var thisAnimation=this;
		this.currentFrame = function() {
			if (animator.frameIndex >= this.frames.length) animator.frameIndex = 0;
			return  this.frames[animator.frameIndex];
		};
		this.setFrameIndex = function(n) {
			animator.frameIndex = n;
			if (animator.frameIndex >= this.frames.length) animator.frameIndex = 0;
			if (animator.frameIndex < 0) animator.frameIndex = this.frames.length - 1;
            if (this.frames.length === 0) return;
			return  this.frames[animator.frameIndex];
		};
		this.nextFrame = function(n=1) {
			animator.frameIndex += n;
			if (animator.frameIndex >= this.frames.length) animator.frameIndex = 0;
            if (this.frames.length === 0) return;
			return this.frames[animator.frameIndex];
		};
		this.prevFrame = function(n=1) {
			animator.frameIndex -= n;
			if (animator.frameIndex < 0) animator.frameIndex = this.frames.length - 1;
            if (this.frames.length === 0) return;
			return  this.frames[animator.frameIndex];
		};
		this.currentNick = function() {
			var frame = this.currentFrame();
			return (frame && 'nick' in frame) ? frame.nick : animator.site.getNick();
		},
		this.currentSkin = function() {
			var frame = this.currentFrame();
			return (frame && 'url' in frame) ? frame.url : animator.site.getSkin();
		};
		this.currentDisplayTime = function() {
			var frame = this.currentFrame();
			if (frame && 'time' in frame) {
				return Math.floor(frame.time / animator.speedMultiplier + 1);
			} else {
				return Math.floor( this.defaultDisplayTime / animator.speedMultiplier + 1);
			}
		};
		this.initialize = function() {   };
		this.clone = function() {
			var newAnimation = Object.assign({},this);
			newAnimation.frames=[];
			return newAnimation;
		};
		var _defaultDisplayTime=null;
		var _title = "";
	    Object.defineProperties(this, {
	        "title": {
	             "get": function() {
	            	 return _title;
	             },
	             "set": function(val) {
	            	 _title=val;
	            	 this.callChangeEvent('title');
	                 //return _title;
	             }
	        },
	        "defaultDisplayTime": {
	             "get": function() {
	            	 if (isNumeric(_defaultDisplayTime) && _defaultDisplayTime > 0) {
	            		return _defaultDisplayTime;
	            	 } else {
	            		return animator.defaultDisplayTime;
	            	 }
	             },
	             "set": function(val) {
	            	 _defaultDisplayTime=val;
	            	 this.callChangeEvent('defaultDisplayTime');
	                 //return _title;
	             }
	        },
            /*stringify: {
                "get": function() {
                    var string="{";
                    if ( typeof(_title)  !== 'undefined' && typeof(_title)  !== 'null' && _title !== '') string += '"title": ' + JSON.stringify(_title);
                    if ( typeof(_defaultDisplayTime) !== 'undefined' && typeof(_defaultDisplayTime)  !== 'null' ) string += ', "defaultDisplayTime": ' + JSON.stringify(_defaultDisplayTime);
                    string += ', "frames": [';
                    for (var idx=0; idx < this.frames.length; idx++) {
                        var frame = this.frames[idx];
                        string += frame.stringify;
                        string += ',';
                    }
                    string += ']';
                    if ( typeof(_time) !== 'undefined' && typeof(_time)  !== 'null' ) string += ', "time": ' + JSON.stringify(_time);
                    string += '}';
                    return string;
                }
            },*/
            saveable: {
                "get": function() {
                    var obj={};
                    if ( typeof(_title)  !== 'undefined' && typeof(_title)  !== 'null' && _title !== '') obj.title = _title;
                    if ( typeof(_defaultDisplayTime) !== 'undefined' && typeof(_defaultDisplayTime)  !== 'null' ) obj.defaultDisplayTime = _defaultDisplayTime;
                    obj.frames=[];
                    for (var idx=0; idx < this.frames.length; idx++) {
                        obj.frames.push(this.frames[idx].saveable);
                    }
                    //if ( typeof(_frameIdx) !== 'undefined' && typeof(_frameIdx)  !== 'null' ) obj.frameIdx = _frameIdx;
                    return obj;
                },
                "set": function(obj) {
                	this.removeAllFrames();
                	var title=undefined;
                	var defaultDisplayTime=undefined;
                    for (var key in obj) {
                    	if (key === 'frames') {
                    		 for (var idx=0; idx < obj.frames.length; idx++) {
                    			 this.addFrame(obj.frames[idx]);
                                 //this.frames[idx]=obj.frames[idx];
                             }
                    	}
                    	else if (key === 'title') title = obj[key];
                    	else if (key === 'defaultDisplayTime') defaultDisplayTime = obj[key];
                    }
                    this.title=title;
                    this.defaultDisplayTime=defaultDisplayTime;
                    //if ( typeof(_frameIdx) !== 'undefined' && typeof(_frameIdx)  !== 'null' ) obj.frameIdx = _frameIdx;
                    return obj;
                }
            },
	        "id": {
	        	"get": function() {
	        		return _id;
	        	}
	        }
	    });
        var changeEvents={};
        this.addChangeEvent = function(id,prop,func) {
    		//var id=changeEventCount++;
        	if (!(prop in changeEvents)) changeEvents[prop]={};
        	changeEvents[prop][id]=func;
            return id;
        };
        this.removeChangeEvent = function(prop,id) {
            //console.log("Setter called me");
        	if (!(prop in changeEvents) || !(id in changeEvents[prop]) ) return;
            delete changeEvents[prop][id];
        };
        this.callChangeEvent = function(prop) {
        	if (!(prop in changeEvents) ) return;
            //Check here since we may add more call events in out called functions
            var stopon=changeEvents[prop].length;
        	for (var id in changeEvents[prop]) {
        		if (!(changeEvents[prop][id])) continue;
        		changeEvents[prop][id](this,prop);
        	}
        };
        
		this.frames = [
			/*{
				nick: null,
				time: 0,
				url: "",
			}*/
		];
		this.framesById = [
			/*{
				nick: null,
				time: 0,
				url: "",
			}*/
		];
		this.addFrame = function(attr) {
			var frame = new animator.Frame(this,attr);
			this.frames.push(frame);
			this.framesById[frame.id]=frame;
			animator.framesById[frame.id]=frame;
			this.callChangeEvent('frame-add');
		};
		this.importFromSkinList = function(skinList,attributes={}) {
			//var animation = this.addAnimation(attributes);
			for (var i = 0; i < skinList.length; i++) {
				this.addFrame({url: skinList[i]});
			}
			return this;
		};
		this.moveFrame = function(source,dest) {
			ArrayMove(this.frames,source,dest);
			this.callChangeEvent('frame-order');
		};
		this.removeFrame = function(frame) {
			var idx = this.frames.indexOf(frame);
			while (idx >= 0) {
				this.frames.splice(idx,1);
				idx = this.frames.indexOf(frame);
			}
			delete this.framesById[frame.id];
			delete animator.framesById[frame.id];
			this.callChangeEvent('frame-remove');
		};
		this.removeAllFrames = function() {
			for (var frameId in this.framesById) {
				this.removeFrameById(frameId);
			}
		};
		this.removeFrameById = function(id) {
			this.removeFrame(this.framesById[id]);
		};
		this.removeFrameByIndex = function(idx) {
			var frame = this.frames[idx];
			this.frames.splice(idx,1);
			if (this.frames.indexOf(frame) === -1) {
				delete this.framesById[frame.id];
				delete animator.framesById[frame.id];
			}
			this.callChangeEvent('frame-remove');
		};
		this.addFrames = function() {
			for (var i=0; i < arguments.length; i++) {
				this.addFrame(arguments[i]);
			}
		};
		this.addChangeEvent('animation modified','frame-add', function() {
			thisAnimation.callChangeEvent('animation-modify');
		});
		this.addChangeEvent('animation modified','frame-remove', function() {
			thisAnimation.callChangeEvent('animation-modify');
		});
		this.addChangeEvent('animation modified','frame-order', function() {
			thisAnimation.callChangeEvent('animation-modify');
		});
		this.addChangeEvent('animation modified','title', function() {
			thisAnimation.callChangeEvent('animation-modify');
		});
		this.addChangeEvent('animation modified','defaultDisplayTime', function() {
			thisAnimation.callChangeEvent('animation-modify');
		});
		this.addChangeEvent('animation modified','animation-modify', function() {
			animator.callChangeEvent('autoSave');
		});
        for (var key in attrib) {
        	if (key === 'frames') {
        		for (var i=0; i<attrib.frames.length; i++) {
        			this.addFrame(attrib.frames[i]);
        		}
        	} else {
        		this[key]=attrib[key];
        	}
        }
	};
	
	//var AnimationContainer = {

	Object.defineProperties(this,{
		"frameIndex": {
			get: function() {
				return _frameIndex;
			},
			set: function(val) {
				_frameIndex=val;
				return this.frameIndex;
			},
		},
		speedMultiplier: {
			get: function() {
				return _speedMultiplier;
			},
			set: function(val) {
				_speedMultiplier=val;
				this.callChangeEvent('speedmultiplier');
				return this.speedMultiplier;
			},
		},
		animationIndex: {
			get: function() {
				return _animationIndex;
			},
			set: function(val) {
				_animationIndex = val;
				this.callChangeEvent('animation-select');
				return _animationIndex;
			},
		},
		isPlaying: {
			get: function() {
				return _isPlaying;
			},
			set: function(val) {
				_isPlaying=val;
				this.callChangeEvent((_isPlaying) ? 'play' : 'pause');
				return _isPlaying;
			},
		},
		status: {
			get: function() {
				return _status;
			},
			set: function(val) {
				_status=val;
				this.callChangeEvent('status');
				return _status;
			},
		},
        saveable: {
            get: function() {
                var obj={};
                //obj.defaultAnimationTime=this.defaultAnimationTime;
                obj.animations=[];
                for (var idx=0; idx < this.animations.length; idx++) {
                    obj.animations.push(this.animations[idx].saveable);
                }
                return obj;
            },
            set: function(obj) {
                //obj.defaultAnimationTime=this.defaultAnimationTime;
                //this.addAnimations(obj.animations);
            	for (var animationId in this.animationsById) {
            		this.removeAnimation(this.animationsById[animationId]);
            	}
                if (!('animations' in obj) ) {
                    console.log("Toradorable Animator requires a animations property to restore from object.");
                    return;
                }
                for (var idx=0; idx<obj.animations.length; idx++) {
                    this.addAnimation(obj.animations[idx]);
                }
                return this;
            }
        },
		isInitialized: {
			get: function() {
				return _isInitialized;
			},
		},
	});
    
    this.save = function(location='ToradorableAnimation') {
        GM_setValue(location, JSON.stringify(this.saveable) );
    };
    
    this.restore = function(location='ToradorableAnimation') {
        this.saveable=JSON.parse(GM_getValue(location, '{}' ));
    };
    
    //this.addChangeEvent('animation modified','autoSave', function() {
	//	if (animator.autoSave) animator.save();
	//});
	for (var key in initArgs) {
		this[key] = initArgs[key];
	}
	if (this.autoRestore) {
		this.restore();
	}
    return animator;
}


//var animator = (typeof animator === 'object') ? animator :  new ToradorableAnimator();