User:Rich Smith/iglooTest.js

Source: Wikipedia, the free encyclopedia.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// INCLUDES
function iglooViewable () {


};
_iglooViewable = new iglooViewable();



// iglooMain development copy
	// Alex Barley
	// base code test only
	
	// expected jQuery 1.7.*, jin 1.04a+, Mediawiki 1.19

/*
	CLASSES ==========================
	*/
	
// Class iglooUserSettings
	/*
	** iglooUserSettings is the class that holds the settings
	** for a particular user. The settings for a session can
	** be stored in JSON format for a particular user and then
	** parsed into the program to provide saving and loading.
	**
	** If no settings are loaded, the defauls specified in the
	** class itself will simply apply.
	**
	** It is written here in simplified object form to ensure
	** it can be parsed as expected.
	*/
var iglooConfiguration = {
	api: mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php',

	defaultContentScore: 20
}

var iglooUserSettings = {
	// Modules
	
	// Ticker
	
	// Requests
	limitRequests: 5,

	// Misc
	maxContentSize: 50
};

function getp (obj) {
	if (Object.getPrototypeOf) {
		return Object.getPrototypeOf(obj);
	} else if (obj.__proto__) {
		return obj.__proto__;
	} else return false;
}

function iglooQueue () {
	var internal = [];

	this.push = function (item) {
		internal.push(item);
		return this;
	}

	this.pop = function () {
		if (typeof arguments[0] === 'number') {
			var n = arguments[0];
			if (n > internal.length) n = internal.length;
			return internal.splice(0, n);
		} else
			return internal.shift();
	}

	this.get = function () {
		if (internal[0]) return internal[0];
			else return false;
	}
}




// Class iglooMain
	/*
	** iglooMain is the running class for igloo. It handles:
	** - Building the core interface and starting daemons
	** - Loading external modules
	** - Hooking modules into the correct place
	*/
function iglooMain () {
	var me = this;
	
	// Define state
	this.canvas = null; // igloo exposes its primary canvas to modules for use.
	this.toolPane = null; // igloo exposes its primary toolpane to modules for use.
	this.content = null; // igloo exposes the content panel for convenience.
	this.diffContainer = null; // igloo exposes the diff container for convenience.
	this.ticker = null; // igloo exposes its ticker panel for convenience.

	this.currentView = null;
	
	this.modules = {};

	this.launch = function () {
		if (mw.config.get('wgPageName') !== 'User:Ale_jrb/igDev') return;
		this.loadModules();
		this.buildInterface();

		this.currentView = new iglooView();
		this.recentChanges = new iglooRecentChanges();
		this.contentManager = new iglooContentManager();
		this.recentChanges.setTickTime(2000);
	};

	this.buildInterface = function () {
		try {
			// Create drawing canvas.
			this.canvas = new jin.Canvas();
			this.canvas.setFullScreen(true);
			
			// Create base splitter.
			var mainPanel = new jin.SplitterPanel();
			mainPanel.setPosition(0, 0);
			mainPanel.setSize(0, 0);
			mainPanel.setInitialDrag(260);
			mainPanel.setColour(jin.Colour.DARK_GREY);
			
			mainPanel.left.setColour(jin.Colour.LIGHT_GREY);
			mainPanel.right.setColour(jin.Colour.LIGHT_GREY);
			
			// Expose recent changes panel.
			this.ticker = mainPanel.left;
			
			// Create toolbar pane.
			this.toolPane = new jin.Panel();
			this.toolPane.setPosition(0, 0);
			this.toolPane.setSize(0, 100);
			this.toolPane.setColour(jin.Colour.GREY);
			
			// Create toolbar border.
			var toolBorder = new jin.Panel();
			toolBorder.setPosition(0, 100);
			toolBorder.setSize(0, 1);
			toolBorder.setColour(jin.Colour.DARK_GREY);
			
			// Create content panel.
			this.content = new jin.Panel();
			this.content.setPosition(0, 101);
			this.content.setSize(0, 0);
			this.content.setColour(jin.Colour.WHITE);
			
			// Create diff container.
			this.diffContainer = new jin.Panel();
			this.diffContainer.setPosition(0, 0);
			this.diffContainer.setSize(0, 0);
			this.diffContainer.setColour(jin.Colour.WHITE);
			
			// Combine interface elements.
			this.content.add(this.diffContainer);
			mainPanel.right.add(this.toolPane);
			mainPanel.right.add(toolBorder);
			mainPanel.right.add(this.content);
			this.canvas.add(mainPanel);
			
			// Do initial render.
			this.canvas.render(jin.getDocument());

			this.fireEvent('core','interface-rendered', true);
		} catch (e) {
			jin.handleException(e);
		}
	};


	/*
		UI ======================
		*/
	this.getCurrentView = function () {
		return this.currentView;
	};


	/*
		EVENTS ==================
		*/
	this.announce = function (moduleName) {
		if (!this.modules[moduleName]) this.modules[moduleName] = {};
		this.modules[moduleName]['exists'] = true;
		this.modules[moduleName]['ready'] = true;
	};

	this.isModuleReady = function (moduleName) {
		if (!this.modules[moduleName]) return false;
		return this.modules[moduleName]['ready'];
	};

	this.hookEvent = function (moduleName, hookName, func) {
		if (hookName === 'exists' || hookName === 'ready') return 1;

		if (!this.modules[moduleName]) { 
			this.modules[moduleName] = {};
			this.modules[moduleName]['exists'] = true;
			this.modules[moduleName]['ready'] = false; 
		}

		if (!this.modules[moduleName][hookName]) {
			this.modules[moduleName][hookName] = [func];
		} else {
			this.modules[moduleName][hookName].push(func);
		}

		return 0;
	};

	this.unhookEvent = function (moduleName, hookName, func) {
		if (this.modules[moduleName]) {
			if (this.modules[moduleName][hookName]) {
				for (i = 0; i < this.modules[moduleName][hookName].length; i++) {
					if (this.modules[moduleName][hookName][i] === func)
						this.modules[moduleName][hookName][i] = null;
				}
			}
		}
	};

	this.fireEvent = function (moduleName, hookName, data) {
		if (this.modules[moduleName]) {
			if (this.modules[moduleName][hookName]) {
				for (i = 0; i < this.modules[moduleName][hookName].length; i++) {
					if (this.modules[moduleName][hookName][i] !== null)
						this.modules[moduleName][hookName][i](data);
				}
			}
		}
	};

	this.loadModules = function () {
		// do nothing

		this.fireEvent('core', 'modules-loaded', true);
	};
};



