diff --git a/bower.json b/bower.json index b42d72ab..24aab1e1 100644 --- a/bower.json +++ b/bower.json @@ -28,6 +28,7 @@ "format-io": "~0.9.6", "rendy": "~1.1.0", "github": "~0.10.6", - "vk-openapi": "~0.0.1" + "vk-openapi": "~0.0.1", + "html5-polyfills": "*" } } diff --git a/lib/client.js b/lib/client.js index a82aa104..2caf9596 100644 --- a/lib/client.js +++ b/lib/client.js @@ -161,7 +161,10 @@ var Util, DOM, CloudFunc, join; }, funcBefore = function(callback) { - var src = CloudCmd.LIBDIRCLIENT + 'polyfill.js'; + var src = prefix + '/join:' + [ + CloudCmd.LIBDIRCLIENT + 'polyfill.js', + '/modules/html5-polyfills/classList.js' + ].join(':'); DOM.loadJquery(function() { DOM.load.js(src, callback); diff --git a/lib/client/buffer.js b/lib/client/buffer.js index 9428db16..3a830755 100644 --- a/lib/client/buffer.js +++ b/lib/client/buffer.js @@ -40,7 +40,7 @@ var files = DOM.getActiveFiles(); files.forEach(function(element) { - DOM.addClass(element, CLASS); + element.classList.add(CLASS); }); } @@ -48,7 +48,7 @@ var files = DOM.getByClassAll(CLASS); [].forEach.call(files, function(element) { - DOM.removeClass(element, CLASS); + element.classList.remove(CLASS); }); } diff --git a/lib/client/dom.js b/lib/client/dom.js index d74b3fba..8ff8f9d6 100644 --- a/lib/client/dom.js +++ b/lib/client/dom.js @@ -151,43 +151,6 @@ var CloudCmd, Util, DOM, CloudFunc, Dialog; DOMTreeProto = function() { var DOM = this; - /** - * add class to element - * - * @param element - * @param pClass - */ - this.addClass = function(element, pClass) { - var ret = element && pClass; - - if (ret) - element.classList.add(pClass); - - return this; - }; - - /** - * remove class pClass from element element - * @param element - * @param pClass - */ - this.removeClass = function(element, pClass) { - var ret = element && pClass; - - if (ret) - element.classList.remove(pClass); - - return this; - }; - - this.toggleClass = function(element, pClass) { - var ret = element && pClass; - - if (ret) - element.classList.toggle(pClass); - - return this; - }; /** * check class of element @@ -284,11 +247,13 @@ var CloudCmd, Util, DOM, CloudFunc, Dialog; * @param element */ this.hide = function(element) { - return DOM.addClass(element, 'hidden'); + element.classList.add('hidden'); + return DOM; }; this.show = function(element) { - return DOM.removeClass(element, 'hidden'); + element.classList.remove('hidden'); + return DOM; }; }, @@ -313,7 +278,7 @@ var CloudCmd, Util, DOM, CloudFunc, Dialog; var ret = DOM.isCurrentFile(currentFile); if (ret) - DOM.removeClass(currentFile, CURRENT_FILE); + currentFile.classList.remove(CURRENT_FILE); return ret; } @@ -931,7 +896,7 @@ var CloudCmd, Util, DOM, CloudFunc, Dialog; unsetCurrentFile(currentFileWas); } - this.addClass(currentFile, CURRENT_FILE); + currentFile.classList.add(CURRENT_FILE); path = DOM.getCurrentDirPath(); @@ -1003,10 +968,11 @@ var CloudCmd, Util, DOM, CloudFunc, Dialog; * @param currentFile */ this.toggleSelectedFile = function(currentFile) { - var current = currentFile || this.getCurrentFile(), - ret = this.toggleClass(current, SELECTED_FILE); + var current = currentFile || this.getCurrentFile(); + + current.classList.toggle(SELECTED_FILE); - return ret; + return Cmd; }; this.toggleAllSelectedFiles = function() { diff --git a/lib/client/polyfill.js b/lib/client/polyfill.js index b21a8c79..5cc670d2 100644 --- a/lib/client/polyfill.js +++ b/lib/client/polyfill.js @@ -155,34 +155,6 @@ var Util, DOM, jQuery; element.scrollIntoView(alignWithTop); } }; - - if (!document.body.classList) { - DOM.isContainClass = function(el, className) { - var ret = $(el).hasClass(className); - - return ret; - }; - - DOM.addClass = function(pElement, pClass) { - var lRet, - lClassName = pElement && pElement.className, - lSpaceChar = lClassName ? ' ' : ''; - - lRet = !DOM.isContainClass(pElement, pClass); - - if (lRet) - pElement.className += lSpaceChar + pClass; - - return lRet; - }; - - DOM.removeClass = function(pElement, pClass) { - var lClassName = pElement.className; - - if (lClassName.length > pClass.length) - pElement.className = lClassName.replace(pClass, ''); - }; - } if (!window.JSON) { Util.json.parse = $.parseJSON; diff --git a/lib/client/view.js b/lib/client/view.js index 1e28d8f3..8f6299a2 100644 --- a/lib/client/view.js +++ b/lib/client/view.js @@ -317,11 +317,11 @@ var CloudCmd, Util, DOM, CloudFunc, $; } function hideOverlay() { - DOM.removeClass(Overlay, 'view-overlay'); + Overlay.classList.remove('view-overlay'); } function showOverlay() { - DOM.addClass(Overlay, 'view-overlay'); + Overlay.classList.add('view-overlay'); } function listener(event) { diff --git a/modules/html5-polyfills/.bower.json b/modules/html5-polyfills/.bower.json new file mode 100644 index 00000000..3292443f --- /dev/null +++ b/modules/html5-polyfills/.bower.json @@ -0,0 +1,31 @@ +{ sdfljsdlfjsdf lsdjflsdjflsdj ------- sdfjsdlfjdsljf + "name": "html5-polyfills", sldjflsdjf lsdfjlsdjf + "description": "Collection of polyfills by Remy Sharp", + "homepage": "https://github.com/remy/polyfills", + "keywords": [ + "polyfill", + "client", + "browser" + ], + "author": { + "name": "Remy Sharp", + "email": "remy@leftlogic.com" + }, + "readme": "This is my own collection of code snippets that support different native features of browsers using JavaScript (and if required, Flash in some cases).", + "readmeFilename": "README.md", + "_id": "html5-polyfills@0.0.20130621", + "repository": { + "type": "git", + "url": "git://github.com/remy/polyfills.git" + }, + "_release": "768ce355de", + "_resolution": { + "type": "branch", + "branch": "master", + "commit": "768ce355de8f31d8d0a99aa3aba3708daa54e873" + }, + "_source": "git://github.com/remy/polyfills.git", + "_target": "*", + "_originalSource": "html5-polyfills", + "_direct": true +} \ No newline at end of file diff --git a/modules/html5-polyfills/EventSource.js b/modules/html5-polyfills/EventSource.js new file mode 100644 index 00000000..92ca621d --- /dev/null +++ b/modules/html5-polyfills/EventSource.js @@ -0,0 +1,186 @@ +;(function (global) { + +if ("EventSource" in global) return; + +var reTrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g; + +var EventSource = function (url) { + var eventsource = this, + interval = 500, // polling interval + lastEventId = null, + cache = ''; + + if (!url || typeof url != 'string') { + throw new SyntaxError('Not enough arguments'); + } + + this.URL = url; + this.readyState = this.CONNECTING; + this._pollTimer = null; + this._xhr = null; + + function pollAgain(interval) { + eventsource._pollTimer = setTimeout(function () { + poll.call(eventsource); + }, interval); + } + + function poll() { + try { // force hiding of the error message... insane? + if (eventsource.readyState == eventsource.CLOSED) return; + + // NOTE: IE7 and upwards support + var xhr = new XMLHttpRequest(); + xhr.open('GET', eventsource.URL, true); + xhr.setRequestHeader('Accept', 'text/event-stream'); + xhr.setRequestHeader('Cache-Control', 'no-cache'); + // we must make use of this on the server side if we're working with Android - because they don't trigger + // readychange until the server connection is closed + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + if (lastEventId != null) xhr.setRequestHeader('Last-Event-ID', lastEventId); + cache = ''; + + xhr.timeout = 50000; + xhr.onreadystatechange = function () { + if (this.readyState == 3 || (this.readyState == 4 && this.status == 200)) { + // on success + if (eventsource.readyState == eventsource.CONNECTING) { + eventsource.readyState = eventsource.OPEN; + eventsource.dispatchEvent('open', { type: 'open' }); + } + + var responseText = ''; + try { + responseText = this.responseText || ''; + } catch (e) {} + + // process this.responseText + var parts = responseText.substr(cache.length).split("\n"), + eventType = 'message', + data = [], + i = 0, + line = ''; + + cache = responseText; + + // TODO handle 'event' (for buffer name), retry + for (; i < parts.length; i++) { + line = parts[i].replace(reTrim, ''); + if (line.indexOf('event') == 0) { + eventType = line.replace(/event:?\s*/, ''); + } else if (line.indexOf('retry') == 0) { + retry = parseInt(line.replace(/retry:?\s*/, '')); + if(!isNaN(retry)) { interval = retry; } + } else if (line.indexOf('data') == 0) { + data.push(line.replace(/data:?\s*/, '')); + } else if (line.indexOf('id:') == 0) { + lastEventId = line.replace(/id:?\s*/, ''); + } else if (line.indexOf('id') == 0) { // this resets the id + lastEventId = null; + } else if (line == '') { + if (data.length) { + var event = new MessageEvent(data.join('\n'), eventsource.url, lastEventId); + eventsource.dispatchEvent(eventType, event); + data = []; + eventType = 'message'; + } + } + } + + if (this.readyState == 4) pollAgain(interval); + // don't need to poll again, because we're long-loading + } else if (eventsource.readyState !== eventsource.CLOSED) { + if (this.readyState == 4) { // and some other status + // dispatch error + eventsource.readyState = eventsource.CONNECTING; + eventsource.dispatchEvent('error', { type: 'error' }); + pollAgain(interval); + } else if (this.readyState == 0) { // likely aborted + pollAgain(interval); + } else { + } + } + }; + + xhr.send(); + + setTimeout(function () { + if (true || xhr.readyState == 3) xhr.abort(); + }, xhr.timeout); + + eventsource._xhr = xhr; + + } catch (e) { // in an attempt to silence the errors + eventsource.dispatchEvent('error', { type: 'error', data: e.message }); // ??? + } + }; + + poll(); // init now +}; + +EventSource.prototype = { + close: function () { + // closes the connection - disabling the polling + this.readyState = this.CLOSED; + clearInterval(this._pollTimer); + this._xhr.abort(); + }, + CONNECTING: 0, + OPEN: 1, + CLOSED: 2, + dispatchEvent: function (type, event) { + var handlers = this['_' + type + 'Handlers']; + if (handlers) { + for (var i = 0; i < handlers.length; i++) { + handlers[i].call(this, event); + } + } + + if (this['on' + type]) { + this['on' + type].call(this, event); + } + }, + addEventListener: function (type, handler) { + if (!this['_' + type + 'Handlers']) { + this['_' + type + 'Handlers'] = []; + } + + this['_' + type + 'Handlers'].push(handler); + }, + removeEventListener: function (type, handler) { + var handlers = this['_' + type + 'Handlers']; + if (!handlers) { + return; + } + for (var i = handlers.length - 1; i >= 0; --i) { + if (handlers[i] === handler) { + handlers.splice(i, 1); + break; + } + } + }, + onerror: null, + onmessage: null, + onopen: null, + readyState: 0, + URL: '' +}; + +var MessageEvent = function (data, origin, lastEventId) { + this.data = data; + this.origin = origin; + this.lastEventId = lastEventId || ''; +}; + +MessageEvent.prototype = { + data: null, + type: 'message', + lastEventId: '', + origin: '' +}; + +if ('module' in global) module.exports = EventSource; +global.EventSource = EventSource; + +})(this); diff --git a/modules/html5-polyfills/MIT-LICENCE.txt b/modules/html5-polyfills/MIT-LICENCE.txt new file mode 100644 index 00000000..aecaf1da --- /dev/null +++ b/modules/html5-polyfills/MIT-LICENCE.txt @@ -0,0 +1,21 @@ +Copyright (c) 2010 Remy Sharp, http://remysharp.com + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/modules/html5-polyfills/README.md b/modules/html5-polyfills/README.md new file mode 100644 index 00000000..2493b09d --- /dev/null +++ b/modules/html5-polyfills/README.md @@ -0,0 +1,32 @@ +# Polyfills + +> A way of getting the browser to behave and support the latest specifications. + +This is my own collection of code snippets that support different native features of browsers using JavaScript (and if required, Flash in some cases). + +## Component Installation + +You can install polyfills using component: + +``` +component install remy/polyfills +``` + +If you'd like to include all polyfills (except device motion), you can just: + +``` +require( 'polyfills' ); +``` + +If you'd only like a specific polyfill, you can require indivual ones like this: + +``` +require( 'polyfills/classList' ); + +// now we can use classList in lots of browsers! +element.classList.add( 'foo' ); +``` + +# License + +License: http://rem.mit-license.org diff --git a/modules/html5-polyfills/Storage.js b/modules/html5-polyfills/Storage.js new file mode 100644 index 00000000..f2d79e54 --- /dev/null +++ b/modules/html5-polyfills/Storage.js @@ -0,0 +1,96 @@ +if (typeof window.localStorage == 'undefined' || typeof window.sessionStorage == 'undefined') (function () { + +var Storage = function (type) { + function createCookie(name, value, days) { + var date, expires; + + if (days) { + date = new Date(); + date.setTime(date.getTime()+(days*24*60*60*1000)); + expires = "; expires="+date.toGMTString(); + } else { + expires = ""; + } + document.cookie = name+"="+value+expires+"; path=/"; + } + + function readCookie(name) { + var nameEQ = name + "=", + ca = document.cookie.split(';'), + i, c; + + for (i=0; i < ca.length; i++) { + c = ca[i]; + while (c.charAt(0)==' ') { + c = c.substring(1,c.length); + } + + if (c.indexOf(nameEQ) == 0) { + return c.substring(nameEQ.length,c.length); + } + } + return null; + } + + function setData(data) { + data = JSON.stringify(data); + if (type == 'session') { + window.name = data; + } else { + createCookie('localStorage', data, 365); + } + } + + function clearData() { + if (type == 'session') { + window.name = ''; + } else { + createCookie('localStorage', '', 365); + } + } + + function getData() { + var data = type == 'session' ? window.name : readCookie('localStorage'); + return data ? JSON.parse(data) : {}; + } + + + // initialise if there's already data + var data = getData(); + + return { + length: 0, + clear: function () { + data = {}; + this.length = 0; + clearData(); + }, + getItem: function (key) { + return data[key] === undefined ? null : data[key]; + }, + key: function (i) { + // not perfect, but works + var ctr = 0; + for (var k in data) { + if (ctr == i) return k; + else ctr++; + } + return null; + }, + removeItem: function (key) { + delete data[key]; + this.length--; + setData(data); + }, + setItem: function (key, value) { + data[key] = value+''; // forces the value to a string + this.length++; + setData(data); + } + }; +}; + +if (typeof window.localStorage == 'undefined') window.localStorage = new Storage('local'); +if (typeof window.sessionStorage == 'undefined') window.sessionStorage = new Storage('session'); + +})(); diff --git a/modules/html5-polyfills/bower.json b/modules/html5-polyfills/bower.json new file mode 100644 index 00000000..bd7c2b35 --- /dev/null +++ b/modules/html5-polyfills/bower.json @@ -0,0 +1,22 @@ +{ + "name": "html5-polyfills", + "description": "Collection of polyfills by Remy Sharp", + "homepage": "https://github.com/remy/polyfills", + "keywords": [ + "polyfill", + "client", + "browser" + ], + "author": { + "name": "Remy Sharp", + "email": "remy@leftlogic.com" + }, + "version": "0.0.20130621", + "readme": "This is my own collection of code snippets that support different native features of browsers using JavaScript (and if required, Flash in some cases).", + "readmeFilename": "README.md", + "_id": "html5-polyfills@0.0.20130621", + "repository": { + "type": "git", + "url": "git://github.com/remy/polyfills.git" + } +} \ No newline at end of file diff --git a/modules/html5-polyfills/classList.js b/modules/html5-polyfills/classList.js new file mode 100644 index 00000000..47eddeec --- /dev/null +++ b/modules/html5-polyfills/classList.js @@ -0,0 +1,70 @@ +(function () { + +if (typeof window.Element === "undefined" || "classList" in document.documentElement) return; + +var prototype = Array.prototype, + push = prototype.push, + splice = prototype.splice, + join = prototype.join; + +function DOMTokenList(el) { + this.el = el; + // The className needs to be trimmed and split on whitespace + // to retrieve a list of classes. + var classes = el.className.replace(/^\s+|\s+$/g,'').split(/\s+/); + for (var i = 0; i < classes.length; i++) { + push.call(this, classes[i]); + } +}; + +DOMTokenList.prototype = { + add: function(token) { + if(this.contains(token)) return; + push.call(this, token); + this.el.className = this.toString(); + }, + contains: function(token) { + return this.el.className.indexOf(token) != -1; + }, + item: function(index) { + return this[index] || null; + }, + remove: function(token) { + if (!this.contains(token)) return; + for (var i = 0; i < this.length; i++) { + if (this[i] == token) break; + } + splice.call(this, i, 1); + this.el.className = this.toString(); + }, + toString: function() { + return join.call(this, ' '); + }, + toggle: function(token) { + if (!this.contains(token)) { + this.add(token); + } else { + this.remove(token); + } + + return this.contains(token); + } +}; + +window.DOMTokenList = DOMTokenList; + +function defineElementGetter (obj, prop, getter) { + if (Object.defineProperty) { + Object.defineProperty(obj, prop,{ + get : getter + }); + } else { + obj.__defineGetter__(prop, getter); + } +} + +defineElementGetter(Element.prototype, 'classList', function () { + return new DOMTokenList(this); +}); + +})(); diff --git a/modules/html5-polyfills/component.json b/modules/html5-polyfills/component.json new file mode 100644 index 00000000..73c2261a --- /dev/null +++ b/modules/html5-polyfills/component.json @@ -0,0 +1,16 @@ +{ + "name": "polyfills", + "scripts": [ + "classList.js", + "dataset.js", + "device-motion-polyfill.js", + "EventSource.js", + "index.js", + "input-target.js", + "offline-events.js", + "range.js", + "sessionStorage.js", + "Storage.js" + ], + "main": "index.js" +} \ No newline at end of file diff --git a/modules/html5-polyfills/dataset.js b/modules/html5-polyfills/dataset.js new file mode 100644 index 00000000..8b3d6e5d --- /dev/null +++ b/modules/html5-polyfills/dataset.js @@ -0,0 +1,57 @@ +(function () { + var forEach = [].forEach, + regex = /^data-(.+)/, + dashChar = /\-([a-z])/ig, + el = document.createElement('div'), + mutationSupported = false, + match + ; + + function detectMutation() { + mutationSupported = true; + this.removeEventListener('DOMAttrModified', detectMutation, false); + } + + function toCamelCase(s) { + return s.replace(dashChar, function (m,l) { return l.toUpperCase(); }); + } + + function updateDataset() { + var dataset = {}; + forEach.call(this.attributes, function(attr) { + if (match = attr.name.match(regex)) + dataset[toCamelCase(match[1])] = attr.value; + }); + return dataset; + } + + // only add support if the browser doesn't support data-* natively + if (el.dataset != undefined) return; + + el.addEventListener('DOMAttrModified', detectMutation, false); + el.setAttribute('foo', 'bar'); + + function defineElementGetter (obj, prop, getter) { + if (Object.defineProperty) { + Object.defineProperty(obj, prop,{ + get : getter + }); + } else { + obj.__defineGetter__(prop, getter); + } + } + + defineElementGetter(Element.prototype, 'dataset', mutationSupported + ? function () { + if (!this._datasetCache) { + this._datasetCache = updateDataset.call(this); + } + return this._datasetCache; + } + : updateDataset + ); + + document.addEventListener('DOMAttrModified', function (event) { + delete event.target._datasetCache; + }, false); +})(); diff --git a/modules/html5-polyfills/device-motion-polyfill.js b/modules/html5-polyfills/device-motion-polyfill.js new file mode 100644 index 00000000..b0ef4a55 --- /dev/null +++ b/modules/html5-polyfills/device-motion-polyfill.js @@ -0,0 +1,253 @@ +/** + * DeviceMotion and DeviceOrientation polyfill + * by Remy Sharp / leftlogic.com + * MIT http://rem.mit-license.org + * + * Usage: used for testing motion events, include + * script in head of test document and allow popup + * to open to control device orientation. + */ +(!document.DeviceOrientationEvent || !document.DeviceMotionEvent) && (function () { + +// thankfully we don't have to do anything, because the event only fires on the window object +var polyfill = { + motion: !document.DeviceMotionEvent, + orientation: !document.DeviceOrientationEvent +}; + +if (polyfill.orientation) window.DeviceOrientationEvent = function () {}; + +if (polyfill.motion) window.DeviceMotionEvent = function () {}; + +// images - yes I do like to be self contained don't I?! :) +var imageSrc = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFsAAACpCAYAAABEbEGLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxODkwMDQ2NTNDNEYxMUUxQTYyOTk2M0QwMjU4OThGOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxODkwMDQ2NjNDNEYxMUUxQTYyOTk2M0QwMjU4OThGOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4OTAwNDYzM0M0RjExRTFBNjI5OTYzRDAyNTg5OEY4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4OTAwNDY0M0M0RjExRTFBNjI5OTYzRDAyNTg5OEY4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+jKamsAAAAtNJREFUeNrs2t1t01AYgGG3dACPkA3qEcIlV5QJSCdAnaAwAWKCsgFs0HQChwmaS+6SThCOKwuigtP4J+ln9LzSRyWaRMmTE8fHcJLt3yRNkeY8U9VDmkWa+ZAPOktTptmYf84qzU29GDtXQG49111XM7xuUy3QfF/oa2DHAbeijwReABp8vjVh+zI8zFw4fBxv7q3qF1jdp1s7Qx2ut9UfZ0Gh+26BJ313dANXRDuvXg38xuf1NjrKoeTxMBKlq/rCzlCt01zWP0N0GujjtjzQ4y4iYS+DPJd8ZI/bCTtKHw7wmLNIJwBngbCn9QZgOcDZSF4/XqgzrUjY26ds0//xZPs0E2zYgg1bsGHDFuwRt2sHOcfTqSJruPi1C/s1t07dZg2XGxxGHLNhCzZswYYNW7BhCzZs2IINW7BhCzZs2IINW7BhwxZs2IINW7BhwxZs2IING7ZgwxZs2IING7ZgwxZs2LAFG7Zgw4Yt2LAFG7Zgw4Yt2LAFGzZswYYt2LAFGzZswYYt2LBhCzZswYYt2LBhCzZswYYNW7BhCzZs2IINW7BhCzZs2IINW7BhwxZs2IIdvbMdv7vG06lJF+yP3BxGYAs2bGcj8VukmadZb/1dkWaaJh/Li6hO8TaB57YGbSofwWvYjAH7psWiqd6QVWTsyMfsr2kuW9x+3vL2viDrlmmuOtzve/0mwW7RlydfhG36BLv9Cu3zqVjCbgf2kve3qRl5OezjtY6KPXnh+x/sMPIj4POa9oSOhr3YfnLRdlv3PV7YTfSdcBnwCX7uAF0E3apfbD/JWdAnOWsJvRrLp7TM4l6Meu4S6kXgi1C/V/XJk5VRRj1tqq953GV/X89+X/+MuhN+1/TLqIeTMU759BP5quEUZZqp7+WCN2l+7nNjK3zAFb3vt3sJr9X0/l9kM+g7Z1WfMT27az1puQ2uVvu5Q/JjD9mff/Hfq18CDADFyFpnRW9JPwAAAABJRU5ErkJggg=='; + +var imageBackSrc = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFsAAACpCAIAAADLDtbcAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo3OTFCRTMwQTNDNjMxMUUxQTYyOTk2M0QwMjU4OThGOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo3OTFCRTMwQjNDNjMxMUUxQTYyOTk2M0QwMjU4OThGOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4OTAwNDZCM0M0RjExRTFBNjI5OTYzRDAyNTg5OEY4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4OTAwNDZDM0M0RjExRTFBNjI5OTYzRDAyNTg5OEY4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+zWf2VgAABt9JREFUeNrsnU1P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'; + +var id = (+new Date).toString(32), + body = document.documentElement, // yep - cheatin' Yud'up! :) + height = 320; + +// if the url hash doesn't contain tiltremote (our key) then fireup the popup, else we are the popup +if (window.location.hash.indexOf('tiltremote') === -1) { + initServer(); +} else { + initRemote(); +} + +function initServer() { + // old way didn't work. Shame, but I like the new way too :) + // var remote = window.open('data:text/html,', 'Tilt', 'width=300,height=' + height); + + var remote = window.open(window.location.toString() + '#tiltremote', 'Tilt', 'width=300,height=' + height); + if (!remote) { + alert('The remote control for the orientation event uses a popup') + } + + // TODO add remote-tilt.com support +} + +function initRemote() { + var TO_RADIANS = Math.PI / 180; + orientation = { + alpha: 180, + beta: 0, + gamma: 0 + }, + accelerationIncludingGravity = { x: 0, y: 0, z: -9.81 }, + guid = (+new Date).toString(32); + + function update(fromInput) { + var preview = document.getElementById('preview'); + preview.style.webkitTransform = 'rotateY('+ gamma.value + 'deg) rotate3d(1,0,0, '+ (beta.value*-1) + 'deg)'; + preview.parentNode.style.webkitTransform = 'rotate(' + (180-orientation.alpha) + 'deg)'; + + if (!fromInput) { + for (var key in orientation) { + var el = document.getElementById(key); + oninput.call(window, { target: el }); + } + } + + fireEvent(); + } + + function fireDeviceOrienationEvent() { + var event = document.createEvent('HTMLEvents'); + event.initEvent('deviceorientation', true, true); + event.eventName = 'deviceorientation'; + event.alpha = orientation.alpha; + event.beta = orientation.beta; + event.gamma = orientation.gamma; + + window.opener.dispatchEvent(event); + } + + function fireDeviceMotionEvent() { + var event = document.createEvent('HTMLEvents'); + event.initEvent('devicemotion', true, true); + event.eventName = 'devicemotion'; + + event.accelerationIncludingGravity = {}; + event.accelerationIncludingGravity.x = accelerationIncludingGravity.x; + event.accelerationIncludingGravity.y = accelerationIncludingGravity.y; + event.accelerationIncludingGravity.z = accelerationIncludingGravity.z; + + window.opener.dispatchEvent(event); + } + + function fireEvent() { + if (polyfill.orientation) fireDeviceOrienationEvent(); + if (polyfill.motion) fireDeviceMotionEvent(); + } + + // hides the old body - but doesn't prevent the JavaScript from running. + // just a clean up thing - lame, but tidier + setTimeout(function () { + var bodies = document.getElementsByTagName('body'), + body = bodies[1]; + if (bodies.length == 2) { + if (body.id == guid) body = bodies[0]; + } + document.documentElement.removeChild(body); + }); + + body.innerHTML = ['