// Class iglooContentManager
	/*
	** iglooContentManager keeps track of iglooPage items
	** that are loaded by the recent changes ticker or at
	** the user request. Because igloo cannot store all
	** changes for the duration of the program, it must
	** decide when to discard the page item to save memory.
	** The content manager uses a relevance score to track
	** items. This score is created when the manager first
	** sees the page and decreases when the content manager
	** sees activity. When an item reaches 0, it is open
	** to be discarded. If an item sees further actions, its
	** score can be refreshed, preventing it from being
	** discarded for longer.
	*/
function iglooContentManager () {
	this.contentSize = 0;
	this.discardable = 0;
	this.content = {};

	this.add = function (page) {
		this.decrementScores();
		this.contentSize++;
		this.content[page.info.pageTitle] = {
			exists: true,
			page: page,
			hold: true,
			timeAdded: new Date(),
			timeTouched: new Date(),
			score: iglooConfiguration.defaultContentScore
		}

		console.log("IGLOO: Added a page to the content manager. Size: " + this.contentSize);
		this.gc();

		return this.content[page.info.pageTitle];
	}

	this.getPage = function (title) {
		if (this.content[title]) {
			return this.content[title].page;
		} else {
			return false;
		}
	}

	this.decrementScores = function () {
		var s = "IGLOO: CSCORE: ";
		for (i in this.content) {
			if (this.content[i].score > 0) {
				s += this.content[i].score + ", ";
				if (--this.content[i].score === 0) {
					console.log("IGLOO: an item reached a score of 0 and is ready for discard!");
					this.discardable++;
				}
			}
		}
		console.log(s);
	}

	this.gc = function () {
		console.log("IGLOO: Running GC");
		if (this.discardable === 0) return;
		if (this.contentSize > iglooUserSettings.maxContentSize) {
			console.log("IGLOO: GC removing items to fit limit (" + this.contentSize + "/" + iglooUserSettings.maxContentSize + ")")
			var j = 0, lastZeroScore = null, gcVal = 0.3, gcStep = 0.05;
			for (i in this.content) {
				if (this.content[i].score !== 0 || this.content[i].isRecent !== false || this.content[i].page.displaying !== false) {
					j++;
					gcVal += gcStep;
					continue;
				} else {
					lastZeroScore = i;
				}

				if (j === this.contentSize - 1) {
					if (lastZeroScore !== null) {
						console.log("IGLOO: failed to randomly select item, discarding the last one seen");
						this.content[lastZeroScore] = undefined;
						this.contentSize--;
						this.discardable--;
						break;
					}
				}

				if (this.content[i].score === 0 
					&& this.content[i].isRecent === false 
					&& Math.random() < gcVal
					&& this.content[i].page.displaying === false) {

					console.log("IGLOO: selected an item suitable for discard, discarding");
					this.content[i] = undefined;
					this.contentSize--;
					this.discardable--;
					break;
				} else {
					j++;
					gcVal += gcStep;
				}
			}
		}
	}
}




// Class iglooRecentChanges
	/*
	** iglooRecentChanges is the ticker class for igloo.
	** With no modules loaded, igloo simply acts as a recent
	** changes viewer. This class maintains the list of 
	** iglooPage elements that represent wiki pages that have
	** recently changed. Each pages contains many diffs. Once
	** created, this class will tick in the background and
	** update itself. It can be queried and then rendered at
	** any point.
	*/
function iglooRecentChanges () {
	var me = this;
	
	console.log ( 'IGLOO: generated RC ticker' );
	
	this.tick = null;
	this.loadUrl = iglooConfiguration.api;
	this.tickTime = 4000;
	this.recentChanges = [];

	// Methods
	this.setTickTime = function (newTime) {
		this.tickTime = newTime;
		clearInterval(this.tick);
		this.tick = setInterval(function () { me.update.apply(me); }, this.tickTime);
	};
	
	// Constructor
	this.renderResult = document.createElement('ul'); // this is the output panel
	$(this.renderResult).css({
		'position': 'absolute',
		'top': '0px',
		'left': '0px',
		'padding': '0px',
		'margin': '0px',
		'width': '100%',
		'height': '100%',
		'list-style': 'none inherit none',
		'overflow': 'auto'
	});
	$(me.renderResult).on ({
		mouseover: function () { $(this).css('backgroundColor', '#999999'); },
		mouseout: function () { $(this).css('backgroundColor', jin.Colour.LIGHT_GREY); },
		click: function () { me.show.apply(me, [$(this).data('elId')]) ; }
	}, 'li');
	igloo.ticker.panel.appendChild(this.renderResult);
	
};

iglooRecentChanges.prototype.update = function () {
	var me = this;
	(new iglooRequest({
		url: me.loadUrl,
		data: { format: 'json', action: 'query', list: 'recentchanges' },
		dataType: 'json',
		context: me,
		success: function (data) {
			me.loadChanges.apply(me, [data]);
		}
	}, 0, false)).run();
};

iglooRecentChanges.prototype.loadChanges = function (changeSet) {
	data = changeSet.query.recentchanges;
	
	// For each change, add it to the changeset.
	var l = data.length;
	for (var i = 0; i < l; i++) {
		
		// Check if we already have information about this page.
		var l2 = this.recentChanges.length, exists = false;
		for (var j = 0; j < l2; j++) {
			if (data[i].title === this.recentChanges[j]) {
				var p = igloo.contentManager.getPage(data[i].title);
				p.page.addRevision(new iglooRevision(data[i]));
				p.hold = true;
				exists = true;
				break;
			}
		}
		if (!exists) {
			var p = new iglooPage(new iglooRevision(data[i]));
			igloo.contentManager.add(p);
			this.recentChanges.push(p);
		}
	}
	this.recentChanges.sort(function (a, b) { return b.lastRevision - a.lastRevision; });
	
	// Truncate the recent changes list to the correct length
	if (this.recentChanges.length > 30) {
		// Objects that are being removed from the recent changes list are freed in the
		// content manager for discard.
		for (var i = 30; i < this.recentChanges.length; i++) {
			console.log("IGLOO: Status change. " + this.recentChanges[i].title + " is no longer hold")
			var p = igloo.contentManager.getPage(this.recentChanges[i].title);
			p.hold = false;
		}
		this.recentChanges = this.recentChanges.slice(0, 30);
	}
	
	// Render the result
	this.render();
};

// ask a diff to show its changes
iglooRecentChanges.prototype.show = function (elementId) {
	this.recentChanges[elementId].display();
	
	return this;
};

iglooRecentChanges.prototype.render = function () {
	this.renderResult.innerHTML = '';
	for (var i = 0; i < this.recentChanges.length; i++) {
		// Create each element
		var t = document.createElement('li');
		
		// Styling
		$(t).css ({
			'padding': '0px',
			'borderBottom': '1px solid #000000',
			'list-style-type': 'none',
			'list-style-image': 'none',
			'marker-offset': '0px',
			'margin': '0px'
		});
		
		// Finish
		if (this.recentChanges[i].isNewPage) {
			t.innerHTML = "<strong>N</strong> " + this.recentChanges[i].pageTitle;
		} else {
			t.innerHTML = this.recentChanges[i].pageTitle;
		}
		$(t).data("elId", i);
		this.renderResult.appendChild(t);
	}
	console.log("Rendered " + i + " recent changes.");
	
	return this;
};



// Class iglooView
	// iglooView represents a content view. There could be
	// multiple views, each showing their own bit of content.
	// iglooView can support viewing anything that inherits
	// from iglooViewable.

function iglooView () {
	var me = this;

	// State
	this.displaying = null;
	this.changedSinceDisplay = false;

	// Hook to relevant events
	igloo.hookEvent('core', 'displayed-page-changed', function (data) {
		if (me.displaying) {
			if (data.page === me.displaying.page) {
				this.changedSinceDisplay = true;
				this.displaying = data;
				this.displaying.show();
			}
		}
	});
};

iglooView.prototype.display = function (revision) {
	// If a revision is being displayed, set the displaying
	// flag for the page to false.
	if (this.displaying) {
		this.displaying.page.displaying = false;
		this.displaying.page.changedSinceDisplay = false;
	}

	// Set the new revision into the page, then show it.
	this.displaying = revision;
	this.displaying.show();
}




// Class iglooPage

function iglooPage () {
	var me = this;
	
	// Details
	this.info = {
		pageTitle: '',
		namespace: 0
	}
	this.lastRevision = 0;
	this.revisions = [];
	
	// State
	this.displaying = false; // currently displaying
	this.changedSinceDisplay = false; // the data of this page has changed since it was first displayed
	this.isNewPage = false; // whether this page currently only contains the page creation
	this.isRecent = false;
	
	// Methods
	
	// Revisions can be added to a page either by a history lookup, or 
	// by the recent changes ticker. The 'diff' attached to a revision
	// is always the diff of this revision with the previous one, though
	// other diffs can be loaded as requested (as can the particular 
	// content at any particular revision).
	
	// Constructor
	if (arguments[0]) {
		this.pageTitle = arguments[0].page;
		this.addRevision(arguments[0]);
	}
};

iglooPage.prototype.addRevision = function (newRev) {
	// Check if this is a duplicate revision.
	for (var i = 0; i < this.revisions; i++) {
		if (newRev.revId === this.revisions[i].revId) return;
	}

	if (this.isNewPage) {
		this.isNewPage = false;
	} else if (newRev.type === 'new') {
		this.isNewPage = true;
	}

	newRev.page = this;
	this.revisions.push(newRev);
	this.revisions.sort(function (a, b) { return a.revId - b.revId; });
	
	if (newRev.revId > this.lastRevision) this.lastRevision = newRev.revId;
	if (this.displaying) {
		alert('update');
		igloo.fireEvent('core', 'displayed-page-changed', newRev);
		this.changedSinceDisplay = true;
	}
};

iglooPage.prototype.display = function () {
	// Calling display on a page will invoke the display
	// method for the current view, and pass it the relevant
	// revision object.
	var currentView = igloo.getCurrentView();

	if (arguments[0]) {
		if (this.revisions[arguments[0]]) {
			currentView.display(this.revisions[arguments[0]]);
		} else {
			currentView.display(this.revisions.iglast());
		}
	} else {
		currentView.display(this.revisions.iglast());
	}
	this.displaying = true;
	this.changedSinceDisplay = false;
};



// Class iglooRevision
	/*
	** iglooRevision represents a revision and associated diff
	** on the wiki. It may simply represent the metadata of a
	** change, or it may represent the change in full.
	*/
function iglooRevision () {
	var me = this;
	
	// Content detail
	this.user = ''; // the user who made this revision
	this.page = ''; // the page title that this revision belongs to
	this.namespace = 0;
	this.revId = 0; // the ID of this revision (the diff is between this and oldId)
	this.oldId = 0; // the ID of the revision from which this was created
	this.type = 'edit';
	
	this.revisionContent = ''; // the content of the revision
	this.diffContent = ''; // the HTML content of the diff
	this.revisionRequest = null; // the content request for this revision.
	this.diffRequest = null; // the diff request for this revision
	this.revisionLoaded = false; // there is content stored for this revision
	this.diffLoaded = false; // there is content stored for this diff
	
	this.displayRequest = false; // diff should be displayed when its content next changes
	this.page = null; // the iglooPage object to which this revision belongs
	
	// Constructor
	if (arguments[0]) {
		this.setMetaData(arguments[0]);
	}
};

iglooRevision.prototype.setMetaData = function (newData) {
	this.user = newData.user;
	this.page = newData.title;
	this.namespace = newData.ns;
	this.oldId = newData.old_revid;
	this.revId = newData.revid;
	this.type = newData.type;
};

iglooRevision.prototype.loadRevision = function (newData) {
	var me = this;

	if (this.revisionRequest === null) {
		this.revisionRequest = new iglooRequest({
			url: iglooConfiguration.api,
			data: { format: 'json', action: 'query', prop: 'revisions', revids: '' + me.revId, rvprop: 'content', rvparse: 'true' },
			dataType: 'json',
			context: me,
			success: function (data) {
				for (i in data.query.pages) {
					this.revisionContent = data.query.pages[i].revisions[0]['*'];
				}
				this.revisionLoaded = true;
				if (this.displayRequest === 'revision') this.display('revision');
				this.revisionRequest = null;
			}
		}, 0, true);
		this.revisionRequest.run();
	}
};

iglooRevision.prototype.loadDiff = function () {
	var me = this;

	if (this.diffRequest === null) {
		console.log('Attempted to show a diff, but we had no data so has to load it.')
		this.diffRequest = new iglooRequest({
			url: iglooConfiguration.api,
			data: { format: 'json', action: 'compare', fromrev: '' + me.oldId, torev: '' + me.revId },
			dataType: 'json',
			context: me,
			success: function (data) {
				this.diffContent = data.compare['*'];
				this.diffLoaded = true;
				if (this.displayRequest === 'diff') this.display('diff');
				this.diffRequest = null;
			}
		}, 0, true);
		this.diffRequest.run();
	}
};

iglooRevision.prototype.display = function () {
	// Determine what should be displayed.
	if (!arguments[0]) {
		var displayWhat = 'diff';
	} else {
		var displayWhat = arguments[0];
	}

	// If this was fired as a result of a display request, clear the flag.
	if (this.displayRequest) this.displayRequest = false;
	
	// Mark as displaying, and fire the displaying event.
	this.displaying = true;
	igloo.fireEvent('core', 'displaying-change', this);
	
	// Create display element.
	if (displayWhat === 'revision' || this.type === 'new') {
		var div = document.createElement('div');
		div.innerHTML = this.revisionContent;
		
		// Style display element.
		$(div).find('a').each(function () {
			$(this).prop('target', '_blank');
		});
		
		// Clear current display.
		$(igloo.diffContainer.panel).find('*').remove();
		
		// Append new content.
		igloo.diffContainer.panel.appendChild(div);
	} else if (displayWhat === 'diff') {
		var table = document.createElement('table');

		table.innerHTML = '<tr><td id="iglooDiffCol1" colspan="2"> </td><td id="iglooDiffCol2" colspan="2"> </td></tr>' + this.diffContent;
		
		// Style display element.
		// TODO
		$(table).css({ 'width': '100%', 'overflow': 'auto' });
		$(table).find('#iglooDiffCol1').css({ 'width': '50%' });
		$(table).find('#iglooDiffCol2').css({ 'width': '50%' });

		$(table).find('.diff-empty').css('');
		$(table).find('.diff-addedline').css({ 'background-color': '#ccffcc' });
		$(table).find('.diff-marker').css({ 'text-align': 'right' });
		$(table).find('.diff-lineno').css({ 'font-weight': 'bold' });
		$(table).find('.diff-deletedline').css({ 'background-color': '#ffffaa' });
		$(table).find('.diff-context').css({ 'background-color': '#eeeeee' });
		$(table).find('.diffchange').css({ 'color': 'red' });
		
		// Clear current display.
		$(igloo.diffContainer.panel).find('*').remove();
		
		// Append new content.
		igloo.diffContainer.panel.appendChild(table);
	}
};

iglooRevision.prototype.show = function () {

	// Determine what to show.
	if (!arguments[0]) {
		var displayWhat = 'diff';
	} else {
		var displayWhat = arguments[0];
	}

	if (displayWhat === 'diff' && this.type === 'edit') {
		console.log('IGLOO: diff display requested, page: ' + this.page.pageTitle);
		
		if ((!this.diffLoaded) && (!this.diffRequest)) {
			this.displayRequest = 'diff';
			this.loadDiff();
		} else {
			this.display('diff');
		}
	} else {
		console.log('IGLOO: revision display requested, page: ' + this.page.pageTitle);
		
		if ((!this.revisionLoaded) && (!this.revisionRequest)) {
			this.displayRequest = 'revision';
			this.loadRevision();
		} else {
			this.display('revision');
		}
	}
};




function iglooRequest (request, priority, important) {
	var me = this;
	
	// Statics
	getp(this).requests = [];
	getp(this).queuedRequests = 0;
	getp(this).runningRequests = 0;

	// Constructor
	this.request = request;
	this.priority = priority;
	this.important = important;
	this.requestItem = null;
};

iglooRequest.prototype.run = function () {
	var me = this;

	if (this.important === true) {
		// If important, execute immediately.
		this.requestItem = $.ajax(this.request);
		return this.requestItem;
	} else {
		// If not important, attach our callback to its complete function.
		if (this.request.complete) {
			var f = this.request['complete'];
			this.request['complete'] = function (data) { me.callback(); f(data); };
		} else {
			this.request['complete'] = function (data) { me.callback(); };
		}
		
		// If we have enough requests, just run, otherwise hold.
		if (getp(this).runningRequests >= iglooUserSettings.limitRequests) {
			console.log('IGLOO: queuing a request because ' + getp(this).runningRequests + '/' + iglooUserSettings.limitRequests + ' are running');
			
			getp(this).requests.push(this.request);
			getp(this).requests.sort(function (a, b) { return a.priority - b.priority; });
			
			if (getp(this).queuedRequests > 20) {
				console.log('IGLOO: pruned an old request because the queue contains 20 items');
				getp(this).requests = getp(this).requests.slice(1);
			} else {
				getp(this).queuedRequests++;
			}
		} else {
			console.log ( 'IGLOO: running a request because ' + getp(this).runningRequests + '/' + iglooUserSettings.limitRequests + ' are running' );
			getp(this).runningRequests++;
			this.requestItem = $.ajax(this.request);
			return this.requestItem;
		}
	}
};

iglooRequest.prototype.abort = function () {
	if (this.requestItem !== null) {
		this.requestItem.abort();
		this.requestItem = null;
	} else {
		this.requestItem = null;
	}
};

iglooRequest.prototype.callback = function () {
	getp(this).runningRequests--;
	
	if (getp(this).queuedRequests > 0) {
		console.log('IGLOO: non-important request completed, running another request, remaining: ' + getp(this).queuedRequests);
		
		var request = null;
		while (request === null) {
			request = getp(this).requests.pop();
			getp(this).queuedRequests--;
		}

		if (request !== undefined) {
			getp(this).runningRequests++;
			$.ajax(request);
		}
	} else {		
		console.log ( 'IGLOO: non-important request completed, but none remain queued to run' );
	}
};




/*
	COMPLETE ==========================
	*/
// MAIN
if (!igloo)
	var igloo = new iglooMain();
	
if (typeof jin === 'undefined') {
	tIgLa = function () {
		if (typeof jin === 'undefined') {
			setTimeout(tIgLa, 1000);
		} else {
			igloo.launch();
		}
	}
	setTimeout(tIgLa, 1000);
} else {
	igloo.launch();
}

Array.prototype.iglast = function () {
	return this[this.length - 1];
}

igloo.announce('core');